[
  {
    "path": ".gitattributes",
    "content": "*.js text eol=lf\n*.mjs text eol=lf"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "Welcome! Please use this template for reporting bugs or requesting features. For questions about using Workbox, the best place to ask is Stack Overflow, tagged with `[workbox]`: https://stackoverflow.com/questions/ask?tags=workbox\n\n**Library Affected**:\n_workbox-sw, workbox-build, etc._\n\n**Browser & Platform**:\n_E.g. Google Chrome v51.0.1 for Android, or \"all browsers\"._\n\n**Issue or Feature Request Description**:\n_Your request here._\n\n_When reporting bugs, please include relevant JavaScript Console logs and links to public URLs at which the issue could be reproduced._\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "**Prior to creating a pull request, please follow all the steps in the [contributing guide](https://github.com/GoogleChrome/workbox/blob/v6/CONTRIBUTING.md).**\n\nFixes #_issue number_\n\n_Description of what's changed/fixed._\n"
  },
  {
    "path": ".github/workflows/pull-request.yml",
    "content": "name: Test Suite\n\non: [pull_request]\n\njobs:\n  Node_Tests_Windows:\n    runs-on: windows-latest\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.nvmrc'\n\n      - name: Get npm cache directory\n        id: npm-cache\n        run: |\n          echo \"::set-output name=dir::$(npm config get cache)\"\n      - uses: actions/cache@v4\n        with:\n          path: ${{ steps.npm-cache.outputs.dir }}\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Setup\n        run: |\n          npm ci\n          npx gulp build\n\n      - run: npx gulp test_node\n\n  Full_Suite_Mac:\n    runs-on: macos-latest\n\n    steps:\n      - uses: actions/checkout@v5\n\n      - uses: actions/setup-node@v6\n        with:\n          node-version-file: '.nvmrc'\n\n      - uses: actions/cache@v4\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - uses: actions/cache@v4\n        with:\n          path: ~/.selenium-assistant\n          key: ${{ runner.os }}\n\n      - name: Setup\n        run: |\n          sudo safaridriver --enable\n          npm ci\n          npx gulp build\n\n      - run: npx gulp test\n"
  },
  {
    "path": ".github/workflows/scorecards-analysis.yml",
    "content": "name: Scorecards supply-chain security\non:\n  # Only the default branch is supported.\n  branch_protection_rule:\n  schedule:\n    - cron: '33 8 * * 4'\n  push:\n    branches: [v7]\n\n# Declare default permissions as read only.\npermissions: read-all\n\njobs:\n  analysis:\n    name: Scorecards analysis\n    runs-on: ubuntu-latest\n    permissions:\n      # Needed to upload the results to code-scanning dashboard.\n      security-events: write\n      # Used to receive a badge. (Upcoming feature)\n      id-token: write\n      actions: read\n      contents: read\n\n    steps:\n      - name: 'Checkout code'\n        uses: actions/checkout@v5\n        with:\n          persist-credentials: false\n\n      - name: 'Run analysis'\n        uses: ossf/scorecard-action@v2.4.3\n        with:\n          results_file: results.sarif\n          results_format: sarif\n          # Read-only PAT token. To create it,\n          # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation.\n          repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}\n          # Publish the results to enable scorecard badges. For more details, see\n          # https://github.com/ossf/scorecard-action#publishing-results.\n          # For private repositories, `publish_results` will automatically be set to `false`, regardless\n          # of the value entered here.\n          publish_results: true\n\n      # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF\n      # format to the repository Actions tab.\n      - name: 'Upload artifact'\n        uses: actions/upload-artifact@v5\n        with:\n          name: SARIF file\n          path: results.sarif\n          retention-days: 5\n\n      # Upload the results to GitHub's code scanning dashboard.\n      - name: 'Upload to code-scanning'\n        uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nbuild\nlerna-debug.log\nnode_modules\nnpm-debug.log\npackages/**/LICENSE\ntemp\ntmp-*\n!packages/workbox-cli/test/static/example-project-1/node_modules\ncoverage/\ngenerated-release-files/\nworkbox-*.json\n.esm-cache/\n.nyc_output/\n.rpt2_cache/\ndocs\nlocal-builds/\nfirebase-debug.log\n.firebase\n\n# Generated TypeScript files and build data\ntsconfig.tsbuildinfo\npackages/workbox-*/**/*.d.ts\n!packages/workbox-*/src/**/*.d.ts\npackages/workbox-*/**/*.js\n!packages/workbox-build/**/*.js\n!packages/workbox-webpack-plugin/**/*.js\n\n# Individual package ignores during TypeScript migration\npackages/workbox-background-sync/**/*.mjs\npackages/workbox-broadcast-update/**/*.mjs\npackages/workbox-cacheable-response/**/*.mjs\npackages/workbox-core/**/*.mjs\npackages/workbox-expiration/**/*.mjs\npackages/workbox-google-analytics/**/*.mjs\npackages/workbox-navigation-preload/**/*.mjs\npackages/workbox-precaching/**/*.mjs\npackages/workbox-range-requests/**/*.mjs\npackages/workbox-recipes/**/*.mjs\npackages/workbox-routing/**/*.mjs\npackages/workbox-strategies/**/*.mjs\npackages/workbox-streams/**/*.mjs\npackages/workbox-window/**/*.mjs\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n\n# Please see https://typicode.github.io/husky/#/?id=command-not-found\n# if you have trouble running this command.\n\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint-staged\n"
  },
  {
    "path": ".husky/pre-push",
    "content": "#!/bin/sh\n\n# Please see https://typicode.github.io/husky/#/?id=command-not-found\n# if you have trouble running this command.\n\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint\n"
  },
  {
    "path": ".ncurc.js",
    "content": "// We use `npx npm-check-updates` to find updates to dependencies.\n// Some dependencies have breaking changes that we can't resolve.\n// This config file excludes those dependencies from the checks\n// until we're able to remediate our code to deal with them.\n\nmodule.exports = {\n  reject: [\n    // See https://github.com/GoogleChrome/workbox/issues/2479\n    'service-worker-mock',\n  ],\n};\n"
  },
  {
    "path": ".nvmrc",
    "content": "20\n"
  },
  {
    "path": ".prettierignore",
    "content": "_version.ts\n.nyc_output\n.vscode\n*.hbs\n*.log\nbuild\ncoverage\ndocs\ngenerated-release-files\nnode_modules\npackage-lock.json\npackages/**/*.d.ts\npackages/**/*.js\npackages/**/*.mjs\npackages/workbox-build/src/schema/*.json\ntemp\ntest/workbox-webpack-plugin/static/injected-manifest.json\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nAll Google open source projects are covered by our [community guidelines](https://opensource.google/conduct/) which define the kind of respectful behavior we expect of all participants.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.\n\nPlease fill out either the individual or corporate Contributor License Agreement (CLA).\n\n- If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://developers.google.com/open-source/cla/individual).\n- If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://developers.google.com/open-source/cla/corporate).\n\nFollow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to\naccept your pull requests.\n\n## Contributing A Patch\n\n1. Submit an issue describing your proposed change to the repo in question.\n1. The repo owner will respond to your issue promptly.\n1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).\n1. Fork the repo, develop and test your code changes (see details below).\n1. Ensure that your code adheres to the existing style in the sample to which you are contributing.\n1. Submit a pull request.\n\n## Setting up your environment\n\nWorkbox uses [`node`](https://nodejs.org/) and its related toolchain (`npm`, etc.) to install dependencies and run the build and test processes. Please ensure that you have a working `node` installation before proceeding.\n\nWorkbox uses `git` hooks via [`husky`](https://typicode.github.io/husky/#/) to automatically run code formatters and linters when committing and pushing code to GitHub. If you're running into issues with the `git` hooks, you may need to [create a `~/.huskyrc` file](https://typicode.github.io/husky/#/?id=command-not-found) to set up your `$PATH` correctly.\n\nIt's expected that the Workbox development environment should work on Windows, macOS, and Linux. If you encounter any platform-specific issues, please [open a bug](https://github.com/GoogleChrome/workbox/issues/new).\n\n## Testing your contribution\n\nWhen making local changes, you'll probably want to ensure that your code builds and passes our test suite. To do this, run the following in your local clone of the repo:\n\n```sh\n$ npm ci\n\n$ npm run gulp build\n\n$ npm run gulp test\n```\n\nNote that on Windows, `npm run gulp test` will only run a subset of our test suite. The full test suite will always be run as part of the GitHub continuous integration environment against your pull request.\n\nWhen you add a new feature or fix a bug, please check the test suite to see if its appropriate to add or modify an existing test to cover the updated functionality.\n\n## Running a subset of the tests\n\nWorkbox's test suite is split in two parts: one for the `node`-based tooling (`workbox-cli`, `workbox-build`, `workbox-webpack-plugin`) and one for the browser-based code.\n\nTo run the tests for just the `node`-based tooling:\n\n```sh\nnpm run gulp test_node\n```\n\nTo interactively run tests for the browser-based code, launch the test server:\n\n```sh\nnpm run gulp test_server\n```\n\nThen open a web browser to http://localhost:3004/ and navigate to the test suite for the package you're interested in. For example, to run the tests for `workbox-strategies`, go to http://localhost:3004/test/workbox-streams/sw/\n\nTo do an automated run of the browser-based test suite against the full set of supported browsers, run:\n\n```sh\nnpm run gulp test_integration\n```\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2018 Google LLC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<img src='https://user-images.githubusercontent.com/110953/28352645-7a8a66d8-6c0c-11e7-83af-752609e7e072.png' width='500px'/>\n\n# Welcome to Workbox!\n\nWorkbox is a collection of JavaScript libraries for\n[Progressive Web Apps](https://web.dev/progressive-web-apps/).\n\n## Documentation\n\n- [Overview](https://developer.chrome.com/docs/workbox/)\n- [Get started](https://developer.chrome.com/docs/workbox/what-is-workbox)\n- [Contribute](CONTRIBUTING.md)\n\n## Maintenance update\n\nWorkbox is a powerful library originally developed by members of Chrome's\ndeveloper relations team to facilitate the creation of Progressive Web Apps\nand to improve the offline experience of web applications. It offers a suite\nof tools and strategies for efficiently caching and serving web assets,\nmanaging service workers, and handling offline scenarios. Workbox simplifies\nthe implementation of common caching patterns and provides developers with a\ncomprehensive toolkit to build robust, resilient web applications. From now\non, [Chrome's Aurora team](https://developer.chrome.com/docs/aurora) will be\nthe new owners of Workbox.\n\n## Contributing\n\nDevelopment happens in the open on GitHub. We're thankful to the community for\ncontributing any improvements.\n\nPlease read the [guide to contributing](CONTRIBUTING.md) for information about\nsetting up your environment and other requirements prior to filing any\npull requests.\n\n## License\n\nMIT. See [LICENSE](LICENSE) for details.\n"
  },
  {
    "path": "cdn-details.json",
    "content": "{\n  \"origin\": \"https://storage.googleapis.com\",\n  \"bucketName\": \"workbox-cdn\",\n  \"releasesDir\": \"releases\"\n}\n"
  },
  {
    "path": "demos/README.md",
    "content": "# workbox-module-demos\n\nContains sample demos that are deployed to Glitch at https://glitch.com/@philkrie/workbox-demos and used as examples at https://developers.google.com/web/tools/workbox/modules\n"
  },
  {
    "path": "demos/src/workbox-background-sync-demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-background-sync demo</title>\n    <meta\n      name=\"workbox-background-sync demo\"\n      content=\"An example to demonstrate the workbox-background-sync module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-background-sync Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>\n        <a href=\"https://developers.google.com/web/tools/chrome-devtools/#open\"\n          >Open DevTools.</a\n        >\n      </li>\n      <li>Switch to the Network panel.</li>\n      <li>Toggle `Disable cache` checkbox on (it's unchecked by default).</li>\n      <li>Click `Make Fetch request` button below.</li>\n      <li>Observe that request is successfully completed with status 200.</li>\n      <li>\n        Disconnect from actual network (DevTools offline doesn't work for\n        background sync).\n      </li>\n      <li>Again, click `Make Fetch request` button below.</li>\n      <li>Observe that request is aborted due to no network.</li>\n      <li>\n        Connect to your network and observe the Network panel in DevTools.\n      </li>\n      <li>\n        Once the network is connected, you will see that the request is retried\n        automatically upon the `sync` event.\n      </li>\n    </ol>\n\n    <button class=\"fetch\">Make fetch request</button>\n    <p>\n      <script>\n        document.querySelector('.fetch').addEventListener('click', () => {\n          fetch('example.txt');\n        });\n\n        navigator.serviceWorker.register('./sw.js');\n      </script>\n\n      <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n        >Back to Demos</a\n      >\n    </p>\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-background-sync-demo/package.json",
    "content": "{\n  \"name\": \"workbox-background-sync\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Background Sync Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-background-sync-demo/public/example.txt",
    "content": "example text"
  },
  {
    "path": "demos/src/workbox-background-sync-demo/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\n// Note: Ignore the error that Glitch raises about workbox being undefined.\nworkbox.setConfig({\n  debug: true,\n});\n\nconst bgSyncPlugin = new workbox.backgroundSync.BackgroundSyncPlugin(\n  'myQueueName',\n);\n\nworkbox.routing.registerRoute(\n  ({url}) => url.pathname === '/example.txt',\n  new workbox.strategies.NetworkOnly({\n    plugins: [bgSyncPlugin],\n  }),\n);\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-background-sync-demo/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-broadcast-update-demo/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-broadcast-update demo</title>\n    <meta\n      name=\"workbox-broadcast-update demo\"\n      content=\"An example to demonstrate the workbox-broadcast-update module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-broadcast-update Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click any of the buttons below and view the logs:\n        <ul>\n          <li>\n            <p>\n              <button class=\"trigger-broadcast\">Trigger a Broadcast</button>\n            </p>\n          </li>\n        </ul>\n      </li>\n    </ol>\n\n    <script>\n      const triggerBroadcast = document.querySelector('.trigger-broadcast');\n\n      window.addEventListener('load', () => {\n        navigator.serviceWorker.register('./sw.js').then((reg) => {\n          triggerBroadcast.addEventListener('click', () => {\n            const message = {\n              command: 'trigger-broadcast',\n            };\n            reg.active.postMessage(message);\n          });\n\n          navigator.serviceWorker.addEventListener('message', (event) => {\n            console.log(`Received a message from workbox-broadcast-update.`);\n            console.log(event.data);\n          });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-broadcast-update-demo/package.json",
    "content": "{\n  \"name\": \"workbox-broadcast-update\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Broadcast Update Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-broadcast-update-demo/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nconst broadcastUpdate = new workbox.broadcastUpdate.BroadcastCacheUpdate(\n  'broadcast-update-demo',\n);\n\nconst initialResponse = new Response('Response 1', {\n  headers: {\n    'last-modified': Date.now().toString(),\n  },\n});\n\nself.addEventListener('message', (event) => {\n  switch (event.data.command) {\n    case 'trigger-broadcast': {\n      const secondResponse = new Response('Response 2', {\n        headers: {\n          'last-modified': Date.now().toString(),\n        },\n      });\n\n      broadcastUpdate.notifyIfUpdated({\n        oldResponse: initialResponse,\n        newResponse: secondResponse,\n        request: new Request('exampleUrl'),\n        url: 'exampleUrl',\n        cacheName: 'exampleCacheName',\n      });\n      break;\n    }\n\n    default:\n      console.log(`Unknown command received in the service worker: `, event);\n  }\n});\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-broadcast-update-demo/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-cacheable-response/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-cacheable-response demo</title>\n    <meta\n      name=\"workbox-cacheable-response demo\"\n      content=\"An example to demonstrate the workbox-cacheable-response module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-cacheable-response Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click any of the buttons below and view the logs and network responses:\n        <ul>\n          <li>\n            <p>\n              <button class=\"with-good-header\">\n                With 'X-Is-Cacheable: true' Header\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"with-bad-header\">\n                With 'X-Is-Cacheable: false' Header\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"without-header\">\n                Without 'X-Is-Cacheable' Header\n              </button>\n            </p>\n          </li>\n        </ul>\n      </li>\n    </ol>\n\n    <script>\n      const withGoodHeaderBtn = document.querySelector('.with-good-header');\n      const withBadHeaderBtn = document.querySelector('.with-bad-header');\n      const withoutHeaderBtn = document.querySelector('.without-header');\n\n      const DEMO_REQUEST_URL = '/api/is-response-cacheable';\n\n      window.addEventListener('load', () => {\n        navigator.serviceWorker.register('./sw.js').then(() => {\n          withGoodHeaderBtn.addEventListener('click', () => {\n            fetch(DEMO_REQUEST_URL, {\n              headers: {\n                'X-Is-Cacheable': true,\n              },\n            });\n          });\n\n          withBadHeaderBtn.addEventListener('click', () => {\n            fetch(DEMO_REQUEST_URL, {\n              headers: {\n                'X-Is-Cacheable': false,\n              },\n            });\n          });\n\n          withoutHeaderBtn.addEventListener('click', () => {\n            fetch(DEMO_REQUEST_URL);\n          });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-cacheable-response/package.json",
    "content": "{\n  \"name\": \"workbox-cacheable-response\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Cacheable Response Demo Listener\",\n  \"main\": \"server.js\",\n  \"scripts\": {\n    \"start\": \"node server.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-cacheable-response/server.js",
    "content": "// server.js\n// where your node app starts\n\n// init project\nconst express = require('express');\nconst app = express();\n\n// we've started you off with Express,\n// but feel free to use whatever libs or frameworks you'd like through `package.json`.\n\n// http://expressjs.com/en/starter/basic-routing.html\napp.get('/', function (request, response) {\n  response.sendFile(__dirname + '/index.html');\n});\n\napp.get('/sw.js', function (request, response) {\n  response.sendFile(__dirname + '/sw.js');\n});\n\n// listen for requests :)\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n\napp.get('/api/is-response-cacheable', function (req, res) {\n  if (req.headers['x-is-cacheable']) {\n    const value = req.headers['x-is-cacheable'];\n    res.set('X-Is-Cacheable', value);\n    res.send(`This response has 'X-Is-Cacheable' header set to '${value}'`);\n  } else {\n    res.send(`This response has no 'X-Is-Cacheable' header`);\n  }\n});\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n"
  },
  {
    "path": "demos/src/workbox-cacheable-response/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nconst cacheable = new workbox.cacheableResponse.CacheableResponse({\n  statuses: [200],\n  headers: {\n    'X-Is-Cacheable': 'true',\n  },\n});\n\nconst handleCachableResponse = (event) => {\n  return fetch(event.request).then((response) => {\n    if (cacheable.isResponseCacheable(response)) {\n      console.log('Response meets the criteria');\n    } else {\n      console.log('Response does NOT meet the criteria');\n    }\n\n    return response;\n  });\n};\n\nself.addEventListener('fetch', (event) => {\n  switch (new URL(event.request.url).pathname) {\n    case '/api/is-response-cacheable':\n      event.respondWith(handleCachableResponse(event));\n      break;\n  }\n});\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-core/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-core demo</title>\n    <meta\n      name=\"workbox-core demo\"\n      content=\"An example to demonstrate the workbox-core module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-core demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button:<br /><button class=\"install-sw\">\n          Install Service Worker\n        </button>\n      </li>\n      <li>Checkout the logs for info on Workbox and getting started.</li>\n      <li>\n        Click this button to print out various types of logs:<br /><button\n          class=\"show-logs\"\n        >\n          Show Me Workbox Logs\n        </button>\n      </li>\n      <li>\n        Click this button to print out the Workbox Cache Names:<br /><button\n          class=\"show-cache-names\"\n        >\n          Show Me Workbox Cache Names\n        </button>\n      </li>\n    </ol>\n\n    <script>\n      const installSWBtn = document.querySelector('.install-sw');\n      const showLogsBtn = document.querySelector('.show-logs');\n      const cacheNamesBtn = document.querySelector('.show-cache-names');\n\n      installSWBtn.addEventListener('click', () => {\n        console.log('Installing service worker');\n        navigator.serviceWorker.register('/sw.js').then((reg) => {\n          showLogsBtn.addEventListener('click', () => {\n            const message = {\n              command: 'printLogs',\n            };\n            reg.active.postMessage(message);\n          });\n          cacheNamesBtn.addEventListener('click', () => {\n            const message = {\n              command: 'printCacheNames',\n            };\n            reg.active.postMessage(message);\n          });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-core/package.json",
    "content": "{\n  \"name\": \"workbox-core\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Core Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-core/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\n// Note: Ignore the error that Glitch raises about workbox being undefined.\nworkbox.setConfig({\n  debug: true,\n});\n\n// To avoid async issues, we load core before we call it in the callback\nworkbox.loadModule('workbox-core');\n\nconst printLogs = () => {\n  // ☠️ You should never so this - this is just so we can show off our logging.\n  workbox.core._private.logger.debug(`🐛 Oh hai! I'm a debug message.`);\n  workbox.core._private.logger.log(`🔧 Good ole log message.`);\n  workbox.core._private.logger.warn(`⚠️ Uh Oh.... I'm a warning.`);\n  workbox.core._private.logger.error(`☠️ Stuff is breaking. I'm an error.`);\n};\n\nconst printCacheNames = () => {\n  workbox.core._private.logger.log(`The caches used by Workbox are...`);\n  const cacheNames = workbox.core.cacheNames;\n  Object.keys(cacheNames).forEach((cacheId) => {\n    console.log(`    ${cacheId}: ${cacheNames[cacheId]}`);\n  });\n};\n\nself.addEventListener('message', (event) => {\n  switch (event.data.command) {\n    case 'printLogs':\n      printLogs();\n      break;\n    case 'printCacheNames':\n      printCacheNames();\n      break;\n    default:\n      console.log(`Unknown command received in the service worker: `, event);\n  }\n});\n"
  },
  {
    "path": "demos/src/workbox-core/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n///////////////////////////////////////////////////////////////////////////// */\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-expiration/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-expiration demo</title>\n    <meta\n      name=\"workbox-expiration demo\"\n      content=\"An example to demonstrate the workbox-expiration module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-expiration Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click the following buttons and look at the Cache and IndexedDB entries.\n        <ol>\n          <li>\n            <button class=\"entry-1\">Entry 1</button> (Expires in:\n            <span class=\"entry-expire-1\">Not Added Yet</span>)\n          </li>\n          <li>\n            <button class=\"entry-2\">Entry 2</button> (Expires in:\n            <span class=\"entry-expire-2\">Not Added Yet</span>)\n          </li>\n          <li>\n            <button class=\"entry-3\">Entry 3</button> (Expires in:\n            <span class=\"entry-expire-3\">Not Added Yet</span>)\n          </li>\n          <li>\n            <button class=\"entry-4\">Entry 4</button> (Expires in:\n            <span class=\"entry-expire-4\">Not Added Yet</span>)\n          </li>\n          <li>\n            <button class=\"entry-5\">Entry 5</button> (Expires in:\n            <span class=\"entry-expire-5\">Not Added Yet</span>)\n          </li>\n        </ol>\n      </li>\n      <li>\n        To remove any expired entries click here:\n        <p><button class=\"expire\">Expire Entries</button></p>\n      </li>\n    </ol>\n\n    <script>\n      const expireBtn = document.querySelector('.expire');\n\n      const TIMEOUT_IN_SECS = 30;\n      const MAX_ENTRIES = 3;\n\n      var entryQueue = [];\n\n      const setupTimeout = (btnElement, textElement) => {\n        textElement.textContent = `${btnElement.__workbox_timeoutCount}s`;\n        btnElement.__workbox_timeoutId = setTimeout(() => {\n          // TODO: FIX THIS TO TAKE FIRST ELEMENT OF CLASS LIST AND CHECK AGAIN ENTRY STACK\n          if (entryQueue.includes(btnElement.classList[0])) {\n            btnElement.__workbox_timeoutCount -= 1;\n            if (btnElement.__workbox_timeoutCount > 0) {\n              setupTimeout(btnElement, textElement);\n            } else {\n              textElement.textContent = `Expired (Too Old)`;\n            }\n          } else {\n            textElement.textContent = `Expired (Too Many)`;\n          }\n        }, 1000);\n      };\n\n      window.addEventListener('load', () => {\n        navigator.serviceWorker.register('./sw.js').then((reg) => {\n          for (let i = 0; i < 5; i++) {\n            const entryBtn = document.querySelector(`.entry-${i + 1}`);\n            const expireText = document.querySelector(`.entry-expire-${i + 1}`);\n            entryBtn.addEventListener('click', () => {\n              const message = {\n                command: 'update-entry',\n                id: i + 1,\n              };\n\n              var arrayEntry = `entry-${i + 1}`;\n\n              //If we refresh an entry that has already been added and is not expired\n              //we reorder it to the front of the queue\n              if (entryQueue.includes(arrayEntry)) {\n                entryQueue.splice(entryQueue.indexOf(arrayEntry), 1);\n              }\n              entryQueue.push(`entry-${i + 1}`);\n              if (entryQueue.length >= 4) {\n                entryQueue = entryQueue.slice(MAX_ENTRIES * -1);\n              }\n\n              reg.active.postMessage(message);\n\n              if (entryBtn.__workbox_timeoutId) {\n                clearTimeout(entryBtn.__workbox_timeoutId);\n              }\n\n              entryBtn.__workbox_timeoutCount = TIMEOUT_IN_SECS;\n              setupTimeout(entryBtn, expireText);\n            });\n          }\n\n          expireBtn.addEventListener('click', () => {\n            const message = {\n              command: 'expire-entries',\n            };\n            reg.active.postMessage(message);\n          });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-expiration/package.json",
    "content": "{\n  \"name\": \"workbox-expiration\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Expiration Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-expiration/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\n// Note: Ignore the error that Glitch raises about workbox being undefined.\nworkbox.setConfig({\n  debug: true,\n});\n\nconst expirationManager = new workbox.expiration.CacheExpiration(\n  'demo-cache-for-expiration',\n  {\n    maxEntries: 3,\n    maxAgeSeconds: 30,\n  },\n);\n\nconst updateEntry = async (entryID) => {\n  const openCache = await caches.open('demo-cache-for-expiration');\n\n  openCache.put(\n    `example-entry-${entryID}`,\n    new Response(`Hello from entry number ${entryID}`),\n  );\n\n  expirationManager.updateTimestamp(`example-entry-${entryID}`);\n};\n\nself.addEventListener('message', (event) => {\n  switch (event.data.command) {\n    case 'update-entry':\n      updateEntry(event.data.id);\n      break;\n    case 'expire-entries':\n      expirationManager.expireEntries();\n      break;\n    default:\n      console.log(`Unknown command received in the service worker: `, event);\n  }\n});\n"
  },
  {
    "path": "demos/src/workbox-expiration/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-google-analytics/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-google-analytics demo</title>\n    <meta\n      name=\"workbox-googleAnalytics demo\"\n      content=\"An example to demonstrate the workbox-google-analytics module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-google-analytics Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button:\n        <p><button class=\"make-analytics-call\">Make Analytics Call</button></p>\n      </li>\n      <li>\n        Turn off your machine's connection and press the button again. Of\n        course, the requests fail\n      </li>\n      <li>\n        Upon reconnecting to the network, workbox-googleAnalytics will empty the\n        request queue\n      </li>\n    </ol>\n\n    <!-- Global site tag (gtag.js) - Google Analytics -->\n    <script\n      async\n      src=\"https://www.googletagmanager.com/gtag/js?id=UA-77119321-5\"\n    ></script>\n    <script>\n      (function (i, s, o, g, r, a, m) {\n        i['GoogleAnalyticsObject'] = r;\n        (i[r] =\n          i[r] ||\n          function () {\n            (i[r].q = i[r].q || []).push(arguments);\n          }),\n          (i[r].l = 1 * new Date());\n        (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);\n        a.async = 1;\n        a.src = g;\n        m.parentNode.insertBefore(a, m);\n      })(\n        window,\n        document,\n        'script',\n        'https://www.google-analytics.com/analytics.js',\n        'ga',\n      );\n\n      ga('create', 'UA-77119321-5', 'auto');\n    </script>\n\n    <script>\n      const analyticsCallBtn = document.querySelector('.make-analytics-call');\n\n      navigator.serviceWorker.register('./sw.js').then((reg) => {\n        analyticsCallBtn.addEventListener('click', () => {\n          ga('send', 'pageview');\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-google-analytics/package.json",
    "content": "{\n  \"name\": \"workbox-google-analytics\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Google Analytics Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-google-analytics/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nworkbox.googleAnalytics.initialize();\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-google-analytics/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-navigation-preload/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-navigation-preload demo</title>\n    <meta\n      name=\"workbox-navigation-preload demo\"\n      content=\"An example to demonstrate the workbox-navigation-preload module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-navigation-preload Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>\n        workbox-navigation-preload enables navigation preload in browsers that\n        support it\n      </li>\n      <li>Open the dev tools console and reload this page</li>\n      <li>\n        There should be two requests for the HTML document: one initiated by the\n        service worker, and one by Preload\n      </li>\n    </ol>\n\n    <script>\n      window.addEventListener('load', () => {\n        navigator.serviceWorker.register('./sw.js');\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-navigation-preload/package.json",
    "content": "{\n  \"name\": \"workbox-navigation-preload\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Navigation Preload Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-navigation-preload/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\n// Enable navigation preload.\nworkbox.navigationPreload.enable();\n\n// Swap in NetworkOnly, CacheFirst, or StaleWhileRevalidate as needed.\nconst strategy = new workbox.strategies.NetworkFirst({\n  cacheName: 'cached-navigations',\n  plugins: [\n    // Any plugins, like workbox.expiration, etc.\n  ],\n});\n\nconst navigationRoute = new workbox.routing.NavigationRoute(strategy, {\n  // Optionally, provide a allowlist/denylist of RegExps to determine\n  // which paths will match this route.\n  // allowlist: [],\n  // denylist: [],\n});\n\nworkbox.routing.registerRoute(navigationRoute);\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-navigation-preload/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-precaching/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-precaching demo</title>\n    <meta\n      name=\"workbox-precaching demo\"\n      content=\"An example to demonstrate the workbox-precaching module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-precaching Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button:<br /><button class=\"install-sw-1\">\n          Install Service Worker\n        </button>\n      </li>\n      <li>Checkout the logs for info on what was precached.</li>\n      <li>\n        Explore Cache in the \"Application\" tab in DevTools.\n        <i>Note: You may need to right-click and refresh.</i>\n      </li>\n      <li>\n        Click this button:<br /><button class=\"install-sw-2\">\n          Install New Service Worker\n        </button>\n      </li>\n      <li>\n        Check logs, cache to highlight the new, updated and deleted entry.\n      </li>\n    </ol>\n\n    <script>\n      const firstBtn = document.querySelector('.install-sw-1');\n      const secondBtn = document.querySelector('.install-sw-2');\n\n      firstBtn.addEventListener('click', () => {\n        navigator.serviceWorker.register('./sw-1.js');\n      });\n      secondBtn.addEventListener('click', () => {\n        navigator.serviceWorker\n          .getRegistration()\n          .then((reg) => {\n            return reg.unregister();\n          })\n          .then(() => {\n            navigator.serviceWorker.register('./sw-2.js');\n          });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-precaching/package.json",
    "content": "{\n  \"name\": \"workbox-precaching\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Precaching Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-precaching/public/hello-world.1234.txt",
    "content": "Hello, World!\n\nI'm the first version of this file."
  },
  {
    "path": "demos/src/workbox-precaching/public/hello-world.5678.txt",
    "content": "Hello, World!\n\nI'm the second version of this file."
  },
  {
    "path": "demos/src/workbox-precaching/public/test-file.txt",
    "content": "#Danger\n\nThis file is not revisioned."
  },
  {
    "path": "demos/src/workbox-precaching/sw-1.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nworkbox.precaching.precacheAndRoute([\n  {url: '/', revision: '1'},\n  {url: 'test-file.txt', revision: '1'},\n  'hello-world.1234.txt',\n]);\n"
  },
  {
    "path": "demos/src/workbox-precaching/sw-2.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nworkbox.precaching.precacheAndRoute([\n  {url: '/', revision: '2'},\n  {url: 'test-file.txt', revision: '2'},\n  'hello-world.5678.txt',\n]);\n"
  },
  {
    "path": "demos/src/workbox-precaching/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw-1.js', (request, response) => {\n  response.sendFile(path.resolve('sw-1.js'));\n});\n\napp.get('/sw-2.js', (request, response) => {\n  response.sendFile(path.resolve('sw-2.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-range-requests/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-range-requests demo</title>\n    <meta\n      name=\"workbox-range-requests demo\"\n      content=\"An example to demonstrate the workbox-range-requests module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-range-requests Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button to request range 1 - 4 of 'hello, world.':<br /><button\n          class=\"make-range-request\"\n        >\n          Make Range Request\n        </button>\n      </li>\n      <li>Checkout the logs for info on the range request that was handled.</li>\n    </ol>\n\n    <script>\n      const makeRequestBtn = document.querySelector('.make-range-request');\n\n      caches\n        .open('range-requests-demo')\n        .then((cache) =>\n          cache.put('/range-request-example', new Response('hello, world.')),\n        );\n\n      navigator.serviceWorker.register('./sw.js').then(() => {\n        makeRequestBtn.addEventListener('click', () => {\n          fetch(\n            new Request('/range-request-example', {\n              headers: {\n                Range: `bytes=1-4`,\n              },\n            }),\n          )\n            .then((response) => response.text())\n            .then((responseText) => {\n              console.log(`Received response: '${responseText}'`);\n            });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-range-requests/package.json",
    "content": "{\n  \"name\": \"workbox-range-requests\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Range Requests Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-range-requests/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nworkbox.routing.registerRoute(\n  new RegExp('/range-request-example'),\n  new workbox.strategies.CacheOnly({\n    cacheName: 'range-requests-demo',\n    plugins: [new workbox.rangeRequests.RangeRequestsPlugin()],\n  }),\n);\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-range-requests/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-routing/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-precaching demo</title>\n    <meta\n      name=\"workbox-precaching demo\"\n      content=\"An example to demonstrate the workbox-precaching module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-routing Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button:<br /><button class=\"install-sw\">\n          Install Service Worker\n        </button>\n      </li>\n      <li><strong>Refresh the page</strong></li>\n      <li>\n        Notice that the image '/public/demo-img.png' was routed and is now a\n        party popper\n      </li>\n    </ol>\n\n    <img src=\"demo-img.png\" style=\"width: 300px; height: 300px\" />\n\n    <p>\n      <script>\n        const firstBtn = document.querySelector('.install-sw');\n\n        firstBtn.addEventListener('click', () => {\n          navigator.serviceWorker.register('./sw.js');\n        });\n      </script>\n\n      <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n        >Back to Demos</a\n      >\n    </p>\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-routing/package.json",
    "content": "{\n  \"name\": \"workbox-routing\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Routing Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-routing/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\n// Set logs level to `debug` to view all logs\n// workbox.core.setLogLevel(workbox.core.LOG_LEVELS.debug);\n\n// Set up a route to alter the demo-img\nconst matchCb = ({url, event}) => {\n  return url.pathname === '/demo-img.png';\n};\n\nconst handlerCb = ({url, event, params}) => {\n  return fetch('/demo-popper.png');\n};\n\n// First parameter can be a string, RegExp, workbox Route, or a match callback (used here)\n// Second parameter handles the response and must return a Response promise\nworkbox.routing.registerRoute(matchCb, handlerCb);\n\nconst GLITCH_ICO_URL = 'https://glitch.com/edit/favicon-app.ico';\nworkbox.routing.registerRoute(GLITCH_ICO_URL, () => {\n  // Demonstrating when an error is thrown by a Route.\n  throw new Error(`Example error thrown from the default handler`);\n});\n\nworkbox.routing.setCatchHandler(({event}) => {\n  return fetch(event.request);\n});\n"
  },
  {
    "path": "demos/src/workbox-routing/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n///////////////////////////////////////////////////////////////////////////// */\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-strategies/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-strategies demo</title>\n    <meta\n      name=\"workbox-strategies demo\"\n      content=\"An example to demonstrate the workbox-strategies module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-strategies Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click any of the buttons below and view the logs:\n        <ul>\n          <li>\n            <p>This will attempt to get a request from an empty cache.</p>\n            <p>\n              <button class=\"cache-only-empty-cache\">\n                Cache Only Request (To Empty Cache)\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"cache-only-populated-cache\">\n                Cache Only Request (To Populated Cache)\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"cache-first\">\n                Cache First Request (Try it Several Times)\n              </button>\n            </p>\n          </li>\n          <li>\n            <p><button class=\"network-only\">Network Only Request</button></p>\n          </li>\n          <li>\n            <p>\n              <button class=\"network-first-valid\">\n                Network First Request (Valid URL)\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"network-first-failing\">\n                Network First Request (Failing URL)\n              </button>\n            </p>\n          </li>\n          <li>\n            <p>\n              <button class=\"stale-while-revalidate\">\n                Stale While Revalidate Request\n              </button>\n            </p>\n          </li>\n        </ul>\n      </li>\n    </ol>\n\n    <script>\n      const cacheOnlyEmpty = document.querySelector('.cache-only-empty-cache');\n      const cacheOnlyPopulated = document.querySelector(\n        '.cache-only-populated-cache',\n      );\n      const cacheFirst = document.querySelector('.cache-first');\n      const networkOnly = document.querySelector('.network-only');\n      const networkFirstValid = document.querySelector('.network-first-valid');\n      const networkFirstFailing = document.querySelector(\n        '.network-first-failing',\n      );\n      const staleWhileRevalidate = document.querySelector(\n        '.stale-while-revalidate',\n      );\n\n      window.addEventListener('load', () => {\n        navigator.serviceWorker.register('./sw.js').then(() => {\n          cacheOnlyEmpty.addEventListener('click', () => {\n            fetch('cache-only-empty-cache.txt').catch(() => {});\n          });\n\n          cacheOnlyPopulated.addEventListener('click', () => {\n            fetch('cache-only-populated-cache').then((response) => {\n              return response.text();\n            });\n          });\n\n          cacheFirst.addEventListener('click', () => {\n            fetch('cache-first.txt').then((response) => {\n              return response.text();\n            });\n          });\n\n          networkOnly.addEventListener('click', () => {\n            fetch('network-only.txt').then((response) => {\n              return response.text();\n            });\n          });\n\n          networkFirstValid.addEventListener('click', () => {\n            fetch('network-first.txt').then((response) => {\n              return response.text();\n            });\n          });\n\n          networkFirstFailing.addEventListener('click', () => {\n            fetch('network-first-404.txt').catch(() => {});\n          });\n\n          staleWhileRevalidate.addEventListener('click', () => {\n            fetch('stale-while-revalidate.txt').then((response) => {\n              return response.text();\n            });\n          });\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-strategies/public/cache-first.txt",
    "content": "Hello from CacheFirst"
  },
  {
    "path": "demos/src/workbox-strategies/public/network-first.txt",
    "content": "Hello from NetworkFirst"
  },
  {
    "path": "demos/src/workbox-strategies/public/network-only.txt",
    "content": "Hello from NetworkOnly"
  },
  {
    "path": "demos/src/workbox-strategies/public/stale-while-revalidate.txt",
    "content": "Hello from StaleWhileRevalidate"
  },
  {
    "path": "demos/src/workbox-strategies/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n// To avoid async issues, we load strategies before we call it in the event listener\nworkbox.loadModule('workbox-strategies');\n// Note: Ignore the error that Glitch raises about workbox being undefined.\nworkbox.setConfig({\n  debug: true,\n});\n\nself.addEventListener('fetch', (event) => {\n  const request = event.request;\n  switch (new URL(event.request.url).pathname) {\n    case '/cache-only-empty-cache.txt': {\n      const cacheOnlyEmpty = new workbox.strategies.CacheOnly();\n      event.respondWith(cacheOnlyEmpty.handle({event, request}));\n      break;\n    }\n    case '/cache-only-populated-cache': {\n      const cacheOnlyPopulated = new workbox.strategies.CacheOnly();\n      event.respondWith(cacheOnlyPopulated.handle({event, request}));\n      break;\n    }\n    case '/cache-first.txt': {\n      const cacheFirst = new workbox.strategies.CacheFirst();\n      event.respondWith(cacheFirst.handle({event, request}));\n      break;\n    }\n    case '/network-only.txt': {\n      const networkOnly = new workbox.strategies.NetworkOnly();\n      event.respondWith(networkOnly.handle({event, request}));\n      break;\n    }\n    case '/network-first.txt': {\n      const networkFirst = new workbox.strategies.NetworkFirst();\n      event.respondWith(networkFirst.handle({event, request}));\n      break;\n    }\n    case '/network-first-404.txt': {\n      const networkFirstInvalid = new workbox.strategies.NetworkFirst();\n      event.respondWith(networkFirstInvalid.handle({event, request}));\n      break;\n    }\n    case '/stale-while-revalidate.txt': {\n      const staleWhileRevalidate =\n        new workbox.strategies.StaleWhileRevalidate();\n      event.respondWith(staleWhileRevalidate.handle({event, request}));\n      break;\n    }\n  }\n});\n\n// This immediately deploys the service worker w/o requiring a refresh\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n\n// Populate the cache to illustrate cache-only-populated-cache route\nself.addEventListener('install', (event) => {\n  event.waitUntil(\n    caches.open(workbox.core.cacheNames.runtime).then((cache) => {\n      return cache.put(\n        new Request('/cache-only-populated-cache'),\n        new Response('Hello from the populated cache.'),\n      );\n    }),\n  );\n});\n"
  },
  {
    "path": "demos/src/workbox-strategies/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw1.js', (request, response) => {\n  response.sendFile(path.resolve('sw1.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-streams/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-streams demo</title>\n    <meta\n      name=\"workbox-streams demo\"\n      content=\"An example to demonstrate the workbox-streams module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-workbox-streams Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools</li>\n      <li>Go to the Console</li>\n      <li>\n        Click this button to add an <code>iframe</code> to this page, whose body\n        is created from multiple streamed sources:<br />\n        <button class=\"make-streams-request\">Add iframe</button>\n      </li>\n      <li>\n        Checkout the logs for info on how the streamed response was generated.\n      </li>\n    </ol>\n\n    <div id=\"iframes\"></div>\n\n    <script>\n      const makeRequestBtn = document.querySelector('.make-streams-request');\n\n      navigator.serviceWorker.register('./sw.js').then(() => {\n        makeRequestBtn.addEventListener('click', () => {\n          const iframe = document.createElement('iframe');\n          // This URL will be handled by the service worker.\n          iframe.src = 'iframe';\n          document.querySelector('#iframes').appendChild(iframe);\n        });\n      });\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-streams/package.json",
    "content": "{\n  \"name\": \"workbox-streams\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Streams Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node server.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-streams/server.js",
    "content": "// server.js\n// where your node app starts\n\n// init project\nconst express = require('express');\nconst app = express();\n\n// we've started you off with Express,\n// but feel free to use whatever libs or frameworks you'd like through `package.json`.\n\n// http://expressjs.com/en/starter/basic-routing.html\napp.get('/', function (request, response) {\n  response.sendFile(__dirname + '/index.html');\n});\n\napp.get('/sw.js', function (request, response) {\n  response.sendFile(__dirname + '/sw.js');\n});\n\n// listen for requests :)\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n\napp.get('/api/date', function (req, res) {\n  res.header('Content-Type', 'text/plain');\n  res.header('Cache-Control', 'no-cache');\n  res.send(`Received from the server at ${new Date().toLocaleString()}`);\n});\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n"
  },
  {
    "path": "demos/src/workbox-streams/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nconst CACHE_NAME = 'my-cache';\nconst START_CACHE_KEY = 'start';\nconst END_CACHE_KEY = 'end';\n\nself.addEventListener('install', (event) => {\n  event.waitUntil(\n    (async () => {\n      const cache = await caches.open(CACHE_NAME);\n      await Promise.all([\n        cache.put(START_CACHE_KEY, new Response('<html><head></head><body>')),\n        cache.put(END_CACHE_KEY, new Response('</body></html>')),\n      ]);\n    })(),\n  );\n});\n\n// Use a stale-while-revalidate strategy as a source for part of the response.\nconst apiStrategy = new workbox.strategies.StaleWhileRevalidate({\n  cacheName: 'apiStrategy',\n});\n\n// String together an artificially complex series of stream sources.\nconst streamsStrategy = workbox.streams.strategy([\n  () => caches.match(START_CACHE_KEY, {cacheName: CACHE_NAME}),\n  () => `<p>🎉 This <code>iframe</code> is composed of multiple streams.</p>`,\n  () => `<p>Here's an API call, using a stale-while-revalidate strategy:</p>`,\n  ({event}) =>\n    apiStrategy.handle({\n      event: event,\n      request: new Request('/api/date'),\n    }),\n  () => caches.match(END_CACHE_KEY, {cacheName: CACHE_NAME}),\n]);\n\n// Once the strategy is configured, the actual routing looks clean.\nworkbox.routing.registerRoute(new RegExp('iframe$'), streamsStrategy);\n\nworkbox.core.skipWaiting();\nworkbox.core.clientsClaim();\n"
  },
  {
    "path": "demos/src/workbox-sw/README.md",
    "content": "# workbox-sw-demo\n"
  },
  {
    "path": "demos/src/workbox-sw/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-sw demo</title>\n    <meta\n      name=\"workbox-sw demo\"\n      content=\"An example to demonstrate the workbox-sw module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-sw demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>Open DevTools and go to the console</li>\n      <li>\n        Click this button:<br /><button class=\"install-sw\">\n          Install Service Worker\n        </button>\n      </li>\n      <li>You should see a workbox message appear</li>\n      <li>\n        Refresh the page, go to the network tab, and check the \"Size\" of\n        demo-img.png. It should read (ServiceWorker)\n      </li>\n    </ol>\n\n    <!-- Image will be stored in a custom cache -->\n    <img src=\"demo-img.png\" style=\"width: 300px; height: 300px\" />\n\n    <script>\n      const installSWBtn = document.querySelector('.install-sw');\n      installSWBtn.addEventListener('click', () => {\n        navigator.serviceWorker.register('./sw.js');\n      });\n    </script>\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n        >Back to Demos</a\n      >\n    </p>\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-sw/package.json",
    "content": "{\n  \"name\": \"workbox-sw\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox SW Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-sw/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\n// Note: Ignore the error that Glitch raises about workbox being undefined.\nworkbox.setConfig({\n  debug: true,\n});\n\nworkbox.precaching.precacheAndRoute([\n  'https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css',\n]);\n\n// Demonstrates using default cache\nworkbox.routing.registerRoute(\n  new RegExp('.*\\\\.(?:js)'),\n  new workbox.strategies.NetworkFirst(),\n);\n\n// Demonstrates a custom cache name for a route.\nworkbox.routing.registerRoute(\n  new RegExp('.*\\\\.(?:png|jpg|jpeg|svg|gif)'),\n  new workbox.strategies.CacheFirst({\n    cacheName: 'image-cache',\n    plugins: [\n      new workbox.expiration.ExpirationPlugin({\n        maxEntries: 3,\n      }),\n    ],\n  }),\n);\n"
  },
  {
    "path": "demos/src/workbox-sw/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "demos/src/workbox-window/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>workbox-window demo</title>\n    <meta\n      name=\"workbox-window demo\"\n      content=\"An example to demonstrate the workbox-window module\"\n    />\n    <link\n      id=\"favicon\"\n      rel=\"icon\"\n      href=\"https://glitch.com/edit/favicon-app.ico\"\n      type=\"image/x-icon\"\n    />\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <style>\n      body {\n        margin-left: 5%;\n        font-family: 'Open Sans', sans-serif;\n      }\n      ol {\n        padding-left: 20px;\n      }\n      li {\n        margin-bottom: 5px;\n      }\n      button {\n        margin: 20px 0;\n        font-weight: bold;\n      }\n    </style>\n  </head>\n  <body>\n    <header>\n      <div>\n        <h1>workbox-window Demo</h1>\n      </div>\n    </header>\n\n    <ol>\n      <li>workbox-window runs in the window context, not the service worker</li>\n      <li>Open up DevTools and go to the console</li>\n    </ol>\n\n    <script type=\"module\">\n      import {Workbox} from 'https://storage.googleapis.com/workbox-cdn/releases/5.0.0-beta.1/workbox-window.prod.mjs';\n\n      if ('serviceWorker' in navigator) {\n        const wb = new Workbox('/sw.js');\n\n        wb.addEventListener('activated', (event) => {\n          // `event.isUpdate` will be true if another version of the service\n          // worker was controlling the page when this version was registered.\n          if (!event.isUpdate) {\n            console.log('Service worker activated for the first time!');\n\n            // If your service worker is configured to precache assets, those\n            // assets should all be available now.\n          }\n        });\n\n        wb.addEventListener('waiting', (event) => {\n          console.log(\n            `A new service worker has installed, but it can't activate` +\n              `until all tabs running the current version have fully unloaded.`,\n          );\n        });\n\n        // Register the service worker after event listeners have been added.\n        wb.register();\n\n        const message = async () => {\n          const swVersion = await wb.messageSW({type: 'GET_VERSION'});\n          console.log('Service Worker version:', swVersion);\n        };\n\n        message();\n      }\n    </script>\n\n    <a href=\"https://developers.google.com/web/tools/workbox/modules\"\n      >Back to Demos</a\n    >\n\n    <p>\n      <a href=\"https://developers.google.com/web/tools/workbox\">Docs</a> |\n      <a href=\"https://github.com/googlechrome/workbox\">GitHub</a> |\n      <a href=\"https://twitter.com/workboxjs\">@workboxjs</a>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "demos/src/workbox-window/package.json",
    "content": "{\n  \"name\": \"workbox-window\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Workbox Window Demo Git Listener\",\n  \"scripts\": {\n    \"start\": \"node updateServer.js\"\n  },\n  \"dependencies\": {\n    \"express\": \"^4.17.1\"\n  },\n  \"engines\": {\n    \"node\": \"8.x\"\n  },\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": "demos/src/workbox-window/sw.js",
    "content": "importScripts(\n  'https://storage.googleapis.com/workbox-cdn/releases/6.1.5/workbox-sw.js',\n);\n\nworkbox.setConfig({\n  debug: true,\n});\n\nconst SW_VERSION = '5.0.0';\n\naddEventListener('message', (event) => {\n  if (event.data.type === 'GET_VERSION') {\n    event.ports[0].postMessage(SW_VERSION);\n  }\n});\n"
  },
  {
    "path": "demos/src/workbox-window/updateServer.js",
    "content": "const express = require('express');\nconst app = express();\nconst path = require('path');\n\napp.get('/', (request, response) => {\n  response.sendFile(path.resolve('index.html'));\n});\n\napp.get('/sw.js', (request, response) => {\n  response.sendFile(path.resolve('sw.js'));\n});\n\napp.use(express.static('public'));\n\n/* /////////////////////////////////////////////////////////////////////////////\n The code below this comment is unrelated to the demo and used for maintenance\n vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv\n/////////////////////////////////////////////////////////////////////////////*/\n\nconst {execSync} = require('child_process');\nconst bodyParser = require('body-parser');\n\napp.use(bodyParser.json());\n\napp.post('/deploy', (request, response) => {\n  if (request.query.secret !== process.env.SECRET) {\n    response.status(401).send();\n    return;\n  }\n\n  const repoUrl = request.query.repo;\n  execSync(\n    `git checkout -- ./ && git pull -X theirs ${repoUrl} ` +\n      `glitch && refresh && git branch -D glitch`,\n  );\n  response.status(200).send();\n});\n\nconst listener = app.listen(process.env.PORT, function () {\n  console.log('Your app is listening on port ' + listener.address().port);\n});\n"
  },
  {
    "path": "gulp-tasks/analyze-properties.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst AnalyseBuildForProperties = require('./utils/analyse-properties');\n\nasync function analyze_properties() {\n  const analysisTool = new AnalyseBuildForProperties();\n  const results = await analysisTool.run();\n  for (const result of results) {\n    analysisTool.printDetails(result);\n  }\n}\n\nmodule.exports = {\n  analyze_properties,\n};\n"
  },
  {
    "path": "gulp-tasks/build-node-packages.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel} = require('gulp');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst TJS = require('typescript-json-schema');\nconst upath = require('upath');\n\nconst constants = require('./utils/constants');\nconst packageRunner = require('./utils/package-runner');\n\nasync function buildNodePackage(packagePath) {\n  const outputDirectory = upath.join(\n    packagePath,\n    constants.PACKAGE_BUILD_DIRNAME,\n  );\n\n  const configFile = upath.join(\n    __dirname,\n    'utils',\n    'node-projects-babel.config.json',\n  );\n\n  await execa(\n    'babel',\n    [\n      '--config-file',\n      configFile,\n      `${packagePath}/src`,\n      '--out-dir',\n      outputDirectory,\n      '--copy-files',\n    ],\n    {preferLocal: true},\n  );\n}\n\nasync function generateWorkboxBuildJSONSchema(packagePath) {\n  // We only want to do this for workbox-build, but this function might be\n  // run for any package, so exit early.\n  if (!packagePath.endsWith('workbox-build')) {\n    return;\n  }\n\n  const program = TJS.programFromConfig(\n    upath.join(packagePath, 'tsconfig.json'),\n  );\n  const generator = TJS.buildGenerator(program, {\n    noExtraProps: true,\n    required: true,\n  });\n  const optionTypes = [\n    'GenerateSWOptions',\n    'GetManifestOptions',\n    'InjectManifestOptions',\n    'WebpackGenerateSWOptions',\n    'WebpackInjectManifestOptions',\n  ];\n  for (const optionType of optionTypes) {\n    const schema = generator.getSchemaForSymbol(optionType);\n\n    if (schema.properties.manifestTransforms) {\n      schema.properties.manifestTransforms.items = {};\n    }\n\n    if (schema.properties.exclude) {\n      schema.properties.exclude.items = {};\n    }\n\n    if (schema.properties.include) {\n      schema.properties.include.items = {};\n    }\n\n    // See https://github.com/GoogleChrome/workbox/issues/2910\n    if (schema.definitions.OnSyncCallback) {\n      schema.definitions.OnSyncCallback = {};\n    }\n\n    if (schema.definitions.AbortSignal) {\n      schema.definitions.AbortSignal = {};\n    }\n\n    if (schema.definitions.RouteMatchCallback) {\n      schema.definitions.RouteMatchCallback = {};\n    }\n\n    if (schema.definitions.RouteHandlerCallback) {\n      schema.definitions.RouteHandlerCallback = {};\n    }\n\n    // See https://github.com/GoogleChrome/workbox/issues/2901\n    if (schema.definitions.WorkboxPlugin) {\n      for (const plugin of Object.keys(\n        schema.definitions.WorkboxPlugin.properties,\n      )) {\n        schema.definitions.WorkboxPlugin.properties[plugin] = {};\n      }\n    }\n\n    await fse.writeJSON(\n      upath.join(packagePath, 'src', 'schema', `${optionType}.json`),\n      schema,\n      {spaces: 2},\n    );\n  }\n}\n\nmodule.exports = {\n  build_node_packages: parallel(\n    packageRunner('build_node_packages', 'node', buildNodePackage),\n  ),\n  build_node_ts_packages: parallel(\n    packageRunner(\n      'build_node_ts_packages',\n      'node_ts',\n      generateWorkboxBuildJSONSchema,\n    ),\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/build-packages.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel, series} = require('gulp');\nconst del = require('del');\nconst fse = require('fs-extra');\nconst upath = require('upath');\n\nconst {\n  build_node_packages,\n  build_node_ts_packages,\n} = require('./build-node-packages');\nconst {build_sw_packages} = require('./build-sw-packages');\nconst {build_window_packages} = require('./build-window-packages');\nconst {transpile_typescript} = require('./transpile-typescript');\nconst constants = require('./utils/constants');\nconst packageRunner = require('./utils/package-runner');\n\nasync function cleanPackage(packagePath) {\n  // Delete generated files from the the TypeScript transpile.\n  if (await fse.pathExists(upath.join(packagePath, 'src', 'index.ts'))) {\n    // Store the list of deleted files, so we can delete directories after.\n    const deletedPaths = await del([\n      `${packagePath}/**/*.+(js|mjs|d.ts)`,\n      // Don't delete files in node_modules.\n      '!**/node_modules/**/*',\n      // Don't delete anything under src.\n      `!${packagePath}/src/**/*`,\n    ]);\n\n    // Any directories in `deletedPaths` that are top-level directories to the\n    // package should also be deleted since those directories should only\n    // contain generated `.mjs` and `.d.ts` files.\n    const directoriesToDelete = new Set();\n    for (const deletedPath of deletedPaths) {\n      const relativePath = upath.relative(packagePath, deletedPath);\n      const directory = relativePath.split(upath.sep)[0];\n      directoriesToDelete.add(upath.join(packagePath, directory));\n    }\n    await del([...directoriesToDelete]);\n  }\n\n  // Delete build files.\n  await del(upath.join(packagePath, constants.PACKAGE_BUILD_DIRNAME));\n\n  // Delete tsc artifacts (if present).\n  await del(upath.join(packagePath, 'tsconfig.tsbuildinfo'));\n}\n\n// Wrap this in a function since it's used multiple times.\nfunction cleanSequence() {\n  return parallel(packageRunner('build_packages_clean', 'all', cleanPackage));\n}\n\nmodule.exports = {\n  build_packages_clean: cleanSequence(),\n  build_packages: series(\n    // This needs to be a series, not in parallel, so that there isn't a\n    // race condition with the terser nameCache.\n    transpile_typescript,\n    series(build_sw_packages, build_window_packages),\n    parallel(build_node_packages, build_node_ts_packages),\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/build-sw-packages.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel, series} = require('gulp');\nconst {rollup} = require('rollup');\nconst fse = require('fs-extra');\nconst ol = require('common-tags').oneLine;\nconst upath = require('upath');\n\nconst constants = require('./utils/constants');\nconst logHelper = require('../infra/utils/log-helper');\nconst packageRunner = require('./utils/package-runner');\nconst pkgPathToName = require('./utils/pkg-path-to-name');\nconst rollupHelper = require('./utils/rollup-helper');\nconst versionModule = require('./utils/version-module');\n\n// This makes Rollup assume workbox-* will be added to the global\n// scope and replace references with the core namespace.\nfunction globals(moduleId) {\n  if (moduleId === 'workbox') {\n    return moduleId;\n  }\n\n  const splitImportPath = moduleId.split('/');\n  if (splitImportPath[0].indexOf('workbox-') !== 0) {\n    throw new Error(`Unknown global module ID: ${moduleId}`);\n  }\n\n  const packageName = splitImportPath.shift();\n  const packagePath = upath.join(__dirname, '..', 'packages', packageName);\n  const namespacePathParts = splitImportPath.map((importPathPiece) => {\n    // The browser namespace will need the file extension removed\n    return upath.basename(importPathPiece, upath.extname(importPathPiece));\n  });\n\n  if (namespacePathParts.length === 0) {\n    // Tried to pull in default export of module - this isn't allowed.\n    // A specific file must be referenced\n    throw new Error(ol`You cannot use a module directly. Instead, you must\n        specify a named import, to facilitate tree-shaking. Please fix the\n        import: '${moduleId}'`);\n  }\n\n  let additionalNamespace;\n  if (namespacePathParts.length > 1) {\n    if (namespacePathParts[0] !== '_private' || namespacePathParts.length > 2) {\n      // Tried to pull in default export of module - this isn't allowed.\n      // A specific file must be referenced\n      throw new Error(ol`You cannot use nested files. It must be a top level\n          (and public) file or a file under '_private' in a module. Please fix\n          the import: '${moduleId}'`);\n    }\n    additionalNamespace = namespacePathParts[0];\n  }\n\n  // Get a package's browserNamespace so we know where it will be\n  // on the global scope (i.e. workbox.<browserNamespace>)\n  try {\n    const pkg = require(upath.join(packagePath, 'package.json'));\n    return [pkg.workbox.browserNamespace, additionalNamespace]\n      .filter((value) => value && value.length > 0)\n      .join('.');\n  } catch (err) {\n    logHelper.error(`Unable to get browserNamespace for '${packageName}'`);\n    throw err;\n  }\n}\n\n// This ensures all workbox-* modules are treated as external and are\n// referenced as globals.\nfunction externalAndPure(importPath) {\n  return importPath.indexOf('workbox-') === 0;\n}\n\nasync function buildSWBundle(packagePath, buildType) {\n  const packageName = pkgPathToName(packagePath);\n  const packageIndex = upath.join(packagePath, 'index.mjs');\n\n  // First check if the bundle file exists, if it doesn't\n  // there is nothing to build\n  if (!(await fse.exists(packageIndex))) {\n    throw new Error(`Could not find ${packageIndex}`);\n  }\n\n  const pkgJson = require(upath.join(packagePath, 'package.json'));\n  if (!pkgJson.workbox || !pkgJson.workbox.browserNamespace) {\n    throw new Error(ol`You must define a 'workbox.browserNamespace' property in\n        ${packageName}/package.json`);\n  }\n\n  let outputFilename = pkgJson.workbox.outputFilename || packageName;\n  if (pkgJson.workbox.prodOnly) {\n    // Bail out early if this is a non-prod build.\n    if (buildType !== constants.BUILD_TYPES.prod) {\n      return Promise.resolve();\n    }\n  } else {\n    // Prod-only builds (above) don't need the build type, but when there's a\n    // dev and prod build we have to include it.\n    outputFilename += `.${buildType.slice(0, 4)}`;\n  }\n  outputFilename += '.js';\n\n  const namespace = pkgJson.workbox.browserNamespace;\n  const outputDirectory = upath.join(\n    packagePath,\n    constants.PACKAGE_BUILD_DIRNAME,\n  );\n\n  const plugins = rollupHelper.getDefaultPlugins(buildType);\n\n  const bundle = await rollup({\n    input: packageIndex,\n    external: externalAndPure,\n    treeshake: {\n      moduleSideEffects: externalAndPure ? 'no-external' : false,\n    },\n    plugins,\n    onwarn: (warning) => {\n      if (\n        buildType === constants.BUILD_TYPES.prod &&\n        warning.code === 'UNUSED_EXTERNAL_IMPORT'\n      ) {\n        // This can occur when using rollup-plugin-replace.\n        logHelper.warn(`[${warning.code}] ${warning.message}`);\n        return;\n      }\n\n      // The final builds should have no warnings.\n      if (warning.code && warning.message) {\n        throw new Error(\n          `Unhandled Rollup Warning: [${warning.code}] ` + `${warning.message}`,\n        );\n      } else {\n        throw new Error(`Unhandled Rollup Warning: ${warning}`);\n      }\n    },\n  });\n\n  await bundle.write({\n    file: upath.join(outputDirectory, outputFilename),\n    name: namespace,\n    sourcemap: true,\n    format: 'iife',\n    globals,\n    esModule: false,\n  });\n}\n\n// This reads a little cleaner with a function to generate the sub-sequences.\nfunction swBundleSequence() {\n  const builds = Object.keys(constants.BUILD_TYPES).map((type) =>\n    packageRunner(\n      'build_sw_packages_bundle',\n      'sw',\n      buildSWBundle,\n      constants.BUILD_TYPES[type],\n    ),\n  );\n\n  return series(builds);\n}\n\nmodule.exports = {\n  build_sw_packages: series(\n    parallel(\n      packageRunner('build_sw_packages_version_module', 'sw', versionModule),\n    ),\n    swBundleSequence(),\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/build-window-packages.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel, series} = require('gulp');\nconst {rollup} = require('rollup');\nconst fse = require('fs-extra');\nconst ol = require('common-tags').oneLine;\nconst upath = require('upath');\n\nconst constants = require('./utils/constants');\nconst logHelper = require('../infra/utils/log-helper');\nconst packageRunner = require('./utils/package-runner');\nconst pkgPathToName = require('./utils/pkg-path-to-name');\nconst rollupHelper = require('./utils/rollup-helper');\nconst versionModule = require('./utils/version-module');\n\nasync function buildWindowBundle(packagePath, buildType) {\n  const packageName = pkgPathToName(packagePath);\n  const packageIndex = upath.join(packagePath, 'index.mjs');\n\n  if (!(await fse.exists(packageIndex))) {\n    throw new Error(`Could not find ${packageIndex}`);\n  }\n\n  const outputDirectory = upath.join(\n    packagePath,\n    constants.PACKAGE_BUILD_DIRNAME,\n  );\n\n  const esmFilename = `${packageName}.${buildType.slice(0, 4)}.mjs`;\n  const esmLegacyFilename = `${packageName}.${buildType.slice(0, 4)}.es5.mjs`;\n  const umdFilename = `${packageName}.${buildType.slice(0, 4)}.umd.js`;\n\n  const onwarn = (warning) => {\n    // This can occur when using rollup-plugin-replace.\n    if (\n      buildType === constants.BUILD_TYPES.prod &&\n      warning.code === 'UNUSED_EXTERNAL_IMPORT'\n    ) {\n      logHelper.warn(`[${warning.code}] ${warning.message}`);\n      return;\n    }\n\n    // The final builds should have no warnings.\n    if (warning.code && warning.message) {\n      throw new Error(ol`Unhandled Rollup Warning:\n          [${warning.code}] ${warning.message}`);\n    } else {\n      throw new Error(`Unhandled Rollup Warning: ${warning}`);\n    }\n  };\n\n  const mjsBundle = await rollup({\n    input: packageIndex,\n    plugins: rollupHelper.getDefaultPlugins(buildType, 'esm', false),\n    onwarn,\n  });\n\n  const es5Bundle = await rollup({\n    input: packageIndex,\n    plugins: rollupHelper.getDefaultPlugins(buildType, 'esm', true),\n    onwarn,\n  });\n\n  const umdBundle = await rollup({\n    input: packageIndex,\n    plugins: rollupHelper.getDefaultPlugins(buildType, 'umd', true),\n    onwarn,\n  });\n\n  // Generate both a native module and a UMD module (for compat).\n  await Promise.all([\n    mjsBundle.write({\n      file: upath.join(outputDirectory, esmFilename),\n      sourcemap: true,\n      format: 'esm',\n    }),\n    es5Bundle.write({\n      file: upath.join(outputDirectory, esmLegacyFilename),\n      sourcemap: true,\n      format: 'esm',\n    }),\n    umdBundle.write({\n      file: upath.join(outputDirectory, umdFilename),\n      sourcemap: true,\n      format: 'umd',\n      name: 'workbox',\n    }),\n  ]);\n}\n\n// This reads a little cleaner with a function to generate the sub-sequences.\nfunction windowBundleSequence() {\n  const builds = Object.keys(constants.BUILD_TYPES).map((type) =>\n    packageRunner(\n      'build_window_packages_bundle',\n      'window',\n      buildWindowBundle,\n      constants.BUILD_TYPES[type],\n    ),\n  );\n\n  return series(builds);\n}\n\nmodule.exports = {\n  build_window_packages: series(\n    parallel(\n      packageRunner(\n        'build_window_packages_version_module',\n        'window',\n        versionModule,\n      ),\n    ),\n    windowBundleSequence(),\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/build.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {series} = require('gulp');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst upath = require('upath');\n\nconst {build_packages} = require('./build-packages');\n\n// This is needed for workbox-build but is also used by the rollup-helper\n// to add CDN details to workbox-sw.\n// Make sure this runs **before** build_lerna_bootstrap.\nasync function build_update_cdn_details() {\n  const cdnDetails = await fse.readJSON(\n    upath.join(__dirname, '..', 'cdn-details.json'),\n  );\n\n  const workboxBuildPath = upath.join(\n    __dirname,\n    '..',\n    'packages',\n    'workbox-build',\n  );\n\n  const workboxBuildCdnDetailsPath = upath.join(\n    workboxBuildPath,\n    'src',\n    'cdn-details.json',\n  );\n\n  const workboxBuildPkg = await fse.readJSON(\n    upath.join(workboxBuildPath, 'package.json'),\n  );\n\n  cdnDetails.latestVersion = workboxBuildPkg.version;\n\n  await fse.writeJson(workboxBuildCdnDetailsPath, cdnDetails, {\n    spaces: 2,\n  });\n}\n\nasync function build_lerna_bootstrap() {\n  await execa('lerna', ['bootstrap'], {preferLocal: true});\n}\n\nmodule.exports = {\n  build: series(\n    build_update_cdn_details,\n    build_lerna_bootstrap,\n    build_packages,\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/docs.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {series, watch} = require('gulp');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst upath = require('upath');\n\nconst logHelper = require('../infra/utils/log-helper');\n\nconst DOCS_DIRECTORY = upath.join(__dirname, '..', 'docs');\n\nasync function docs_build() {\n  await fse.remove(DOCS_DIRECTORY);\n\n  const queryString = [\n    `projectRoot=/`,\n    `basepath=/`,\n    `productName=Workbox`,\n  ].join('&');\n\n  const params = [\n    '-c',\n    upath.join(__dirname, '..', 'jsdoc.conf'),\n    '-d',\n    DOCS_DIRECTORY,\n  ];\n\n  if (!(global.cliOptions && global.cliOptions.pretty)) {\n    logHelper.warn(`\n\nThese docs will look ugly, but they will more accurately match what\nis shown on developers.google.com.\n\nYou can view a friendlier UI by running\n\n'gulp docs --pretty'\n`);\n    params.push(\n      '--template',\n      upath.join(\n        __dirname,\n        '..',\n        'infra',\n        'templates',\n        'reference-docs',\n        'jsdoc',\n      ),\n      '--query',\n      queryString,\n    );\n  }\n\n  if (global.cliOptions && global.cliOptions.debugDocs) {\n    params.push('--debug');\n  }\n\n  await execa('jsdoc', params, {preferLocal: true});\n}\n\nfunction docs_watch() {\n  const watcher = watch('packages/**/*', docs_build);\n  watcher.on('error', (err) => logHelper.error(`Docs failed to build: `, err));\n}\n\nmodule.exports = {\n  docs_build,\n  docs: series(docs_build, docs_watch),\n};\n"
  },
  {
    "path": "gulp-tasks/lint.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel} = require('gulp');\nconst execa = require('execa');\n\nasync function lint_js() {\n  await execa(\n    'eslint',\n    [\n      '**/*.{js,mjs}',\n      '--config',\n      'javascript.eslintrc.js',\n      '--ignore-path',\n      '.gitignore',\n    ],\n    {preferLocal: true},\n  );\n}\n\nasync function lint_ts() {\n  await execa(\n    'eslint',\n    [\n      '**/*.ts',\n      '--config',\n      'typescript.eslintrc.js',\n      '--ignore-path',\n      '.gitignore',\n    ],\n    {preferLocal: true},\n  );\n}\n\nmodule.exports = {\n  lint_js,\n  lint_ts,\n  // Temporarily disable lint_ts until we upgrade our ESLint dependencies.\n  lint: parallel(lint_js, lint_ts),\n};\n"
  },
  {
    "path": "gulp-tasks/publish-cdn.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst upath = require('upath');\n\nconst cdnUploadHelper = require('./utils/cdn-helper');\nconst githubHelper = require('./utils/github-helper');\nconst logHelper = require('../infra/utils/log-helper');\nconst publishHelpers = require('./utils/publish-helpers');\n\n// Git adds 'v' to tag name, lerna does not in package.json version.\n// We are going to publish to CDN *without* the 'v'\nfunction cleanTagName(name) {\n  let friendlyTagName = name;\n\n  if (friendlyTagName.startsWith('v')) {\n    friendlyTagName = friendlyTagName.substring(1);\n  }\n\n  return friendlyTagName;\n}\n\nasync function findMissingCDNTags(tagsData) {\n  const missingTags = [];\n\n  for (const tagData of tagsData) {\n    const exists = await cdnUploadHelper.tagExists(cleanTagName(tagData.name));\n\n    if (!exists) {\n      missingTags.push(tagData);\n    }\n  }\n\n  return missingTags;\n}\n\nasync function handleCDNUpload(tagName, gitBranch) {\n  const buildDir = await publishHelpers.groupBuildFiles(tagName, gitBranch);\n\n  const friendlyTagName = cleanTagName(tagName);\n\n  logHelper.log(`Uploading '${tagName}' to the CDN as ${friendlyTagName}.`);\n  const urls = await cdnUploadHelper.upload(friendlyTagName, buildDir);\n\n  logHelper.log('The following URLs are now available:');\n  for (const url of urls) {\n    // Skip the .map for cleaner logs.\n    if (upath.extname(url) !== '.map') {\n      logHelper.log(`• ${url}`);\n    }\n  }\n}\n\nasync function publish_cdn() {\n  let missingTags = [];\n\n  // Usage: npx gulp publish_cdn --cdnTag=vX.Y.Z\n  // See https://github.com/GoogleChrome/workbox/issues/2479\n  if (global.cliOptions.cdnTag) {\n    const tags = [{name: global.cliOptions.cdnTag}];\n    missingTags = await findMissingCDNTags(tags);\n  } else {\n    // Get all of the tags in the repo.\n    const tags = await githubHelper.getTags();\n    missingTags = await findMissingCDNTags(tags);\n\n    if (missingTags.length === 0) {\n      logHelper.log('No tags missing from CDN.');\n    }\n  }\n\n  for (const missingTag of missingTags) {\n    // Override the git branch here since we aren't actually\n    // using a tagged release.\n    await handleCDNUpload(missingTag.name, missingTag.name);\n  }\n}\n\nmodule.exports = {\n  publish_cdn,\n};\n"
  },
  {
    "path": "gulp-tasks/publish-github.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst semver = require('semver');\n\nconst githubHelper = require('./utils/github-helper');\nconst logHelper = require('../infra/utils/log-helper');\n\nasync function publishReleaseOnGithub(tagName, releaseInfo) {\n  if (!releaseInfo) {\n    const releaseData = await githubHelper.createRelease({\n      tag_name: tagName,\n      draft: true,\n      name: `Workbox ${tagName}`,\n      prerelease: semver.prerelease(tagName) !== null,\n    });\n    releaseInfo = releaseData.data;\n  }\n}\n\nasync function handleGithubRelease(tagName, gitBranch, releaseInfo) {\n  logHelper.log(`Creating GitHub release ${logHelper.highlight(tagName)}.`);\n\n  await publishReleaseOnGithub(tagName, releaseInfo);\n}\n\nfunction filterTagsWithReleaseBundles(allTags, taggedReleases) {\n  return allTags.filter((tagInfo) => {\n    const release = taggedReleases[tagInfo.name];\n    if (release && release.assets.length > 0) {\n      // If a tag has a release and there is an asset let's assume the\n      // the release is fine. Note: GitHub's source doesn't count as an\n      // asset\n      return false;\n    }\n\n    return true;\n  });\n}\n\nasync function publish_github() {\n  // Get all of the tags in the repo.\n  const allTags = await githubHelper.getTags();\n  const taggedReleases = await githubHelper.getTaggedReleases();\n  const tagsToBuild = filterTagsWithReleaseBundles(allTags, taggedReleases);\n\n  if (tagsToBuild.length === 0) {\n    logHelper.log(`No tags missing from GitHub.`);\n    return;\n  }\n\n  for (const tagToBuild of tagsToBuild) {\n    await handleGithubRelease(\n      tagToBuild.name,\n      tagToBuild.name,\n      taggedReleases[tagToBuild.name],\n    );\n  }\n}\n\nmodule.exports = {\n  publish_github,\n};\n"
  },
  {
    "path": "gulp-tasks/publish-glitch.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst del = require('del');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst globby = require('globby');\nconst ol = require('common-tags').oneLine;\nconst olt = require('common-tags').oneLineTrim;\nconst tempy = require('tempy');\nconst upath = require('upath');\n\nconst logHelper = require('../infra/utils/log-helper');\n\nconst DEMOS_DIR = 'demos/src';\n\nasync function publish_glitch() {\n  const glitchProjects = await globby('*', {cwd: DEMOS_DIR, onlyFiles: false});\n\n  if (!process.env.GLITCH_PERSONAL_TOKEN) {\n    throw new Error(ol`You must set a GLITCH_TOKEN in your environment to\n        publish to Glitch (you must have owner or editor access for the\n        demo associated with the token).`);\n  }\n\n  if (!process.env.GLITCH_WORKBOX_SECRET) {\n    throw new Error(ol`You must set the correct GLITCH_SECRET in your\n        environment to publish to Workbox Demos on Glitch.`);\n  }\n\n  for (const project of glitchProjects) {\n    const projectURL = olt`https://${process.env.GLITCH_PERSONAL_TOKEN}\n        @api.glitch.com/git/${project}`;\n    const projectPath = tempy.directory();\n\n    try {\n      await execa('git', ['clone', projectURL, projectPath]);\n      await fse.copy(upath.join(DEMOS_DIR, project), projectPath, {\n        overwrite: true,\n      });\n      await execa('git', ['checkout', '-b', 'glitch'], {cwd: projectPath});\n      await execa('git', ['add', '-A'], {cwd: projectPath});\n      await execa('git', ['commit', `-m'Autocommit on ${new Date()}'`], {\n        cwd: projectPath,\n      });\n      await execa(\n        'git',\n        ['push', 'origin', 'glitch', '-f', '--set-upstream', '--no-verify'],\n        {cwd: projectPath},\n      );\n\n      const deployURL = new URL(`https://${project}.glitch.me/deploy`);\n      deployURL.searchParams.set('secret', process.env.GLITCH_WORKBOX_SECRET);\n      deployURL.searchParams.set(\n        'repo',\n        `https://api.glitch.com/git/${project}`,\n      );\n      await execa('curl', ['-X', 'POST', deployURL.href]);\n    } catch (error) {\n      logHelper.error(`'${error}' occurred while processing ${project}.`);\n    }\n\n    await del(projectPath, {force: true});\n  }\n}\n\nmodule.exports = {\n  publish_glitch,\n};\n"
  },
  {
    "path": "gulp-tasks/publish-lerna.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst execa = require('execa');\nconst ol = require('common-tags').oneLine;\n\nconst logHelper = require('../infra/utils/log-helper');\n\nasync function publish_lerna() {\n  // See https://github.com/GoogleChrome/workbox/issues/2904#issuecomment-894452253\n  const options = ['publish', '--force-publish', '--exact'];\n\n  // gulp publish --distTag=latest would be the most common.\n  if (global.cliOptions.distTag) {\n    logHelper.log(\n      ol`Using ${logHelper.highlight(\n        '--dist-tag=' + global.cliOptions.distTag,\n      )}`,\n    );\n    options.push('--dist-tag', global.cliOptions.distTag);\n  } else {\n    throw new Error(ol`Please set the --distTag command line option, normally\n        to 'latest' (for a stable release) or 'next' (for a pre-release).`);\n  }\n\n  await execa('lerna', options, {\n    preferLocal: true,\n    stdio: 'inherit',\n  });\n}\n\nmodule.exports = {\n  publish_lerna,\n};\n"
  },
  {
    "path": "gulp-tasks/publish.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {series} = require('gulp');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst ol = require('common-tags').oneLine;\n\nconst {build} = require('./build');\nconst {build_packages_clean} = require('./build-packages');\nconst {publish_cdn} = require('./publish-cdn');\nconst {publish_github} = require('./publish-github');\nconst {publish_lerna} = require('./publish-lerna');\nconst {test} = require('./test');\nconst constants = require('./utils/constants');\n\nasync function publish_clean() {\n  await fse.remove(constants.GENERATED_RELEASE_FILES_DIRNAME);\n}\n\nasync function publish_sign_in_check() {\n  await execa('npm', ['whoami']);\n}\n\nasync function dist_tag_check() {\n  if (!global.cliOptions.distTag) {\n    throw new Error(ol`Please set the --distTag command line option, normally\n        to 'latest' (for a stable release) or 'next' (for a pre-release).`);\n  }\n}\n\nmodule.exports = {\n  publish: series(\n    dist_tag_check,\n    publish_sign_in_check,\n    build_packages_clean,\n    publish_clean,\n    build,\n    test,\n    publish_lerna,\n    publish_github,\n    publish_cdn,\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/test-integration.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst clearModule = require('clear-module');\nconst execa = require('execa');\nconst {globSync} = require('glob');\nconst ol = require('common-tags').oneLine;\nconst upath = require('upath');\nconst seleniumAssistant = require('selenium-assistant');\n\nconst constants = require('./utils/constants');\nconst logHelper = require('../infra/utils/log-helper');\nconst server = require('../infra/testing/server/index');\n\nfunction runFiles(filePaths) {\n  // Mocha can't be run multiple times, which we need for NODE_ENV.\n  // More info: https://github.com/mochajs/mocha/issues/995\n  clearModule.all();\n  const Mocha = require('mocha');\n\n  return new Promise((resolve, reject) => {\n    const mocha = new Mocha({\n      retries: process.env.TRAVIS || process.env.GITHUB_ACTIONS ? 4 : 1,\n      timeout: 3 * 60 * 1000,\n    });\n\n    for (const filePath of filePaths) {\n      mocha.addFile(filePath);\n    }\n\n    // Run the tests.\n    mocha.run(function (failureCount) {\n      if (failureCount > 0) {\n        return reject(new Error(`${failureCount} tests failed.`));\n      }\n      resolve();\n    });\n  });\n}\n\nasync function runTestSuite(testPath, nodeEnv, seleniumBrowser, webdriver) {\n  logHelper.log(ol`Running integration test on ${logHelper.highlight(testPath)}\n      with NODE_ENV '${nodeEnv}' and browser\n      '${logHelper.highlight(seleniumBrowser.getPrettyName())}'`);\n\n  const options = [];\n  if (global.cliOptions.grep) {\n    options.push('--grep', global.cliOptions.grep);\n  }\n  const originalNodeEnv = process.env.NODE_ENV;\n\n  process.env.NODE_ENV = nodeEnv;\n\n  try {\n    global.__workbox = {\n      seleniumBrowser,\n      server,\n      webdriver,\n    };\n\n    const testFiles = globSync(\n      upath.join(__dirname, '..', testPath, 'test-*.js'),\n    );\n\n    await runFiles(testFiles);\n  } finally {\n    process.env.NODE_ENV = originalNodeEnv;\n  }\n}\n\nasync function runIntegrationForBrowser(browser) {\n  const packagesToTest = globSync(`test/${global.packageOrStar}/integration`);\n\n  for (const buildKey of Object.keys(constants.BUILD_TYPES)) {\n    const webdriver = await browser.getSeleniumDriver();\n    const timeout = 2 * 60 * 1000;\n    webdriver.manage().setTimeouts({\n      implicit: timeout,\n      pageLoad: timeout,\n      script: timeout,\n    });\n\n    for (const packageToTest of packagesToTest) {\n      // Since workbox-google-analytics is deprecated, removing the tests from integration tests.\n      if (packageToTest.includes('workbox-google-analytics')) {\n        continue;\n      }\n      const nodeEnv = constants.BUILD_TYPES[buildKey];\n      try {\n        await runTestSuite(packageToTest, nodeEnv, browser, webdriver);\n      } catch (error) {\n        await seleniumAssistant.killWebDriver(webdriver);\n        throw error;\n      }\n    }\n\n    await seleniumAssistant.killWebDriver(webdriver);\n  }\n}\n\nasync function test_integration() {\n  if (process.platform === 'win32') {\n    logHelper.warn(`Skipping integration tests on Windows.`);\n    return;\n  }\n\n  // Install the latest Chrome and Firefox webdrivers without referencing\n  // package-lock.json, to ensure that they're up to date.\n  await execa('npm', ['install', '--no-save', 'chromedriver', 'geckodriver'], {\n    preferLocal: true,\n  });\n\n  logHelper.log(`Downloading browsers...`);\n  const expiration = 24;\n  // We are only running tests in stable, see bellow for reasons.\n  await seleniumAssistant.downloadLocalBrowser('chrome', 'stable', expiration);\n  await seleniumAssistant.downloadLocalBrowser('firefox', 'stable', expiration);\n\n  const packagesToTest = globSync(`test/${global.packageOrStar}/integration`);\n  if (packagesToTest.length === 0) {\n    return;\n  }\n\n  await server.start();\n\n  try {\n    const localBrowsers = seleniumAssistant.getLocalBrowsers();\n    for (const localBrowser of localBrowsers) {\n      switch (localBrowser.getId()) {\n        case 'firefox':\n          if (localBrowser.getReleaseName() !== 'unstable') {\n            await runIntegrationForBrowser(localBrowser);\n          }\n          break;\n        // Temporarily only test the stable release of Chrome, until the next\n        // https://www.npmjs.com/package/chromedriver release.\n        case 'chrome':\n        case 'safari':\n          if (localBrowser.getReleaseName() === 'stable') {\n            await runIntegrationForBrowser(localBrowser);\n          }\n          break;\n        default:\n          logHelper.warn(ol`Skipping integration tests for\n              ${localBrowser.getPrettyName()}.`);\n      }\n    }\n  } finally {\n    await server.stop();\n  }\n}\n\nmodule.exports = {\n  test_integration,\n};\n"
  },
  {
    "path": "gulp-tasks/test-node.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {series} = require('gulp');\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst {globSync} = require('glob');\nconst ol = require('common-tags').oneLine;\nconst upath = require('upath');\n\nconst constants = require('./utils/constants');\nconst logHelper = require('../infra/utils/log-helper');\n\nasync function runNodeTestSuite(testPath, nodeEnv) {\n  logHelper.log(ol`Running node test on ${logHelper.highlight(testPath)}\n      with NODE_ENV '${nodeEnv}'`);\n\n  const options = [];\n  if (global.cliOptions.grep) {\n    options.push('--grep', global.cliOptions.grep);\n  }\n  const originalNodeEnv = process.env.NODE_ENV;\n\n  process.env.NODE_ENV = nodeEnv;\n  try {\n    const {stdout} = await execa(\n      'nyc',\n      [\n        '--clean',\n        'false',\n        '--silent',\n        'mocha',\n        '--timeout',\n        '60000',\n        `${testPath}/**/*.{js,mjs}`,\n        ...options,\n      ],\n      {preferLocal: true},\n    );\n\n    console.log(stdout);\n  } finally {\n    process.env.NODE_ENV = originalNodeEnv;\n  }\n}\n\nasync function runNodeTestsWithEnv(testGroup, nodeEnv) {\n  const globConfig = {\n    ignore: ['**/all/**'],\n  };\n\n  if (testGroup === 'all') {\n    globConfig.ignore = [];\n  }\n\n  const packagesToTest = globSync(`test/${testGroup}/node`, globConfig);\n  for (const packageToTest of packagesToTest) {\n    // Hardcode special logic for webpack v4 and v5 tests, which need to\n    // be run in separate processes.\n    if (packageToTest.includes('workbox-webpack-plugin')) {\n      await runNodeTestSuite(`${packageToTest}/v4`, nodeEnv);\n      await runNodeTestSuite(`${packageToTest}/v5`, nodeEnv);\n    } else {\n      await runNodeTestSuite(packageToTest, nodeEnv);\n    }\n  }\n}\n\nasync function test_node_prod() {\n  await runNodeTestsWithEnv(global.packageOrStar, constants.BUILD_TYPES.prod);\n}\n\nasync function test_node_dev() {\n  await runNodeTestsWithEnv(global.packageOrStar, constants.BUILD_TYPES.dev);\n}\n\nasync function test_node_all() {\n  await runNodeTestsWithEnv('all', constants.BUILD_TYPES.prod);\n}\n\nasync function test_node_clean() {\n  await fse.remove(upath.join(__dirname, '..', '.nyc_output'));\n}\n\nasync function test_node_coverage() {\n  const runOptions = [];\n  if (global.packageOrStar !== '*') {\n    runOptions.push('--include');\n    runOptions.push(upath.join('packages', global.packageOrStar, '**', '*'));\n  }\n\n  const {stdout} = await execa(\n    'nyc',\n    ['report', '--reporter', 'lcov', '--reporter', 'text', ...runOptions],\n    {preferLocal: true},\n  );\n\n  console.log(stdout);\n}\n\nmodule.exports = {\n  test_node_all,\n  test_node_coverage,\n  test_node_dev,\n  test_node_prod,\n  test_node: series(\n    test_node_clean,\n    test_node_dev,\n    test_node_prod,\n    test_node_all,\n    test_node_coverage,\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/test-server.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {series} = require('gulp');\n\nconst {\n  transpile_typescript,\n  transpile_typescript_watch,\n} = require('./transpile-typescript');\nconst constants = require('./utils/constants');\nconst testServer = require('../infra/testing/server/index');\n\nfunction handleExit() {\n  testServer.stop();\n  process.exit(0);\n}\n\nfunction startServer() {\n  process.env.NODE_ENV = process.env.NODE_ENV || constants.BUILD_TYPES.dev;\n\n  const eventNames = [\n    'exit',\n    'SIGINT',\n    'SIGUSR1',\n    'SIGUSR2',\n    'uncaughtException',\n  ];\n  for (const eventName of eventNames) {\n    process.on(eventName, handleExit);\n  }\n\n  return testServer.start();\n}\n\nmodule.exports = {\n  test_server: series(\n    transpile_typescript,\n    startServer,\n    transpile_typescript_watch,\n  ),\n};\n"
  },
  {
    "path": "gulp-tasks/test.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {parallel, series} = require('gulp');\n\nconst {lint} = require('./lint');\nconst {test_integration} = require('./test-integration');\nconst {test_node} = require('./test-node');\nconst logHelper = require('../infra/utils/log-helper');\n\nasync function logSkip() {\n  logHelper.log('Skipping test suite due to --skipTests');\n}\n\nfunction runTestsUnlessSkipped() {\n  if (global.cliOptions.skipTests) {\n    return logSkip;\n  } else {\n    // The node and integration tests both muck with process.env.NODE_ENV,\n    // and therefore can't be run in parallel.\n    return parallel(lint, series(test_node, test_integration));\n  }\n}\n\nmodule.exports = {\n  test: runTestsUnlessSkipped(),\n};\n"
  },
  {
    "path": "gulp-tasks/transpile-typescript.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst globby = require('globby');\nconst upath = require('upath');\n\nconst {AsyncDebounce} = require('../infra/utils/AsyncDebounce');\n\n/**\n * A mapping between each package name and its corresponding `AsyncDebounce`\n * instance of the `transpilePackage()` function.\n *\n * @type {{[key: string]: AsyncDebounce}}\n */\nconst debouncedTranspilerMap = {};\n\n/**\n * Takes a package name and schedules that package's source TypeScript code\n * to be converted to JavaScript. If a transpilation is already scheduled, it\n * won't be queued twice, so this function is safe to call as frequently as\n * needed.\n *\n * @param {string} packageName\n * @param {Object} [options]\n */\nasync function queueTranspile(packageName, options) {\n  if (!debouncedTranspilerMap[packageName]) {\n    debouncedTranspilerMap[packageName] = new AsyncDebounce(async () => {\n      await transpile_typescript();\n    });\n  }\n  await debouncedTranspilerMap[packageName].call();\n  debouncedTranspilerMap[packageName] = null;\n}\n\n/**\n * A mapping between package names and whether or not they have pending\n * file changes\n *\n * @type {{[key: string]: boolean}}\n */\nconst pendingChangesMap = {};\n\n/**\n * Returns true if a TypeScript file has been modified in the package since\n * the last time it was transpiled.\n *\n * @param {string} packageName\n */\nfunction needsTranspile(packageName) {\n  return pendingChangesMap[packageName] === true;\n}\n\n/**\n * Transpiles all packages listed in the root tsconfig.json's references section\n * into .js and .d.ts files. Creates stub .mjs files that re-export the contents\n * of the .js files.\n *\n * Unlike other scripts, this does not take the --package= command line param\n * into account. Each project in packages/ theoretically could depend on any\n * other project, so kicking off a single, top-level compilation makes the\n * most sense (and is faster when all the packages need to be compiled).\n */\nasync function transpile_typescript() {\n  await execa('tsc', ['--build', 'tsconfig.json'], {preferLocal: true});\n\n  const jsFiles = await globby(`packages/*/**/*.js`, {\n    ignore: ['**/build/**', '**/src/**'],\n  });\n\n  for (const jsFile of jsFiles) {\n    const {dir, name} = upath.parse(jsFile);\n    const mjsFile = upath.join(dir, `${name}.mjs`);\n    const mjsSource = `export * from './${name}.js';`;\n    await fse.outputFile(mjsFile, mjsSource);\n  }\n}\n\nasync function transpile_typescript_watch() {\n  await execa('tsc', ['--build', '--watch', 'tsconfig.json'], {\n    preferLocal: true,\n  });\n}\n\nmodule.exports = {\n  // These are used as functions from other modules, not as gulp tasks.\n  functions: {\n    needsTranspile,\n    queueTranspile,\n  },\n  transpile_typescript_watch,\n  transpile_typescript,\n};\n"
  },
  {
    "path": "gulp-tasks/utils/analyse-properties.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/*\n * This file should be run as a node script to analyse the content of the\n * browser bundles.\n *\n * It's an extremely naive approach to picking property names out and seeing\n * how often they are used in the final browser bundles. This can be\n * used to detect variable names that are long and used repeatedly meaning\n * that a small refactor allowing `uglify-es` to mangle these properties,\n * could lead to reasonable improvement in final bundle size.\n *\n * This is very rough and vague. It needs to be run manually and if it's not\n * used can and should be removed from Workbox repo.\n */\nconst {globSync} = require('glob');\nconst path = require('path');\nconst fs = require('fs-extra');\nconst babylon = require('babylon');\n\nconst constants = require('./constants');\nconst logHelper = require('../../infra/utils/log-helper');\n\nclass AnalyseBuildForProperties {\n  run() {\n    const filePaths = this.getBuildFiles();\n    return Promise.all(\n      filePaths.map((filePath) => {\n        const rawAnalysis = this.analyzeFile(filePath);\n        const analysis = this.tidyData(rawAnalysis);\n\n        return {\n          filePath,\n          analysis,\n        };\n      }),\n    );\n  }\n\n  getBuildFiles() {\n    // workbox-sw doesn't include .prod. in the build name.\n    const buildGlob = path.join(\n      __dirname,\n      '..',\n      '..',\n      'packages',\n      '*',\n      constants.PACKAGE_BUILD_DIRNAME,\n      '{*.prod.js,workbox-sw.js}',\n    );\n    return globSync(buildGlob);\n  }\n\n  analyzeFile(filePath) {\n    const fileContents = fs.readFileSync(filePath).toString();\n\n    const parsedCode = babylon.parse(fileContents);\n    const tokens = parsedCode.tokens;\n    const nameTokens = tokens.filter((token) => {\n      return token.type.label === 'name';\n    });\n\n    const matches = {};\n\n    nameTokens.forEach((token) => {\n      const variableName = token.value;\n      if (!matches[variableName]) {\n        matches[variableName] = 0;\n      }\n      matches[variableName]++;\n    });\n\n    return Object.keys(matches).map((matchKey) => {\n      return {\n        propertyName: matchKey,\n        propertyCount: matches[matchKey],\n      };\n    });\n  }\n\n  tidyData(analysisEntries) {\n    return analysisEntries\n      .filter((entry) => {\n        // If there is only one entry or it's a single character it's\n        // either not important or it's already been minified.\n        return entry.propertyCount > 1 && entry.propertyName.length > 1;\n      })\n      .filter((entry) => {\n        switch (entry.propertyName) {\n          case 'await':\n          case 'async':\n            return false;\n          default:\n            return true;\n        }\n      })\n      .sort((a, b) => {\n        return b.propertyCount - a.propertyCount;\n      });\n  }\n\n  printDetails({filePath, analysis}) {\n    let longestPropertyName = 0;\n    analysis.forEach((entry) => {\n      if (entry.propertyName.length > longestPropertyName) {\n        longestPropertyName = entry.propertyName.length;\n      }\n    });\n\n    logHelper.log();\n    logHelper.log(`Results for '${path.relative(process.cwd(), filePath)}'`);\n    logHelper.log();\n\n    analysis.forEach((entry) => {\n      const numberOfSpaces = longestPropertyName - entry.propertyName.length;\n      const extraSpace = ' '.repeat(numberOfSpaces);\n      logHelper.log(\n        `    ${entry.propertyName} ` + `${extraSpace} ${entry.propertyCount}`,\n      );\n    });\n    logHelper.log();\n  }\n}\n\nmodule.exports = AnalyseBuildForProperties;\n"
  },
  {
    "path": "gulp-tasks/utils/cdn-helper.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {globSync} = require('glob');\nconst path = require('path');\nconst {Storage} = require('@google-cloud/storage');\n\nconst cdnDetails = require('../../cdn-details.json');\nconst logHelper = require('../../infra/utils/log-helper');\n\nconst PROJECT_ID = 'workbox-bab1f';\n\nclass CDNHelper {\n  constructor() {\n    this._gcs = null;\n  }\n\n  _getReleaseTagPath(tagName) {\n    return `${cdnDetails.releasesDir}/${tagName}`;\n  }\n\n  async getGCS() {\n    if (!this._gcs) {\n      // Run `gcloud auth application-default login` if needed.\n      // See https://stackoverflow.com/a/42059661/385997\n      this._gcs = new Storage({\n        projectId: PROJECT_ID,\n      });\n    }\n\n    return this._gcs;\n  }\n\n  async tagExists(tagName) {\n    const gcs = await this.getGCS();\n    try {\n      const bucket = gcs.bucket(cdnDetails.bucketName);\n      // bucket.file('some/path/').exists() doesn't seem to work\n      // for nested directories. Instead we are checking if there are\n      // files in the expected release directory.\n      const [files] = await bucket.getFiles({\n        prefix: `${this._getReleaseTagPath(tagName)}/`,\n      });\n      return files.length > 0;\n    } catch (err) {\n      logHelper.error(err);\n      throw err;\n    }\n  }\n\n  async upload(tagName, directoryToUpload) {\n    const gcs = await this.getGCS();\n\n    const filePaths = globSync(`${directoryToUpload}/*`, {\n      absolute: true,\n    });\n\n    const publicUrls = [];\n    const bucket = gcs.bucket(cdnDetails.bucketName);\n\n    for (const filePath of filePaths) {\n      const destination = `${this._getReleaseTagPath(tagName)}/${path.basename(\n        filePath,\n      )}`;\n\n      try {\n        await bucket.upload(filePath, {\n          destination,\n          gzip: true,\n          public: true,\n          resumable: false,\n          metadata: {\n            cacheControl: 'public, max-age=31536000',\n          },\n        });\n      } catch (err) {\n        logHelper.error(`Failed to upload file to GCS bucket: '${filePath}'`);\n        throw err;\n      }\n      publicUrls.push(\n        `${cdnDetails.origin}/${cdnDetails.bucketName}/${destination}`,\n      );\n    }\n\n    return publicUrls;\n  }\n}\n\nmodule.exports = new CDNHelper();\n"
  },
  {
    "path": "gulp-tasks/utils/constants.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nmodule.exports = {\n  // This is a directory that should not be commited\n  // to git and will be removed and rebuilt between\n  // test runs.\n  PACKAGE_BUILD_DIRNAME: 'build',\n  GENERATED_RELEASE_FILES_DIRNAME: 'generated-release-files',\n\n  // This is used in the publish-bundle step to avoid doing anything\n  // with tags that have known issues.\n  MIN_RELEASE_TAG_TO_PUBLISH: 'v6.1.5',\n  GITHUB_OWNER: 'GoogleChrome',\n  GITHUB_REPO: 'workbox',\n\n  // This is the environments that we should use for NODE_ENV.\n  BUILD_TYPES: {\n    dev: 'dev',\n    prod: 'production',\n  },\n};\n"
  },
  {
    "path": "gulp-tasks/utils/get-packages.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {globSync} = require('glob');\nconst path = require('path');\n\nconst DEFAULT_ROOT = path.join(__dirname, '..', '..');\n\nconst getPackages = ({type, root = DEFAULT_ROOT} = {}) => {\n  const pathToPkgJsons = globSync('packages/*/package.json', {cwd: root});\n\n  return pathToPkgJsons\n    .map((pathToPkgJson) => {\n      const pkg = require(`${path.resolve(root)}/${pathToPkgJson}`);\n      return pkg;\n    })\n    .filter((pkg) => {\n      return pkg.workbox && pkg.workbox.packageType === type;\n    });\n};\n\nmodule.exports = {\n  getPackages,\n};\n"
  },
  {
    "path": "gulp-tasks/utils/github-helper.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {Octokit} = require('@octokit/rest');\nconst semver = require('semver');\n\nconst constants = require('./constants');\n\n// github.authenticate() is synchronous, and it only stores the credentials for\n// the next request, so it should be called once per method that requires auth.\n// See https://github.com/mikedeboer/node-github#authentication\nconst authenticate = () => {\n  if (!process.env.GITHUB_TOKEN) {\n    throw new Error(\n      'You must set a GITHUB_TOKEN in your environment to ' +\n        'publish a GitHub release.',\n    );\n  }\n\n  return new Octokit({auth: process.env.GITHUB_TOKEN});\n};\n\nmodule.exports = {\n  createRelease: (args) => {\n    const github = authenticate();\n\n    args.owner = constants.GITHUB_OWNER;\n    args.repo = constants.GITHUB_REPO;\n    return github.rest.repos.createRelease(args);\n  },\n\n  uploadAsset: (args) => {\n    const github = authenticate();\n    args.owner = constants.GITHUB_OWNER;\n    args.repo = constants.GITHUB_REPO;\n    return github.rest.repos.uploadReleaseAsset(args);\n  },\n\n  getTaggedReleases: async () => {\n    const github = authenticate();\n    const releasesData = await github.rest.repos.listReleases({\n      owner: constants.GITHUB_OWNER,\n      repo: constants.GITHUB_REPO,\n    });\n\n    const releases = releasesData.data;\n    const releasesByTags = {};\n    releases.forEach((release) => {\n      const tagName = release.tag_name;\n      if (semver.gte(tagName, constants.MIN_RELEASE_TAG_TO_PUBLISH)) {\n        releasesByTags[tagName] = release;\n      }\n    });\n\n    return releasesByTags;\n  },\n\n  getTags: async () => {\n    const github = authenticate();\n    const tagsResponse = await github.repos.listTags({\n      owner: constants.GITHUB_OWNER,\n      repo: constants.GITHUB_REPO,\n    });\n\n    const tagsData = tagsResponse.data;\n    return tagsData.filter((tagData) => {\n      return semver.gte(tagData.name, constants.MIN_RELEASE_TAG_TO_PUBLISH);\n    });\n  },\n};\n"
  },
  {
    "path": "gulp-tasks/utils/node-projects-babel.config.json",
    "content": "{\n  \"presets\": [[\"@babel/preset-env\", {\"targets\": {\"node\": \"10.0\"}}]]\n}\n"
  },
  {
    "path": "gulp-tasks/utils/output-filename-to-package-map.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {getPackages} = require('./get-packages');\n\nconst outputFilenameToPkgMap = {};\n\nconst windowAndSWPackages = [\n  ...getPackages({type: 'sw'}),\n  ...getPackages({type: 'window'}),\n];\n\nwindowAndSWPackages.forEach((pkg) => {\n  // When no `outputFilename` property exists, the package name is used.\n  const outputFilename = pkg.workbox.outputFilename || pkg.name;\n\n  outputFilenameToPkgMap[outputFilename] = pkg;\n});\n\nmodule.exports = {\n  outputFilenameToPkgMap,\n};\n"
  },
  {
    "path": "gulp-tasks/utils/package-runner.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst path = require('path');\nconst {globSync} = require('glob');\n\nconst oneLine = require('common-tags').oneLine;\n\nconst pkgPathToName = require('./pkg-path-to-name');\n\n/**\n * @param {string} typeFilter The type of packages to return: 'node', 'sw',\n * or 'all'.\n * @return Array<string> Paths to package.json files for the matching packages.\n */\nfunction getPackages(typeFilter) {\n  return globSync(`packages/${global.packageOrStar}/package.json`, {\n    absolute: true,\n  }).filter((pathToPackageJson) => {\n    const pkgInfo = require(pathToPackageJson);\n    const packageType = pkgInfo.workbox.packageType;\n    if (!packageType) {\n      throw Error(oneLine`Unable to determine package type. Please add\n      workbox.packageType metadata to ${pathToPackageJson}`);\n    }\n\n    return typeFilter === 'all' || typeFilter === packageType;\n  });\n}\n\n/*\n * This methods only purpose is to call a function with\n * the package path that needs to be acted upon.\n *\n * The specific function might vary depending on whether a give project targets\n * Node or the browser, so you can specify 'sw', 'node', or 'all'.\n *\n * If we ran gulp as `gulp build` we would want the\n * 'build' task to run against all packages.\n *\n * If we ran gulp as `gulp build --project workbox-core`\n * we would want the 'build' task to run against `workbox-core`\n * **only**.\n *\n * This method simplifies how we would write the 'build'\n * task and should be the only way we write functions.\n *\n * ```javascript\n * gulp.task('build',\n *   gulp.series(\n *     packageRunner(displayName, 'all', func, arg1, arg2)\n *   )\n * );\n * ```\n *\n * Package runner will return an array of functions that\n * will call `func` where the first argument\n * is the absolute path to a package,\n * like `/user/matt/workbox/packages/workbox-core` and\n * the remaining arguments will be whatever is passed to\n * packageRunner, in the above sample this would be\n * `arg1` and `arg2`.\n *\n * In the example above, `func` would be written as:\n *\n * ```javascript\n * function functionForEachProjectType(packagePath, arg1, arg2) {\n *   // Return a Promise\n *   return Promise.resolve().then(() => ...)\n *\n *   // OR\n *\n *   // Return a gulp stream\n *   return gulp.src(path.posix.join(packagePath, 'example-dir', '*.js'))\n *   .pipe(...)\n *   .pipe(gulp.dest(...));\n * }\n * ```\n *\n * You can use this with both `gulp.series` and\n * `gulp.parallel`.\n *\n * ```javascript\n * gulp.series(packageRunner(displayName, 'all', func))\n * gulp.parallel(packageRunner(displayName, 'all', func))\n * ```\n *\n * If you need to call the runner with multiple functions\n * you can do this and gulp will merge the arrays returned\n * by the runner.\n *\n * For example:\n *\n * ```javascript\n * gulp.parallel(\n *   packageRunner(displayName, 'sw', func, true),\n *   packageRunner(displayName, 'sw', func, false),\n * )\n * ```\n *\n * If we run the above, it would call the appropriate\n * `func` for all packages in workbox twice, once with\n * argument `true` and once with `false`. `gulp.parallel` will run all of these\n * calls to `func` in parallel.\n *\n * @param {string} displayName A friendly name to log.\n * @param Array<string> The type of package we want to run func against:\n * 'node', 'sw', or 'all'.\n * @param {function} func The function that we want to run for each package.\n * @param {*} args Any arguments that should be passed in to the func.\n * @return Array<function> All of the function wrappers.\n */\nmodule.exports = (displayName, packageType, func, ...args) => {\n  const packagePaths = getPackages(packageType);\n  // We need to return a single no-op rather than an empty array, or else gulp\n  // will throw 'One or more tasks should be combined using series or parallel'.\n  if (packagePaths.length === 0) {\n    const noOp = (done) => done();\n    noOp.displayName = displayName;\n    return [noOp];\n  }\n  return packagePaths.map((packagePath) => {\n    const packageRootPath = path.dirname(packagePath);\n\n    const wrappedFunc = () => func(packageRootPath, ...args);\n    wrappedFunc.displayName = oneLine`\n      ${displayName}\n      (${pkgPathToName(packageRootPath)})\n      ${args.length === 0 ? '' : JSON.stringify(args)}`;\n    return wrappedFunc;\n  });\n};\n"
  },
  {
    "path": "gulp-tasks/utils/pkg-path-to-name.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst path = require('path');\n\nconst packagesPath = path.join(__dirname, '..', '..', 'packages');\n\n// A helper method that should be used when you want to log\n// the package name ONLY.\nmodule.exports = (pkgPath) => {\n  return path.relative(packagesPath, pkgPath);\n};\n"
  },
  {
    "path": "gulp-tasks/utils/publish-helpers.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst execa = require('execa');\nconst fse = require('fs-extra');\nconst {globSync} = require('glob');\nconst ol = require('common-tags').oneLine;\nconst upath = require('upath');\n\nconst {outputFilenameToPkgMap} = require('./output-filename-to-package-map');\nconst constants = require('./constants');\nconst logHelper = require('../../infra/utils/log-helper');\n\nconst SOURCE_CODE_DIR = 'source-code';\nconst GROUPED_BUILD_FILES = 'grouped-build-files';\n\nconst doesDirectoryExist = async (directoryPath) => {\n  let stats = null;\n  try {\n    stats = await fse.stat(directoryPath);\n  } catch (err) {\n    return false;\n  }\n  return stats.isDirectory();\n};\n\nconst getBuildPath = (tagName) => {\n  const tempReleasePath = upath.join(\n    __dirname,\n    '..',\n    '..',\n    constants.GENERATED_RELEASE_FILES_DIRNAME,\n  );\n  return upath.join(tempReleasePath, tagName);\n};\n\nconst downloadGitCommit = async (tagName, gitBranch) => {\n  if (!tagName) {\n    throw new Error(`You must provide a tagName to 'downloadGitCommit()`);\n  }\n\n  if (!gitBranch) {\n    throw new Error(`You must provide a gitBranch to 'downloadGitCommit()`);\n  }\n\n  const sourceCodePath = upath.join(getBuildPath(tagName), SOURCE_CODE_DIR);\n\n  logHelper.log(`Download Git Commit ${logHelper.highlight(gitBranch)}.`);\n\n  const dirExists = await doesDirectoryExist(sourceCodePath);\n  if (!dirExists) {\n    logHelper.log(`    Clone Git repo for branch: '${gitBranch}'.`);\n\n    await execa('git', [\n      'clone',\n      '--branch',\n      gitBranch,\n      '--depth',\n      '1',\n      `http://github.com/${constants.GITHUB_OWNER}/${constants.GITHUB_REPO}.git`,\n      sourceCodePath,\n    ]);\n  } else {\n    logHelper.log(`   Git repo for branch '${gitBranch}' is cloned already.`);\n  }\n\n  return sourceCodePath;\n};\n\nconst buildGitCommit = async (tagName) => {\n  const sourceCodePath = upath.join(getBuildPath(tagName), SOURCE_CODE_DIR);\n\n  logHelper.log(ol`\n    Building Commit\n    ${logHelper.highlight(upath.relative(process.cwd(), sourceCodePath))}.\n  `);\n\n  await execa('npm', ['install'], {cwd: sourceCodePath});\n\n  await execa('gulp', ['build'], {cwd: sourceCodePath});\n\n  // This is to try and fix GitHub and CDN steps from having file read / close\n  // issues by removing any risk of spawn tasks being out of sync\n  await new Promise((resolve) => setTimeout(resolve, 1000));\n};\n\n/*\n * This function will create a directory with the same name as the\n * .tar.gz file it generates. This way when the file is extracted\n * the folder structure will be the same.\n */\nconst groupBuildFiles = async (tagName, gitBranch) => {\n  const groupedBuildFiles = upath.join(\n    getBuildPath(tagName),\n    GROUPED_BUILD_FILES,\n  );\n  const dirExists = await doesDirectoryExist(groupedBuildFiles);\n\n  if (!dirExists) {\n    await downloadGitCommit(tagName, gitBranch);\n    await buildGitCommit(tagName);\n\n    const sourceCodePath = upath.join(getBuildPath(tagName), SOURCE_CODE_DIR);\n\n    const browserPackages = Object.values(outputFilenameToPkgMap).map(\n      (item) => item.name,\n    );\n\n    const pattern = upath.join(\n      sourceCodePath,\n      'packages',\n      `{${browserPackages.join(',')}}`,\n      constants.PACKAGE_BUILD_DIRNAME,\n      '*.{js,mjs,map}',\n    );\n\n    logHelper.log(ol`\n      Grouping Build Files into\n      ${logHelper.highlight(upath.relative(process.cwd(), groupedBuildFiles))}.\n    `);\n\n    // Copy files from the source code and move into the grouped build\n    // directory. In others, have a flat file structure of just the built files.\n    const filesToInclude = globSync(pattern);\n    for (const fileToInclude of filesToInclude) {\n      await fse.copy(\n        fileToInclude,\n        upath.join(groupedBuildFiles, upath.basename(fileToInclude)),\n      );\n    }\n  } else {\n    logHelper.log(`   Build files already grouped.`);\n  }\n\n  return groupedBuildFiles;\n};\n\nmodule.exports = {\n  groupBuildFiles,\n};\n"
  },
  {
    "path": "gulp-tasks/utils/rollup-helper.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {babel} = require('@rollup/plugin-babel');\nconst {nodeResolve} = require('@rollup/plugin-node-resolve');\nconst asyncToPromises = require('babel-plugin-transform-async-to-promises');\nconst replace = require('@rollup/plugin-replace');\nconst terserPlugin = require('@rollup/plugin-terser');\n\nconst constants = require('./constants');\nconst getVersionsCDNUrl = require('./versioned-cdn-url');\n\n// See https://github.com/GoogleChrome/workbox/issues/1674\nconst nameCache = {};\n\nmodule.exports = {\n  // Every use of rollup should have minification and the replace\n  // plugin set up and used to ensure as consist set of tests\n  // as possible.\n  getDefaultPlugins: (buildType, buildFormat = 'iife', es5 = false) => {\n    const plugins = [nodeResolve()];\n\n    const babelConfig = {\n      babelHelpers: 'bundled',\n      presets: [\n        [\n          '@babel/preset-env',\n          {\n            loose: true,\n            targets: {\n              browsers: es5\n                ? // If es5 is true, target IE11\n                  // https://github.com/browserslist/browserslist#queries\n                  ['ie 11']\n                : // This corresponds to the version of Chromium used by\n                  // Samsung Internet 6.x, which is the minimum non-evergreen\n                  // browser we currently support.\n                  ['chrome >= 56'],\n            },\n          },\n        ],\n      ],\n    };\n    if (es5) {\n      babelConfig.plugins = [asyncToPromises];\n    }\n    plugins.push(babel(babelConfig));\n\n    const minifyBuild = buildType === constants.BUILD_TYPES.prod;\n    if (minifyBuild) {\n      const terserOptions = {\n        nameCache,\n        module: buildFormat === 'esm' ? true : false,\n        mangle: {\n          properties: {\n            reserved: [\n              // Chai will break unless we reserve this private variable.\n              '_obj',\n              // We need this to be exported correctly.\n              '_private',\n              // See https://github.com/GoogleChrome/workbox/issues/2686\n              '_handle',\n            ],\n            // mangle > properties > regex will allow terser to minify\n            // private variable and names that start with a single underscore\n            // followed by a letter. This restriction to avoid mangling\n            // unintentional fields in our or other libraries code.\n            regex: /^_[A-Za-z]/,\n            // If you are getting an error due to a property mangle\n            // set this flag to true and the property will be changed\n            // from '_foo' to '$_foo$' to help diagnose the problem.\n            debug: false,\n          },\n        },\n      };\n      plugins.push(terserPlugin(terserOptions));\n    }\n\n    // This is what the build should be\n    const replaceOptions = {\n      preventAssignment: true,\n      WORKBOX_CDN_ROOT_URL: getVersionsCDNUrl(),\n    };\n\n    if (buildType) {\n      replaceOptions['process.env.NODE_ENV'] = JSON.stringify(buildType);\n    }\n\n    // Replace allows us to input NODE_ENV and strip code accordingly\n    plugins.push(replace(replaceOptions));\n\n    return plugins;\n  },\n};\n"
  },
  {
    "path": "gulp-tasks/utils/version-module.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst fs = require('fs-extra');\nconst path = require('path');\n\nconst pkgPathToName = require('./pkg-path-to-name');\n\nconst getDetails = (packagePath) => {\n  // Read file from filesystem to avoid require caching\n  const packageJsonPath = path.join(packagePath, 'package.json');\n  const pkgJson = fs.readJSONSync(packageJsonPath);\n  const name = pkgPathToName(packagePath).replace('workbox-', '');\n  return ['workbox', name, pkgJson.version].join(':');\n};\n\nmodule.exports = async (packagePath) => {\n  const versionString = `try{self['${getDetails(\n    packagePath,\n  )}']&&_()}catch(e){}`;\n\n  if (await fs.pathExists(path.join(packagePath, 'src', 'index.ts'))) {\n    const tsVersionString = `// @ts-ignore\\n${versionString}`;\n    await fs.writeFile(\n      path.join(packagePath, 'src', '_version.ts'),\n      tsVersionString,\n    );\n  }\n\n  const mjsVersionString = `${versionString}// eslint-disable-line`;\n  return fs.writeFile(path.join(packagePath, '_version.mjs'), mjsVersionString);\n};\n"
  },
  {
    "path": "gulp-tasks/utils/versioned-cdn-url.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst cdn = require('../../packages/workbox-build/src/cdn-details.json');\nconst lernaPkg = require('../../lerna.json');\n\nmodule.exports = () =>\n  `${cdn.origin}/${cdn.bucketName}/${cdn.releasesDir}` + `/${lernaPkg.version}`;\n"
  },
  {
    "path": "gulpfile.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst fse = require('fs-extra');\nconst globby = require('globby');\nconst minimist = require('minimist');\nconst upath = require('upath');\n\nconst options = minimist(process.argv.slice(2));\n\nif (options.package) {\n  // Ensure the package is valid before running tasks\n  try {\n    fse.statSync(upath.join(__dirname, 'packages', options.package));\n  } catch (err) {\n    throw new Error(`The supplied package '${options.package}' is invalid.`);\n  }\n}\n\nglobal.port = options.port || 3000;\nglobal.packageOrStar = options.package || '*';\nglobal.cliOptions = options;\n\nconst taskFiles = globby.sync('./gulp-tasks/*.js');\n\nfor (const taskFile of taskFiles) {\n  const taskDefinitions = require(taskFile);\n  for (const [name, task] of Object.entries(taskDefinitions)) {\n    if (name === 'functions') {\n      continue;\n    }\n    if (name in module.exports) {\n      throw new Error(\n        `Duplicate task definition: ${name} defined in` +\n          ` ${taskFile} conflicts with another task.`,\n      );\n    }\n    module.exports[name] = task;\n  }\n}\n"
  },
  {
    "path": "infra/pr-bot/aggregate-size-plugin.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {oneLine} = require('common-tags');\nconst {PluginInterface} = require('pr-bot');\nconst bytes = require('bytes');\nconst fs = require('fs-extra');\nconst gzipSize = require('gzip-size');\nconst path = require('path');\n\n// 15kb max size, gzip'ed.\nconst MAX_SIZE_GZIP = 15 * 1024;\n\nclass AggregateSizePlugin extends PluginInterface {\n  constructor() {\n    super(`Workbox Aggregate Size Plugin`);\n  }\n\n  async run({afterPath} = {}) {\n    const packagesToAggregate = [\n      `workbox-cacheable-response`,\n      `workbox-core`,\n      `workbox-expiration`,\n      `workbox-precaching`,\n      `workbox-routing`,\n      `workbox-strategies`,\n      `workbox-sw`,\n    ];\n\n    const absoluteAfterPath = path.resolve(afterPath);\n    const files = packagesToAggregate.map((pkgName) => {\n      const prefix = `${absoluteAfterPath}/packages/${pkgName}/`;\n      const pkgJson = require(`${prefix}package.json`);\n      const posixPath = prefix + pkgJson.main;\n      return posixPath.split('/').join(path.sep);\n    });\n\n    let totalSize = 0;\n    let totalGzipSize = 0;\n\n    for (const filePath of files) {\n      const fileContents = await fs.readFile(filePath);\n      const stat = await fs.stat(filePath);\n      totalSize += stat.size;\n      totalGzipSize += await gzipSize(fileContents);\n    }\n\n    const percentValue = (totalGzipSize / MAX_SIZE_GZIP) * 100;\n    const percentString = parseFloat(percentValue).toFixed(0);\n\n    const totalSizeString = bytes(totalSize);\n    const totalGzipString = bytes(totalGzipSize);\n\n    let markdownWarning = ``;\n    if (percentValue >= 90) {\n      const markdownMoji = percentValue >= 95 ? '☠️' : '⚠️';\n      markdownWarning = oneLine`\n      <h3 align=\"center\">${markdownMoji} WARNING ${markdownMoji}</h3>\n      <p align=\"center\">\n        We are using <strong>${percentString}%</strong>\n        of our max budget for gzip'ed bundle size.\n      </p>`;\n    }\n\n    const failPR = Boolean(percentValue > 100);\n\n    const prettyLog =\n      `**${totalGzipString}** gzip'ed ` +\n      `(**${percentString}%** of limit)\\n` +\n      `**${totalSizeString}** uncompressed`;\n\n    const markdownLog = `${markdownWarning}\\n\\n${prettyLog}`;\n\n    return {\n      failPR, // Fail the PR if we have exceeded the limit.\n      markdownLog,\n      prettyLog,\n    };\n  }\n}\n\nmodule.exports = AggregateSizePlugin;\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/lang/en.yaml",
    "content": "# @license\n# Copyright 2018 Google LLC\n#\n# Use of this source code is governed by an MIT-style\n# license that can be found in the LICENSE file or at\n# https://opensource.org/licenses/MIT.\n\nconstructor:\n  prefix: 'new '\nreturnTypesSeparator: '&nbsp;returns&nbsp;'\ntables:\n  body:\n    defaultValue: 'Defaults to {valueString}.'\n    defaultValueString: '<code>{value}</code>'\n    eachValueHasProperties: 'Values in <code>{name}</code> have the following properties:'\n    isOptional: 'Optional'\n    isRequired: '&nbsp;'\n    nonNullable:\n      long: 'Value must not be null.'\n      short: 'non-null'\n    nullable:\n      long: 'Value may be null.'\n      short: 'nullable'\n    repeatable:\n      long: 'Value may be repeated.'\n      short: 'repeatable'\n    unknownType: '?'\n  emptyCellPlaceholder: '&nbsp;'\n  header:\n    description: 'Description'\n    name: 'Name'\n    optional: 'Optional'\n    type: 'Type'\n  notApplicable: '&#8212;'\nparams:\n  all: '({params})'\n  joiner: |\n    {items, plural,\n        =0 {{param}}\n        other {, {param}}}\n  optional: '{param}'\n  repeatable: '...{param}'\nheadings:\n  enums: |\n    {items, plural,\n        =1 {Enumeration}\n        other {Enumerations}}\n  values: |\n    {items, plural,\n        =1 {Value}\n        other {Values}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/lib/publishjob.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/* eslint-env node */\n/* eslint-disable valid-jsdoc, require-jsdoc */\n\nconst name = require('jsdoc/name');\nconst BaselinePublishJob = require('jsdoc-baseline/lib/publishjob');\n\nclass PublishJob extends BaselinePublishJob {\n  constructor(...args) {\n    super(...args);\n    this._symbolNames = {};\n    this._data;\n  }\n\n  generateTocYaml(symbols, basepath) {\n    symbols.sort(function (a, b) {\n      let aName = a.kind === 'namespace' ? a.longname : a.name;\n      let bName = b.kind === 'namespace' ? b.longname : b.name;\n\n      aName = aName.toLowerCase();\n      bName = bName.toLowerCase();\n\n      if (aName > bName) {\n        return 1;\n      }\n\n      if (aName < bName) {\n        return -1;\n      }\n\n      return 0;\n    });\n\n    // check for duplicated names\n    symbols.forEach((symbol) => {\n      const symbolName =\n        symbol.kind === 'namespace' ? symbol.longname : symbol.name;\n      if ({}.hasOwnProperty.call(this._symbolNames, symbolName)) {\n        this._symbolNames[symbolName] = true;\n      } else {\n        this._symbolNames[symbolName] = false;\n      }\n    });\n\n    // To disambiguate duplicated names, prepend the name and scope of the\n    // parent. For example, if you have foo.bar.Qux and foo.baz.Qux, the\n    // disambiguated names are bar.Quxand baz.Qux.\n    symbols.forEach((symbol) => {\n      let mappedName = null;\n      const symbolName =\n        symbol.kind === 'namespace' ? symbol.longname : symbol.name;\n\n      if (this._symbolNames[symbolName] === true) {\n        if (symbol.kind !== 'namespace') {\n          // get info for the parent's name\n          const atomized = name.shorten(symbol.longname);\n          const atomizedParent = name.shorten(atomized.memberof);\n          // prepend the parent's name and the symbol's scope to the name\n          mappedName = atomizedParent.name + atomized.scope + symbol.name;\n        } else {\n          mappedName = symbol.longname;\n        }\n      }\n\n      symbol.tocYamlName =\n        mappedName ||\n        (symbol.kind === 'namespace' ? symbol.longname : symbol.name);\n    });\n\n    const data = {\n      symbols,\n      basepath,\n    };\n\n    this.generate('toc-yaml', data, '_toc.yaml');\n  }\n\n  generateIndex(readme) {\n    const data = {\n      allLongnamesTree: this.allLongnamesTree,\n      package: this.package,\n      pageTitle: global.env.opts.query ? global.env.opts.query.productName : '',\n      readme: readme || null,\n    };\n\n    this.generate('index', data, this.indexUrl);\n\n    return this;\n  }\n\n  generateIndexAll(longnames) {\n    longnames.sort();\n    const data = {\n      longnames: longnames,\n      pageTitle: global.env.opts.query ? global.env.opts.query.productName : '',\n    };\n\n    this.generate('index-all', data, 'index-all.html');\n  }\n}\n\n/** const PublishJob = module.exports = function(...args) {\n  Parent.apply(this, [].slice.call(args, 0));\n};\nutil.inherits(PublishJob, Parent);**/\n\nmodule.exports = PublishJob;\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/publish.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/* eslint-env node */\n\nconst baselineConfig = require('jsdoc-baseline/lib/config');\nconst fileFinder = require('jsdoc-baseline/lib/filefinder');\nconst helper = require('jsdoc/util/templateHelper');\nconst path = require('jsdoc/path');\n\nexports.publish = function (data, opts, tutorials) {\n  baselineConfig.loadSync();\n  baselineConfig\n    .set('l10n', path.join(__dirname, 'lang'))\n    .set('static', path.join(__dirname, 'static'))\n    .set('views.partials', path.join(__dirname, 'views'))\n    .set('views.layouts', path.join(__dirname, 'views'))\n    .set('modules', path.join(__dirname, 'lib'));\n\n  const config = baselineConfig.get();\n\n  // load the core modules using the file finder\n  const modulesFinder = fileFinder.get('modules', config.modules);\n\n  const DocletHelper = modulesFinder.require('./doclethelper');\n  const PublishJob = modulesFinder.require('./publishjob');\n  const Template = modulesFinder.require('./template');\n\n  const docletHelper = new DocletHelper();\n  const template = new Template(config);\n  const job = new PublishJob(template, opts);\n\n  // set up tutorials\n  helper.setTutorials(tutorials);\n\n  docletHelper.addDoclets(data);\n\n  job\n    .setPackage(docletHelper.getPackage())\n    .setNavTree(docletHelper.navTree)\n    .setAllLongnamesTree(docletHelper.allLongnamesTree);\n\n  // create the output directory so we can start generating files\n  job\n    .createOutputDirectory()\n    // then generate the source files so we can link to them\n    .generateSourceFiles(docletHelper.shortPaths);\n\n  // generate globals page if necessary\n  job.generateGlobals(docletHelper.globals);\n\n  // generate TOC data and index page\n  job\n    .generateTocData({hasGlobals: docletHelper.hasGlobals()})\n    .generateIndex(opts.readme);\n\n  // generate the rest of the output files (excluding tutorials)\n  docletHelper.getOutputLongnames().forEach(function (longname) {\n    job.generateByLongname(\n      longname,\n      docletHelper.getLongname(longname),\n      docletHelper.getMemberof(longname),\n    );\n  });\n\n  // finally, generate the tutorials, and copy static files to the output\n  // directory\n  job.generateTutorials(tutorials).copyStaticFiles();\n\n  // custom Cast pages\n  job.generateTocYaml(\n    docletHelper\n      .getCategory('classes')\n      .concat(docletHelper.getCategory('namespaces'))\n      .concat(docletHelper.getCategory('interfaces')),\n    global.env.opts.query ? global.env.opts.query.basepath : '',\n  );\n  job.generateIndexAll(Object.keys(docletHelper.longname));\n};\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/static/jsdoc.css",
    "content": ".label {\n  font-style: italic;\n  text-transform: uppercase;\n}\n\n.dl-compact dt {\n  margin: 8px 0px 0px 0px;\n}\n\nh1.devsite-page-title {\n  display: none;\n}\n\nh1 {\n  margin-bottom: 8px;\n}\n\nh2 {\n  margin: 48px 0px 0px 0px;\n}\n\nh3.symbol-name {\n  margin: 32px 0px 8px 0px;\n}\n\nh3.class-list:first-child {\n  display: block;\n  margin: 16px 0px 0px 0px;\n}\n\nh3.class-list {\n  display: block;\n  margin: 12px 0px 0px 0px;\n  font-size: 16px;\n}\n\nh3.class-list + p {\n  margin: 0px 0px 0px 0px;\n}\n\nh4 {\n  margin: 8px 0px 0px 0px;\n}\n\ntd p {\n  margin: 6px 0px 0px 0px;\n}\n\np.type-signature {\n  font-family: 'Roboto Mono', monospace;\n  font-size: 80%;\n  font-style: normal;\n  font-variant: normal;\n  font-weight: 500;\n  font-stretch: normal;\n  line-height: normal;\n  margin-top: 0px;\n}\n\np.symbol-index-name {\n  margin: 8px 0px 0px 0px;\n}\n\nh2.symbol-header {\n  margin: 36px 0px 0px 0px;\n}\n\ntable.function.param tr {\n  background: #80a7b9;\n}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/augments.hbs",
    "content": "{{#any augments}}\n  <dt>{{translateHeading 'augments' augments}}</dt>\n  {{#each augments}}\n    <dd><small>{{link this}}</small></dd>\n  {{/each}}\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/classes-links.hbs",
    "content": "{{#any items}}\n<h>{{translateHeading headingKey items}}</h>\n  <section id='members-links'>\n  {{#each items}}\n    <h3 {{~cssClass '!class-list'}}>{{link longname name}}</h3>\n    {{#if classdesc}}\n      {{#markdown}}{{resolveLinks classdesc}}{{/markdown}}\n    {{else}}\n      {{#markdown}}{{resolveLinks description}}{{/markdown}}\n    {{/if}}\n  {{/each}}\n  </section>\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/details-table-row.hbs",
    "content": "<tr>\n  {{#block 'details-table-name'}}\n    <td {{~cssClass '!details-table-name'}}>\n      <p>{{#if name}}{{name}}{{else}}{{translate 'tables.notApplicable'}}{{/if}}</p>\n    </td>\n  {{/block}}\n  <td>\n  {{#block 'details-table-optional'}}\n    {{#if optional}}\n      <p {{~cssClass '!details-table-optional'}}>{{translate 'tables.body.isOptional'}}</p>\n    {{/if}}\n  {{/block}}\n  {{#block 'details-table-types'}}\n    {{#if type}}\n      {{#all type type.parsedType}}\n        <p {{~cssClass '!details-table-types'}}>{{describeType type.parsedType 'extended'}}</p>\n      {{/all}}\n    {{/if}}\n  {{/block}}\n  {{#block 'details-table-description'}}\n    {{#if description}}\n      {{#markdown}}{{resolveLinks description}}{{/markdown}}\n    {{/if}}\n    {{#if (hasModifiers this)}}\n      <p>{{modifierText this}}</p>\n    {{/if}}\n    {{#if ../children}}\n      <p>{{translate 'tables.body.eachValueHasProperties' name=name}}</p>\n        {{#withOnly values=../children}}\n          {{#embed 'details-table'}}{{/embed}}\n        {{/withOnly}}\n    {{/if}}\n  {{/block}}\n  </td>\n</tr>\n\n{{!-- This produces extra <p>'s in the output\n<p {{~cssClass '!details-table-description'}}>\n  {{#markdown}}{{resolveLinks description}}{{/markdown}}</p>\n--}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/details-table.hbs",
    "content": "<table class=\"function param responsive\">\n  {{#block 'details-table-header'}}\n    <tr>\n      <th colspan=\"2\">\n        {{#if isEnum}}\n          <h>{{translateHeading 'values' params}}</h>\n        {{else}}\n          <h>{{translateHeading 'parameters' params}}</h>\n        {{/if}}\n      </th>\n    </tr>\n  {{/block}}\n  {{#block 'details-table-body'}}\n    <tbody>\n      {{#each values}}\n        {{#embed 'details-table-row'}}{{/embed}}\n      {{/each}}\n    </tbody>\n  {{/block}}\n</table>\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/implements.hbs",
    "content": "{{#any implements}}\n  <dt>{{translateHeading 'implements' implements}}</dt>\n  {{#each implements}}\n    <dd><small>{{link this}}</small></dd>\n  {{/each}}\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/index-all.hbs",
    "content": "{{#extend 'layout'}}\n  {{#content 'title'}}\n    <title>Index All</title>\n  {{/content}}\n  {{#content 'body-main-content'}}\n    <ul>\n      {{#each longnames}}\n        <li>{{link this}}</li>\n      {{/each}}\n    </ul>\n  {{/content}}\n{{/extend}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/index.hbs",
    "content": "{{#extend 'layout'}}\n  {{#content 'title'}}\n    <title>Overview</title>\n  {{/content}}\n  {{#content 'body-main-content'}}\n    {{#if readme}}\n      {{{ readme }}}\n    {{else}}\n      {{#block 'body-symbol-index'}}\n        {{#embed 'symbol-index'}}{{/embed}}\n      {{/block}}\n    {{/if}}\n  {{/content}}\n{{/extend}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/layout.hbs",
    "content": "<!DOCTYPE html>\n<html devsite>\n    {{#block 'head'}}\n        <head>\n            {{#block 'meta'}}\n                <meta name=\"project_path\" value=\"{{query 'projectRoot'}}_project.yaml\" />\n                <meta name=\"book_path\" value=\"{{query 'projectRoot'}}_book.yaml\" />\n                <meta name=\"gtm_var\" data-key=\"docType\" data-value=\"reference\">\n            {{/block}}\n            {{#block 'title'}}\n                <title>{{translatePageTitle pageTitlePrefix pageTitle pageCategory}}</title>\n            {{/block}}\n            {{#block 'css'}}\n                <link href=\"jsdoc.css\" rel=\"stylesheet\">\n            {{/block}}\n        </head>\n    {{/block}}\n    {{#block 'body'}}\n        <body>\n            {{#block 'body-container'}}\n                <div id=\"jsdoc-body-container\">\n                    {{#block 'body-content'}}\n                        <div id=\"jsdoc-content\">\n                            {{#block 'body-content-container'}}\n                                <div id=\"jsdoc-content-container\">\n                                    {{#block 'body-banner'}}\n                                        <div id=\"jsdoc-banner\" role=\"banner\">\n                                            {{#block 'body-banner-content'}}\n                                                {{!--\n                                                    Override the `body-banner-content` block\n                                                    to add content.\n                                                --}}\n                                            {{/block}}\n                                        </div>\n                                    {{/block}}\n                                    {{#block 'body-main'}}\n                                        {{!-- Adding a section here causes heading to bump down H1 > H2, etc. --}}\n                                        <div id=\"jsdoc-main\" role=\"main\">\n                                            {{#block 'body-main-content'}}\n                                                {{!--\n                                                    Override the `body-main-content` block\n                                                    to add content.\n                                                --}}\n                                            {{/block}}\n                                        </div>\n                                    {{/block}}\n                                </div>\n                            {{/block}}\n                            {{#block 'body-toc-navbar'}}\n                                {{!-- content is inserted on page load --}}\n                                <nav id=\"jsdoc-toc-nav\" role=\"navigation\"></nav>\n                            {{/block}}\n                        </div>\n                    {{/block}}\n                </div>\n            {{/block}}\n            {{#block 'body-footer'}}\n            {{/block}}\n            {{!-- No scripts in Devsite --}}\n        </body>\n    {{/block}}\n</html>\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/params.hbs",
    "content": "{{#any params}}\n    <section>\n        {{#withOnly values=(reparentItems this 'params')}}\n            {{#embed 'details-table'}}{{/embed}}\n        {{/withOnly}}\n    </section>\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/properties.hbs",
    "content": "{{#any (filterProperties properties)}}\n  <section>\n    {{#unless isEnum}}\n      <h>{{translateHeading 'properties' properties}}</h>\n    {{/unless}}\n    {{#withOnly values=(reparentItems this 'properties') isEnum=isEnum}}\n      {{#embed 'details-table'}}{{/embed}}\n    {{/withOnly}}\n  </section>\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/see.hbs",
    "content": "{{#any see}}\n    <dt>{{translateHeading 'see' see}}</dt>\n    {{#each see}}\n        <dd><small>{{link (see this ../longname)}}</small></dd>\n    {{/each}}\n{{/any}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/signature.hbs",
    "content": "{{!--\n    Note that this section omits the return type for:\n    + Constructors\n    + Methods with no explicit return type\n--}}\n{{#with symbol}}\n  {{~#if (needsSignature this)~}}\n    {{!-- CONSTRUCTOR PREFIX --}}\n    {{#is kind 'class'}}\n      {{#embed 'constructor-prefix'}}{{/embed}}\n    {{/is}}\n    {{name}}{{~formatParams params~}}\n    {{~#isnt kind 'class'~}}\n      {{~#if (returnTypes this)~}}\n        {{~translate 'returnTypesSeparator'~}}\n        {{describeType (returnTypes this)~}}\n      {{~/if~}}\n    {{~/isnt~}}\n  {{~else~}}\n    {{~#if type~}}\n      {{~describeType type.parsedType~}}\n    {{~/if~}}\n  {{~/if~}}\n{{/with}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-content.hbs",
    "content": "<section>\n  {{#block 'symbol-content-classes'}}\n    {{#withOnly items=members.classes headingKey='classes'}}\n      {{#embed 'classes-links'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-interfaces'}}\n    {{#withOnly items=members.interfaces headingKey='interfaces'}}\n      {{#embed 'members-links'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-namespaces'}}\n    {{#withOnly items=members.namespaces headingKey='namespaces'}}\n      {{#embed 'members-links'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-enums'}}\n    {{#withOnly items=(where members.properties isEnum=true) headingKey='enums'}}\n      {{#embed 'members-details'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-properties'}}\n    {{#withOnly items=(where members.properties isEnum=undefined) headingKey='properties'}}\n      {{#embed 'members-details'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-methods'}}\n    {{#withOnly items=members.functions headingKey='functions'}}\n      {{#embed 'members-details'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-typedefs'}}\n    {{#withOnly items=members.typedefs headingKey='typedefs'}}\n      {{#embed 'members-details'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n\n  {{#block 'symbol-content-events'}}\n    {{#withOnly items=members.events headingKey='events'}}\n      {{#embed 'members-details'}}{{/embed}}\n    {{/withOnly}}\n  {{/block}}\n</section>"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-detail.hbs",
    "content": "{{~#unless symbol.hideconstructor~}}\n  <h id=\"{{id symbol}}\" {{~cssClass '!symbol-name'}}>\n    {{~#with symbol~}}\n      {{name}}\n    {{~/with~}}\n  </h>\n{{~/unless~}}\n{{!--\n    Note that we omit the labels for classes, modules, and namespaces, since\n    these labels would duplicate the labels for the page's main heading.\n--}}\n{{~#if symbol.kind~}}\n  {{~#contains 'class' 'module' 'namespace' value=symbol.kind~}}\n  {{~else~}}\n    {{~#embed 'symbol-labels'}}{{/embed~}}\n  {{~/contains~}}\n{{~/if~}}\n{{~#unless symbol.hideconstructor~}}\n  <p {{~cssClass '!type-signature'}}>\n    {{~#embed 'signature'}}{{/embed~}}\n  </p>\n{{~/unless~}}\n{{#with symbol}}\n  {{~#unless symbol.hideconstructor~}}\n    {{#embed 'description'}}{{/embed}}\n    {{#embed 'params'}}{{/embed}}\n    {{#embed 'properties'}}{{/embed}}\n    <dl {{~cssClass '!dl-compact'}}>\n      {{#embed 'details'}}{{/embed}}\n    </dl>\n    {{#embed 'examples'}}{{/embed}}\n  {{~/unless~}}\n{{/with}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-header.hbs",
    "content": "<header {{~cssClass '!page-header'}}>\n  {{#block 'symbol-header-heading'}}\n      <h>\n        {{~#isnt longname memberof}}<small>{{ancestors longname}}</small>{{/isnt~}}\n        <span {{~cssClass '!symbol-name'}}>{{name}}</span></h>\n  {{~/block}}\n  {{~#withOnly symbol=this~}}\n    {{~#embed 'symbol-labels'}}{{/embed~}}\n  {{/withOnly~}}\n  {{#block 'symbol-header-classdesc'}}\n    {{#if classdesc}}\n      {{#markdown}}{{resolveLinks classdesc}}{{/markdown}}\n    {{/if}}\n  {{/block}}\n  {{#block 'symbol-header-description'}}\n    {{!--\n      We don't put a description here for classes, or for namespaces that are\n      also functions.\n    --}}\n    {{#isnt kind 'class'}}\n      {{#is kind 'namespace'}}\n        {{#unless (needsSignature this)}}\n          {{#embed 'description'}}{{/embed}}\n        {{/unless}}\n      {{else}}\n        {{#embed 'description'}}{{/embed}}\n      {{/is}}\n    {{/isnt}}\n  {{/block}}\n  {{#block 'symbol-header-class-details'}}\n    {{!--\n      For classes where we're not displaying the constructor, we show the\n      details here.\n    --}}\n    {{#if hideconstructor}}\n      <dl {{~cssClass '!dl-compact'}}>\n        {{#embed 'details'}}{{/embed}}\n      </dl>\n    {{/if}}\n  {{/block}}\n</header>\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-index-section.hbs",
    "content": "<div {{~cssClass '!symbol-index-column'}}>\n  {{#each doclets}}\n    <p {{~cssClass '!symbol-index-name'}}>\n      {{linkLongnameWithSignature this '!symbol-index-name'}}\n    </p>\n  {{/each}}\n</div>\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-index.hbs",
    "content": "<div {{~cssClass '!symbol-index'}}>\n  {{#eachIndexGroup allLongnamesTree}}\n    <section>\n      <div {{~cssClass '!symbol-index-content'}}>\n        <h id=\"{{@groupName}}\" {{~cssClass '!symbol-header'}}>{{@groupName}}</h>\n        <div {{~cssClass '!symbol-index-section'}}>\n          {{#each (group this 3)}}\n            {{#withOnly doclets=this}}\n              {{#embed 'symbol-index-section'}}{{/embed}}\n            {{/withOnly}}\n          {{/each}}\n        </div>\n      </div>\n    </section>\n  {{/eachIndexGroup}}\n</div>\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-labels.hbs",
    "content": "{{~#withOnly allLabels=(labels symbol)~}}\n  {{~#any allLabels~}}\n    <div {{~cssClass '!symbol-detail-labels'}}>\n      {{~#each allLabels~}}\n        <span {{~cssClass '!label' class}}><small>{{text}}</small></span>\n        {{~#unless @last}}&nbsp;&nbsp;&nbsp;{{/unless~}}\n      {{~/each~}}\n    </div>\n  {{~/any~}}\n{{~/withOnly~}}\n"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/symbol-overview.hbs",
    "content": "{{#any docs}}\n  {{#first docs}}\n    {{#embed 'symbol-header'}}{{/embed}}\n  {{/first}}\n{{/any}}\n\n{{#each docs}}\n  {{#if @first}}\n    {{!-- heading that indicates what we're about to list --}}\n    {{#is pageCategory 'classes'}}\n      <section>\n    {{else}}\n      {{#any exports}}\n        <section>\n          <h>{{translateHeading 'exports' docs}}</h>\n      {{/any}}\n    {{/is}}\n  {{/if}}\n\n  {{!--\n      If a module exports only one symbol, document the symbol in the overview, since it's not a\n      member of anything else.\n  --}}\n  {{#is kind 'module'}}\n    {{#if exports}}\n      {{#each exports}}\n        {{#withOnly symbol=this}}\n          <section>\n            {{#embed 'symbol-detail'}}{{/embed}}\n          </section>\n        {{/withOnly}}\n      {{/each}}\n    {{/if}}\n  {{!-- Classes also need additional information in the overview. --}}\n  {{else is kind 'class'}}\n    {{#unless hideconstructor}}\n      <section>\n        <h>Constructor</h>\n        <section>\n          {{#withOnly symbol=this}}\n            {{#embed 'symbol-detail'}}{{/embed}}\n          {{/withOnly}}\n        </section>\n      </section>\n    {{/unless}}\n  {{!-- Externals also need additional information in the overview. --}}\n  {{else is kind 'external'}}\n    <section>\n      {{#with this}}\n        <dl {{~cssClass '!dl-compact'}}>\n          {{#embed 'details'}}{{/embed}}\n        </dl>\n      {{/with}}\n    </section>\n  {{!-- Namespaces that are functions also need additional information in the overview. --}}\n  {{else is kind 'namespace'}}\n    {{#if (needsSignature this)}}\n      <section>\n        {{#withOnly symbol=this}}\n          {{#embed 'symbol-detail'}}{{/embed}}\n        {{/withOnly}}\n      </section>\n    {{/if}}\n  {{/is}}\n\n  {{#if @last}}\n    {{#is pageCategory 'classes'}}\n      </section>\n    {{else}}\n      {{#any exports}}\n        </section>\n      {{/any}}\n    {{/is}}\n  {{/if}}\n{{/each}}"
  },
  {
    "path": "infra/templates/reference-docs/jsdoc/views/toc-yaml.hbs",
    "content": "toc:\n{{#each symbols~}}\n- title: \"{{tocYamlName}}\"\n  path: {{../basepath}}/{{basename (url longname) '.html'}}\n{{/each}}\n- title: \"Index of all\"\n  path: {{basepath}}/index-all\n"
  },
  {
    "path": "infra/testing/activate-and-control.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst activateSWSafari = require('./activate-sw-safari');\n\n// TODO(philipwalton): remove this in favor of using workbox-window.\nmodule.exports = async (swURL) => {\n  if (global.__workbox.seleniumBrowser.getId() === 'safari') {\n    return activateSWSafari(swURL);\n  }\n\n  const error = await global.__workbox.webdriver.executeAsyncScript(\n    (swURL, cb) => {\n      function _onStateChangePromise(registration, desiredState) {\n        return new Promise((resolve, reject) => {\n          if (desiredState === 'activated' && registration.active) {\n            resolve();\n            return;\n          }\n\n          if (registration.installing === null) {\n            reject(new Error('No installing service worker found.'));\n            return;\n          }\n\n          const serviceWorker = registration.installing;\n\n          // We unregister all service workers after each test - this should\n          // always trigger an install state change\n          const stateChangeListener = (event) => {\n            if (event.target.state === desiredState) {\n              serviceWorker.removeEventListener(\n                'statechange',\n                stateChangeListener,\n              );\n              resolve();\n              return;\n            }\n\n            if (event.target.state === 'redundant') {\n              serviceWorker.removeEventListener(\n                'statechange',\n                stateChangeListener,\n              );\n              reject(new Error('Installing service worker became redundant.'));\n              return;\n            }\n          };\n\n          serviceWorker.addEventListener('statechange', stateChangeListener);\n        });\n      }\n\n      navigator.serviceWorker\n        .register(swURL)\n        .then((registration) => {\n          return _onStateChangePromise(registration, 'activated');\n        })\n        .then(() => {\n          // Ensure the page is being controlled by the SW.\n          if (\n            navigator.serviceWorker.controller &&\n            navigator.serviceWorker.controller.scriptURL === swURL\n          ) {\n            return;\n          } else {\n            return new Promise((resolve) => {\n              navigator.serviceWorker.addEventListener(\n                'controllerchange',\n                () => {\n                  if (navigator.serviceWorker.controller.scriptURL === swURL) {\n                    resolve();\n                  }\n                },\n              );\n            });\n          }\n        })\n        .then(() => cb())\n        .catch((error) => cb(error.message));\n    },\n    swURL,\n  );\n\n  if (error) {\n    throw new Error(error);\n  }\n};\n"
  },
  {
    "path": "infra/testing/activate-sw-safari.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// TODO(philipwalton): remove this in favor of using workbox-window.\nmodule.exports = async (swURL) => {\n  // First step: Wait for the page to activate\n  let error = await global.__workbox.webdriver.executeAsyncScript(\n    (swURL, cb) => {\n      const onStateChangePromise = (registration, desiredState) => {\n        return new Promise((resolve, reject) => {\n          if (\n            desiredState === 'activated' &&\n            registration.active &&\n            // Checking that the URLs match is needed to fix:\n            // https://github.com/GoogleChrome/workbox/issues/1633\n            registration.active.scriptURL === swURL\n          ) {\n            resolve();\n            return;\n          }\n\n          const serviceWorker = registration.installing || registration.waiting;\n          if (serviceWorker === null) {\n            throw new Error(\n              'There is no installing or waiting service worker.',\n            );\n          }\n\n          const stateChangeListener = function (evt) {\n            if (evt.target.state === desiredState) {\n              serviceWorker.removeEventListener(\n                'statechange',\n                stateChangeListener,\n              );\n              resolve();\n              return;\n            }\n\n            if (evt.target.state === 'redundant') {\n              serviceWorker.removeEventListener(\n                'statechange',\n                stateChangeListener,\n              );\n\n              // Must call reject rather than throw error here due to this\n              // being inside the scope of the callback function stateChangeListener\n              reject(new Error('The new service worker became redundant.'));\n              return;\n            }\n          };\n\n          serviceWorker.addEventListener('statechange', stateChangeListener);\n        });\n      };\n\n      navigator.serviceWorker\n        .register(swURL)\n        .then((registration) => onStateChangePromise(registration, 'activated'))\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    },\n    swURL,\n  );\n\n  if (error) {\n    throw error;\n  }\n\n  // To be 100% certain - ensure the SW is controlling the page.\n  error = await global.__workbox.webdriver.executeAsyncScript((swURL, cb) => {\n    if (\n      navigator.serviceWorker.controller &&\n      navigator.serviceWorker.controller.scriptURL === swURL\n    ) {\n      cb();\n    } else if (!navigator.serviceWorker.controller) {\n      cb(`There's no service worker controlling the page.`);\n    } else {\n      cb(\n        `There's an unexpected SW controlling the page: ${navigator.serviceWorker.controller.scriptURL}`,\n      );\n    }\n  }, swURL);\n\n  if (error) {\n    throw error;\n  }\n};\n"
  },
  {
    "path": "infra/testing/auto-stub-logger.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport sinon from 'sinon';\nimport {logger} from '../../packages/workbox-core/_private/logger.mjs';\n\nconst sandbox = sinon.createSandbox();\nconst stubLogger = () => {\n  // Logger will be `null` in production mode.\n  if (logger) {\n    sandbox.stub(logger);\n  }\n};\n\n// Silence any early messages (Normally caused by logging from an import at\n// the top of a test)\nstubLogger();\n\n// This is part of the \"root\" mocha suite - meaning it'll reset all the logger\n// values before every test.\nbeforeEach(function () {\n  sandbox.restore();\n\n  stubLogger();\n});\n\nafter(function () {\n  sandbox.restore();\n});\n"
  },
  {
    "path": "infra/testing/clean-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst runInSW = require('./comlink/node-interface');\n\nmodule.exports = async (webdriver, testingURL) => {\n  await webdriver.get(testingURL);\n  try {\n    await runInSW('clearAllCaches');\n  } catch (ignored) {\n    // There might not yet be a service worker registered, in which case we\n    // can't call runInSW().\n  }\n  const error = await webdriver.executeAsyncScript((cb) => {\n    navigator.serviceWorker\n      .getRegistration()\n      .then((reg) => {\n        if (reg) {\n          return reg.unregister();\n        }\n      })\n      .then(() => cb())\n      .catch((err) => cb(err.message));\n  });\n  if (error) {\n    throw new Error(error);\n  }\n  await webdriver.get(testingURL);\n};\n"
  },
  {
    "path": "infra/testing/comlink/node-interface.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nmodule.exports = async (command, ...args) => {\n  const result = await global.__workbox.webdriver.executeAsyncScript(\n    (command, args, cb) => {\n      if (!('_runInSW' in window)) {\n        cb({error: '_runInSW is not initialized.'});\n      } else {\n        window._runInSW[command](...args)\n          .then((result) => cb(result))\n          .catch((error) =>\n            cb({\n              error: `While running ${command}(${args}): ${error.message}`,\n            }),\n          );\n      }\n    },\n    command,\n    args,\n  );\n\n  if (result instanceof Object && 'error' in result) {\n    throw new Error(result.error);\n  }\n  return result;\n};\n"
  },
  {
    "path": "infra/testing/comlink/sw-interface.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/comlink.js');\n\n// TODO: Standardize on naming, and move over some of the legacy uses of\n// postMessage() to this new approach.\n// These are all the methods that will be called in the SW, but are exposed in\n// the window context via Comlink.\nconst api = {\n  cachesKeys: () => {\n    return caches.keys();\n  },\n\n  clearAllCaches: async () => {\n    const keys = await caches.keys();\n    return Promise.all(keys.map((key) => caches.delete(key)));\n  },\n\n  doesDbExist: (dbName) => {\n    return new Promise((resolve) => {\n      const result = indexedDB.open(dbName);\n      result.onupgradeneeded = (event) => {\n        event.target.transaction.abort();\n        event.target.result.close();\n        resolve(false);\n      };\n      result.onsuccess = (event) => {\n        event.target.result.close();\n        resolve(true);\n      };\n    });\n  },\n\n  getObjectStoreEntries: (dbName, objStoreName) => {\n    return new Promise((resolve) => {\n      const result = indexedDB.open(dbName);\n      result.onsuccess = (event) => {\n        const db = event.target.result;\n        db\n          .transaction(objStoreName)\n          .objectStore(objStoreName)\n          .getAll().onsuccess = (event) => {\n          resolve(event.target.result);\n        };\n      };\n    });\n  },\n\n  cacheURLs: async (cacheName) => {\n    const cache = await caches.open(cacheName);\n    const requests = await cache.keys();\n    return requests.map((request) => request.url);\n  },\n\n  getCachedResponseText: async (cacheName, url) => {\n    const cache = await caches.open(cacheName);\n    const response = await cache.match(url);\n    return response.text();\n  },\n\n  isNavigationPreloadSupported: async () => {\n    return workbox.navigationPreload.isSupported();\n  },\n};\n\nself.addEventListener('message', (event) => {\n  if (event.data instanceof MessagePort) {\n    Comlink.expose(api, event.data);\n    event.data.start();\n  }\n});\n"
  },
  {
    "path": "infra/testing/comlink/window-interface.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nfunction initComlink() {\n  const channel = new MessageChannel();\n  navigator.serviceWorker.controller.postMessage(channel.port2, [\n    channel.port2,\n  ]);\n  window._runInSW = Comlink.wrap(channel.port1);\n  channel.port1.start();\n}\n\nconst scriptEl = document.createElement('script');\nscriptEl.src = '/__WORKBOX/comlink.js';\nscriptEl.addEventListener('load', () => {\n  if (navigator.serviceWorker.controller) {\n    initComlink();\n  }\n  navigator.serviceWorker.addEventListener('controllerchange', initComlink);\n});\ndocument.body.appendChild(scriptEl);\n"
  },
  {
    "path": "infra/testing/confirm-directory-contains.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst globby = require('globby');\nconst upath = require('upath');\n\nmodule.exports = async (directory, expectedContents) => {\n  const relativeFiles = await globby('**', {cwd: directory});\n  const absoluteFilesWithNativeSeparator = relativeFiles.map((file) =>\n    upath.resolve(directory, file).replace(/\\//g, upath.sep),\n  );\n  expect(absoluteFilesWithNativeSeparator).to.have.members(expectedContents);\n};\n"
  },
  {
    "path": "infra/testing/env-it.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nmodule.exports = {\n  devOnly: {\n    it: function (title, cb) {\n      // If the wrapped callback expects done, then we need to call it() with\n      // a function that expects done.\n      if (cb.length === 1) {\n        it(title, function (done) {\n          if (process.env.NODE_ENV === 'production') {\n            return this.skip();\n          }\n\n          return cb(done);\n        });\n      } else {\n        it(title, function () {\n          if (process.env.NODE_ENV === 'production') {\n            return this.skip();\n          }\n\n          return cb();\n        });\n      }\n    },\n  },\n\n  prodOnly: {\n    it: function (title, cb) {\n      // If the wrapped callback expects done, then we need to call it() with\n      // a function that expects done.\n      if (cb.length === 1) {\n        it(title, function (done) {\n          if (process.env.NODE_ENV !== 'production') {\n            return this.skip();\n          }\n\n          return cb(done);\n        });\n      } else {\n        it(title, function () {\n          if (process.env.NODE_ENV !== 'production') {\n            return this.skip();\n          }\n\n          return cb();\n        });\n      }\n    },\n  },\n};\n"
  },
  {
    "path": "infra/testing/expectError.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst logHelper = require('../utils/log-helper');\n\nmodule.exports = async (func, errorName, finalCb) => {\n  let caughtError = null;\n  try {\n    const result = func();\n    if (result && result instanceof Promise) {\n      await result;\n    }\n  } catch (err) {\n    caughtError = err;\n  }\n\n  if (!caughtError) {\n    throw new Error('Expected error to be thrown but function ran correctly.');\n  }\n\n  if (caughtError.constructor.name !== 'WorkboxError') {\n    logHelper.warn(`Unexpected error thrown.`, caughtError);\n  }\n\n  expect(caughtError.constructor.name).to.equal('WorkboxError');\n  expect(caughtError.name).to.equal(errorName);\n\n  if (finalCb) {\n    return finalCb(caughtError);\n  }\n};\n"
  },
  {
    "path": "infra/testing/generate-variant-tests.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/**\n * @callback VariantCallback\n * @param {Object} variant\n */\n/**\n * This is a helper function that will auto-generate mocha unit tests\n * for various inputs.\n *\n * @param {string} itTitle This is the title that will be passed to the it()\n * function. The variant will be added to the end of this title to help\n * idenfity the failing test.\n * @param {Array<Object>} variants This should be all the variations of the\n * test you wish to generate.\n * @param {VariantCallback} func This is the function that will be called, with a\n * variant as the only argument. This function should perform the desired test.\n */\nconst generateVariantTests = (itTitle, variants, func) => {\n  variants.forEach((variant) => {\n    // We are using function() {} here and NOT ARROW FUNCTIONS\n    // to work with Mocha's binding for tests.\n    it(`${itTitle}. Variant: '${JSON.stringify(variant)}'`, function () {\n      // Use .call to get the correct `this` binding needed by mocha.\n      // eslint-disable-next-line no-invalid-this\n      return func.call(this, variant);\n    });\n  });\n};\n\nif (module.exports) {\n  module.exports = generateVariantTests;\n} else {\n  self.__workbox.generateVariantTests = generateVariantTests;\n}\n"
  },
  {
    "path": "infra/testing/helpers/compareResponses.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst compareResponses = async (first, second, shouldBeSame) => {\n  const firstBody = await first.clone().text();\n  const secondBody = await second.clone().text();\n\n  if (shouldBeSame) {\n    expect(firstBody).to.equal(secondBody);\n  } else {\n    expect(firstBody).to.not.equal(secondBody);\n  }\n};\n\nexport {compareResponses};\n"
  },
  {
    "path": "infra/testing/helpers/extendable-event-utils.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst extendLifetimePromises = new WeakMap();\nconst eventResponses = new WeakMap();\n\nexport const eventDoneWaiting = async (event, {catchErrors = true} = {}) => {\n  const promises = extendLifetimePromises.get(event);\n  let promise;\n\n  while ((promise = promises.shift())) {\n    // Ignore errors by default;\n    if (catchErrors) {\n      promise = promise.catch((e) => e);\n    }\n    await promise;\n  }\n};\n\nexport const spyOnEvent = (event) => {\n  const promises = [];\n  extendLifetimePromises.set(event, promises);\n\n  event.waitUntil = sinon.stub().callsFake((promise) => {\n    promises.push(promise);\n  });\n\n  if (event instanceof FetchEvent) {\n    event.respondWith = sinon.stub().callsFake((responseOrPromise) => {\n      const promise = Promise.resolve(responseOrPromise);\n\n      eventResponses.set(event, promise);\n      promises.push(promise);\n\n      // TODO(philipwalton): we cannot currently call the native\n      // `respondWith()` due to this bug in Firefox:\n      // https://bugzilla.mozilla.org/show_bug.cgi?id=1538756\n      // FetchEvent.prototype.respondWith.call(event, responseOrPromise);\n    });\n  }\n};\n\nexport const dispatchAndWaitUntilDone = async (event) => {\n  spyOnEvent(event);\n  self.dispatchEvent(event);\n  await eventDoneWaiting(event);\n};\n\nexport const dispatchAndWaitForResponse = async (event) => {\n  await dispatchAndWaitUntilDone(event);\n  const response = await eventResponses.get(event);\n  return response;\n};\n"
  },
  {
    "path": "infra/testing/helpers/generateOpaqueResponse.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Cache a resonse value and clone it instead of re-fetching every time.\nlet response;\n\nexport const generateOpaqueResponse = async () => {\n  if (!response) {\n    response = await fetch('https://google.com', {mode: 'no-cors'});\n  }\n  return response.clone();\n};\n"
  },
  {
    "path": "infra/testing/helpers/generateUniqueResponse.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nlet uid = 0;\n\nexport const generateUniqueResponse = (responseInit = {}) => {\n  return new Response(`${++uid}`, responseInit);\n};\n"
  },
  {
    "path": "infra/testing/helpers/sleep.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/**\n * Asynchronously waits for the passed number of milliseconds.\n *\n * @param {number} ms\n * @return {Promise}\n */\nexport const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n"
  },
  {
    "path": "infra/testing/server/cross-origin-server.js",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst express = require('express');\n\nconst PORT = 3010;\n\nlet app;\nlet server;\n\nfunction initApp(staticDir) {\n  app = express();\n  app.use(express.static(staticDir));\n}\n\nfunction start(staticDir) {\n  if (!app) {\n    initApp(staticDir);\n  }\n\n  return new Promise((resolve, reject) => {\n    server = app.listen(PORT, (error) => {\n      if (error) {\n        reject(error);\n      } else {\n        resolve(`http://localhost:${PORT}`);\n      }\n    });\n  });\n}\n\nfunction stop() {\n  if (server) {\n    server.close();\n  }\n}\n\nmodule.exports = {\n  start,\n  stop,\n};\n"
  },
  {
    "path": "infra/testing/server/index.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst bodyParser = require('body-parser');\nconst express = require('express');\nconst nunjucks = require('nunjucks');\nconst path = require('path');\nconst requireDir = require('require-dir');\nconst serveIndex = require('serve-index');\n\nconst logHelper = require('../../utils/log-helper');\nconst RequestCounter = require('./request-counter');\n\nconst PORT = 3004;\n\nlet app;\nlet requestCounters;\nlet server;\n\nfunction initApp() {\n  app = express();\n\n  // Configure nunjucks to work with express routes.\n  nunjucks.configure('./', {express: app, noCache: true});\n\n  // Exposed the `.body` property on requests for application/json.\n  app.use(bodyParser.json());\n\n  requestCounters = new Set();\n  app.use((req, res, next) => {\n    for (const requestCounter of requestCounters) {\n      requestCounter.count(req);\n    }\n    next();\n  });\n\n  const routes = Object.values(requireDir('./routes'));\n  for (const {match, handler, method} of routes) {\n    app[method || 'get'](match, handler);\n  }\n\n  const staticDir = path.resolve(__dirname, '..', '..', '..');\n  app.use(express.static(staticDir), serveIndex(staticDir, {icons: true}));\n}\n\nfunction start() {\n  if (!app) {\n    initApp();\n  }\n\n  return new Promise((resolve, reject) => {\n    server = app.listen(PORT, (error) => {\n      if (error) {\n        reject(error);\n      } else {\n        logHelper.log(`The test server is running at ${getAddress()}`);\n        resolve();\n      }\n    });\n  });\n}\n\nfunction stop() {\n  if (server) {\n    server.close();\n  }\n}\n\nfunction getAddress() {\n  return `http://localhost:${PORT}`;\n}\n\nfunction startCountingRequests(headerValue) {\n  const requestCounter = new RequestCounter(headerValue);\n  requestCounters.add(requestCounter);\n  return requestCounter;\n}\n\nfunction stopCountingRequests(requestCounter) {\n  requestCounters.delete(requestCounter);\n}\n\nmodule.exports = {\n  getAddress,\n  start,\n  startCountingRequests,\n  stop,\n  stopCountingRequests,\n};\n"
  },
  {
    "path": "infra/testing/server/request-counter.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nclass RequestCounter {\n  constructor(headerName) {\n    this.headerName = headerName;\n    this.headerCount = {};\n    this.urlCount = {};\n  }\n\n  count(req) {\n    if (this.headerName) {\n      const headerValue = req.get(this.headerName);\n      if (headerValue !== undefined) {\n        if (!(headerValue in this.headerCount)) {\n          this.headerCount[headerValue] = 0;\n        }\n        this.headerCount[headerValue]++;\n      }\n    }\n\n    const url = req.url;\n    if (!(url in this.urlCount)) {\n      this.urlCount[url] = 0;\n    }\n    this.urlCount[url]++;\n  }\n\n  getHeaderCount(headerValue) {\n    return this.headerCount[headerValue] || 0;\n  }\n\n  getURLCount(url) {\n    return this.urlCount[url] || 0;\n  }\n}\n\nmodule.exports = RequestCounter;\n"
  },
  {
    "path": "infra/testing/server/routes/build-file.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst path = require('path');\nconst {\n  outputFilenameToPkgMap,\n} = require('../../../../gulp-tasks/utils/output-filename-to-package-map');\n\nconst match = '/__WORKBOX/buildFile/:packageFile';\n\nasync function handler(req, res) {\n  const {packageFile} = req.params;\n  const pkg = outputFilenameToPkgMap[packageFile.split('.')[0]];\n  const pkgDir = path.resolve('packages', pkg.name);\n\n  let file;\n  if (packageFile.includes('.')) {\n    file = path.join(pkgDir, 'build', packageFile);\n  } else {\n    const pkg = outputFilenameToPkgMap[packageFile];\n\n    // If the pkg.module or pkg.main references something in the build\n    // directory, use that. Otherwise base the build file on the pkg name.\n\n    if (pkg.module && pkg.module.startsWith('build/')) {\n      file = path.join(pkgDir, pkg.module);\n    } else if (pkg.main && pkg.main.startsWith('build/')) {\n      file = path.join(pkgDir, pkg.main);\n    } else {\n      file = path.join(pkgDir, 'build', `${pkg.name}.prod.js`);\n    }\n\n    // When not specifying a dev or prod build via the filename,\n    // we default to the value of NODE_ENV.\n    if (process.env.NODE_ENV !== 'production') {\n      file = file.replace('.prod.', '.dev.');\n    }\n  }\n\n  res.sendFile(file);\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/comlink.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst match = '/__WORKBOX/comlink.js';\n\nasync function handler(req, res) {\n  const comlinkMain = require.resolve('comlink');\n  res.sendFile(comlinkMain);\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/integration-html.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst path = require('path');\n\nconst match = '/*/integration.html';\n\nasync function handler(req, res) {\n  const filePath = path.join(__dirname, '..', 'static', 'integration.html');\n  res.sendFile(filePath);\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/sw-bundle.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {getPackages} = require('../../../../gulp-tasks/utils/get-packages');\nconst {needsTranspile, queueTranspile} =\n  require('../../../../gulp-tasks/transpile-typescript').functions;\nconst {nodeResolve} = require('@rollup/plugin-node-resolve');\nconst {rollup} = require('rollup');\nconst commonjs = require('@rollup/plugin-commonjs');\nconst multiEntry = require('@rollup/plugin-multi-entry');\nconst replace = require('@rollup/plugin-replace');\n\nconst SW_NAMESPACES = getPackages({type: 'sw'}).map((pkg) => {\n  return pkg.workbox.browserNamespace;\n});\n\nconst match = '/test/:package/*/sw-bundle.js';\nconst caches = {};\n\nasync function handler(req, res) {\n  const env = process.env.NODE_ENV || 'development';\n  const packageName = req.params.package;\n\n  res.set('Content-Type', 'text/javascript');\n\n  try {\n    // Ensure the TypeScript transpile step has completed first.\n    if (needsTranspile(packageName)) {\n      await queueTranspile(packageName);\n    }\n\n    // Allows you to selectively run tests by adding the `?test=` to the URL.\n    const testFilter = req.query.filter || '**/test-*.mjs';\n    const bundle = await rollup({\n      input: `./test/${packageName}/sw/` + testFilter,\n      plugins: [\n        multiEntry(),\n        nodeResolve({\n          moduleDirectories: ['packages', 'node_modules'],\n        }),\n        // TODO(philipwalton): some of our shared testing helpers use commonjs\n        // so we have to support this for the time being.\n        commonjs({\n          exclude: '*.mjs',\n        }),\n        replace({\n          preventAssignment: true,\n          delimiters: ['', ''],\n          values: {\n            'process.env.NODE_ENV': JSON.stringify(env),\n            'SW_NAMESPACES': JSON.stringify(SW_NAMESPACES),\n            'WORKBOX_CDN_ROOT_URL': '/__WORKBOX/buildFile',\n          },\n        }),\n      ],\n      // Fail in the case of warning, so rebuilds work.\n      onwarn({loc, message}) {\n        if (loc) {\n          message = `${loc.file} (${loc.line}:${loc.column}) ${message}`;\n        }\n        throw new Error(message);\n      },\n      cache: caches[env],\n    });\n\n    // Update the cache so subsequent bundles are faster, and make sure it\n    // keep the dev/prod caches separate since the source files won't change\n    // between those builds, but the outputs will.\n    caches[env] = bundle.cache;\n\n    const {output} = await bundle.generate({format: 'iife'});\n\n    res.send(output[0].code);\n  } catch (error) {\n    res.status(400).send('');\n    console.error(error);\n  }\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/templates-update.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst templateData = require('../template-data');\n\n// An endpoint to update template data from within tests. Any JSON in the\n// POST body will be merged with the existing template data.\nconst match = '/__WORKBOX/updateTemplate';\n\nasync function handler(req, res) {\n  templateData.assign(req.body);\n  res.end();\n}\n\nmodule.exports = {\n  handler,\n  match,\n  method: 'post',\n};\n"
  },
  {
    "path": "infra/testing/server/routes/templates.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst path = require('path');\nconst templateData = require('../template-data');\n\n// Matches any URL ending in `.njk` and renders the file in the\n// `../templates/*` directory as the response.\n// NOTE: this allows you to serve a template file with any directory path,\n// which is useful when dealing with service worker scope.\nconst match = /(\\.[a-z]+)\\.njk$/;\n\nasync function handler(req, res) {\n  const ext = req.params[0];\n\n  // Since templates can change between tests without the URL changing,\n  // we need to make sure the browser doesn't cache the response.\n  res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n  res.set('Expires', '0');\n\n  switch (ext) {\n    case '.js':\n    case '.mjs':\n      res.set('Content-Type', 'text/javascript');\n      break;\n    case '.html':\n      res.set('Content-Type', 'text/html');\n      break;\n  }\n\n  const file = path.join(__dirname, '..', 'templates', path.basename(req.path));\n  res.render(file, templateData.get());\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/test-sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst globby = require('globby');\nconst path = require('path');\nconst templateData = require('../template-data');\n\nconst match = '/test/:packageName/sw/';\n\nasync function handler(req, res) {\n  // See https://github.com/GoogleChrome/workbox/pull/2744#issuecomment-774138051\n  if (!req.path.endsWith('/')) {\n    return res.redirect(req.path + '/');\n  }\n\n  const {packageName} = req.params;\n  const testFilter = req.query.filter || '**/test-*.mjs';\n\n  const testFiles =\n    (await globby(`test/${packageName}/sw/${testFilter}`)) || [];\n  const testModules = testFiles.map((file) => '/' + file);\n\n  if (testFiles.length > 0) {\n    templateData.assign({packageName, testModules, testFilter});\n\n    res.set('Content-Type', 'text/html');\n\n    // Since templates can change between tests without the URL changing,\n    // we need to make sure the browser doesn't cache the response.\n    res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n    res.set('Expires', '0');\n\n    const file = path.join(__dirname, '..', 'templates', 'test-sw.html.njk');\n    res.render(file, templateData.get());\n  } else {\n    res.status(404).send(`No test files found for: ${testFilter}`);\n  }\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/test-window.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst globby = require('globby');\nconst path = require('path');\nconst templateData = require('../template-data');\n\nconst match = '/test/:packageName/window/';\n\nasync function handler(req, res) {\n  const {packageName} = req.params;\n  const testFilter = req.query.filter || '**/test-*.mjs';\n\n  const testFiles =\n    (await globby(`test/${packageName}/window/**/${testFilter}`)) || [];\n\n  const testModules = testFiles.map((file) => '/' + file);\n\n  templateData.assign({packageName, testModules, testFilter});\n\n  res.set('Content-Type', 'text/html');\n\n  // Since templates can change between tests without the URL changing,\n  // we need to make sure the browser doesn't cache the response.\n  res.set('Cache-Control', 'no-cache, no-store, must-revalidate');\n  res.set('Expires', '0');\n\n  const file = path.join(__dirname, '..', 'templates', 'test-window.html.njk');\n  res.render(file, templateData.get());\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/unique-etag.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst match = '/__WORKBOX/uniqueETag';\n\nlet counter = 0;\nasync function handler(req, res) {\n  res.set('ETag', ++counter);\n  res.send(`ETag is ${counter}.`);\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/routes/unique-value.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst match = '/__WORKBOX/uniqueValue';\n\nlet counter = 0;\nasync function handler(req, res) {\n  res.send(`Unique value is ${counter++}.`);\n}\n\nmodule.exports = {\n  handler,\n  match,\n};\n"
  },
  {
    "path": "infra/testing/server/static/integration.html",
    "content": "<html>\n  <head>\n    <title>Integration Test Harness</title>\n  </head>\n  <body>\n    <p>You need to manually register the service worker.</p>\n    <script src=\"/infra/testing/comlink/window-interface.js\"></script>\n    <script>\n      window.__test = {};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "infra/testing/server/template-data.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// We have to use global variables instead of local variables because\n// at the moment we're using `clear-require` to reset all modules between\n// tests, which means all local variables get reset, but globals persist.\nglobal.__uid = 0;\nglobal.__templateData = {\n  ENV: process.env.NODE_ENV,\n  uniqueID: () => {\n    return ++global.__uid;\n  },\n};\n\nconst get = () => {\n  return Object.assign({}, global.__templateData);\n};\n\nconst assign = (newData) => {\n  Object.assign(global.__templateData, newData);\n};\n\nmodule.exports = {get, assign};\n"
  },
  {
    "path": "infra/testing/server/templates/integration.html.njk",
    "content": "<!DOCTYPE html>\n<!--\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n-->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>{{ title }}</title>\n  <script>\n    {{ script | safe }}\n  </script>\n</head>\n<body>\n  {{ body }}\n</body>\n</html>\n"
  },
  {
    "path": "infra/testing/server/templates/sw-clients-claim.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('install', (event) => event.waitUntil(skipWaiting()));\naddEventListener('activate', (event) => event.waitUntil(\n  clients.claim().catch((error) => console.error(error))));\n"
  },
  {
    "path": "infra/testing/server/templates/sw-no-skip-waiting.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n"
  },
  {
    "path": "infra/testing/server/templates/sw-script-version.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('install', (event) => event.waitUntil(skipWaiting()));\naddEventListener('activate', (event) => event.waitUntil(clients.claim()));\n\naddEventListener('message', async (event) => {\n  // Assert the type and meta are correct.\n  if (event.data.type === 'GET_VERSION' &&\n      event.data.meta === 'workbox-window') {\n    event.ports[0].postMessage('{{ version }}');\n  }\n});\n"
  },
  {
    "path": "infra/testing/server/templates/sw-skip-waiting-deferred.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('install', (event) => {\n  const doneInstalling = new Promise((resolve) => {\n    setTimeout(() => {\n      skipWaiting();\n      resolve();\n    }, 500);\n  });\n  event.waitUntil(doneInstalling);\n});\n"
  },
  {
    "path": "infra/testing/server/templates/sw-skip-waiting-on-message.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('message', (event) => {\n  if (event.data.type === 'SKIP_WAITING') {\n    skipWaiting();\n  }\n});\n"
  },
  {
    "path": "infra/testing/server/templates/sw-skip-waiting.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('install', (event) => event.waitUntil(skipWaiting()));\n"
  },
  {
    "path": "infra/testing/server/templates/sw-window-ready.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// {{ version }}\n\naddEventListener('install', (event) => event.waitUntil(skipWaiting()));\naddEventListener('activate', (event) => event.waitUntil(clients.claim()));\n\naddEventListener('message', async (event) => {\n  // Assert the type and meta are correct.\n  if (event.data.type === 'WINDOW_READY' &&\n      event.data.meta === 'workbox-window') {\n    const windows = await clients.matchAll({\n      type: 'window',\n      includeUncontrolled: true,\n    });\n    for (const win of windows) {\n      win.postMessage({type: 'sw:message:ready'});\n    }\n  }\n});\n"
  },
  {
    "path": "infra/testing/server/templates/test-sw-runner.js.njk",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts(\n    '/node_modules/mocha/mocha.js',\n    '/node_modules/chai/chai.js',\n    '/node_modules/sinon/pkg/sinon-no-sourcemaps.js'\n);\n\n// Disable workbox logs in test mode.\nself.__WB_DISABLE_DEV_LOGS = true;\n\nself.expect = chai.expect;\n\n// TODO(philipwalton): Move these globals back into imports once the\n// conversion to run all unit tests in browsers is complete.\nself.expectError = async (func, errorName, finalCb) => {\n  let caughtError = null;\n  try {\n    const result = func();\n    if (result && result instanceof Promise) {\n      await result;\n    }\n  } catch (err) {\n    caughtError = err;\n  }\n\n  if (!caughtError) {\n    throw new Error('Expected error to be thrown but function ran correctly.');\n  }\n\n  if (caughtError.constructor.name !== 'WorkboxError') {\n    console.warn(`Unexpected error thrown.`, caughtError);\n  }\n\n  expect(caughtError.constructor.name).to.equal('WorkboxError');\n  expect(caughtError.name).to.equal(errorName);\n\n  if (finalCb) {\n    return finalCb(caughtError);\n  }\n};\nself.waitUntil = async (fn, retries = 20, intervalMillis = 50) => {\n  for (let i = 0; i < retries; i++) {\n    const result = await fn();\n    if (result) {\n      return;\n    }\n    await new Promise((resolve) => setTimeout(resolve, intervalMillis));\n  }\n  throw new Error(`${fn} didn't return true after ${retries} retries.`);\n};\n\n\nmocha.setup({\n  ui: 'bdd',\n  timeout: 5000,\n  reporter: null,\n});\n\naddEventListener('install', (event) => {\n  const testsComplete = new Promise((resolve, reject) => {\n    const reports = [];\n    const runner = mocha.run();\n\n    runner.on('fail', (test, err) => {\n      const flattenTitles = (test) => {\n        const titles = [test.title];\n        while (test.parent.title) {\n          titles.push(test.parent.title);\n          test = test.parent;\n        }\n        return titles.reverse().join(' ');\n      };\n\n      reports.push({\n        name: flattenTitles(test),\n        result: false,\n        message: err.message,\n        stack: err.stack,\n      });\n    });\n\n    runner.on('end', async () => {\n      const results = runner.stats;\n      results.reports = reports;\n\n      const windows = await clients.matchAll({\n        type: 'window',\n        includeUncontrolled: true,\n      });\n\n      for (const win of windows) {\n        win.postMessage({\n          type: 'MOCHA_RESULTS',\n          payload: results,\n        });\n      }\n\n      // Fail installation if the tests don't pass.\n      if (results.failures) {\n        reject();\n      } else {\n        resolve();\n      }\n    });\n  });\n\n  event.waitUntil(testsComplete);\n}, {once: true}); // Run once since `install` events are dispatched in tests.\n\nimportScripts('sw-bundle.js?filter={{ testFilter }}');\n"
  },
  {
    "path": "infra/testing/server/templates/test-sw.html.njk",
    "content": "<!DOCTYPE html>\n<!--\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n-->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Workbox SW Unit Tests</title>\n  <style>\n    body {\n      font: 1em/1.4 sans-serif;\n      margin: 1em;\n    }\n    #success {\n      color: hsl(150, 100%, 40%);\n      font-size: 1.33em;\n      font-weight: bold;\n    }\n    #errors {\n      color: hsl(0, 100%, 40%);\n    }\n  </style>\n</head>\n<body>\n  <h2>Service Worker Test Runner</h2>\n  <p>Running tests for the following modules:</p>\n  <ul>\n    {% block tests %}\n      {% for testModule in testModules %}\n        <li><code>{{ testModule }}\"</code></li>\n      {% endfor %}\n    {% endblock %}\n  </ul>\n  <p>Open the console to see progress...</p>\n  <p id=\"success\"></p>\n  <pre id=\"errors\"></pre>\n\n  <script type=\"module\">\n    import {Workbox} from '/__WORKBOX/buildFile/workbox-window.prod.mjs';\n\n    (async () => {\n      // // Randomize the test URL so every test run forces a new SW install.\n      const wb = new Workbox('test-sw-runner.js.njk?v={{ uniqueID() }}');\n\n      wb.addEventListener('message', (event) => {\n        if (event.data && event.data.type === 'MOCHA_RESULTS') {\n          self.mochaResults = event.data.payload;\n\n          if (self.mochaResults.failures === 0) {\n            const styles = [\n              `color: hsl(150, 100%, 40%)`,\n              `font-weight: bold`,\n              `font-size: 1.5em`,\n              `padding: .25em 0`,\n            ];\n            console.log('%cAll TESTS PASS!', styles.join(';'));\n            document.getElementById('success').textContent = 'ALL TESTS PASS!';\n          } else {\n            const errors = self.mochaResults.reports.map((report) => {\n              return `${report.name}\\n${report.stack}`;\n            }).join('\\n\\n');\n\n            document.getElementById('errors').textContent = errors\n          }\n        }\n      });\n\n      wb.register();\n    })();\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "infra/testing/server/templates/test-window.html.njk",
    "content": "<!DOCTYPE html>\n<!--\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n-->\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Workbox Unit Tests</title>\n\n  <!-- Tests can reference the environment, so it needs to be defined.  -->\n  <script>\n    self.process = self.process || {\n      env: {\n        NODE_ENV: '{{ ENV }}',\n      },\n    };\n  </script>\n\n  <link href=\"/node_modules/mocha/mocha.css\" rel=\"stylesheet\">\n  <script src=\"/node_modules/mocha/mocha.js\"></script>\n  <script src=\"/node_modules/sinon/pkg/sinon.js\"></script>\n  <script src=\"/node_modules/chai/chai.js\"></script>\n</head>\n<body>\n  <div id=\"mocha\"></div>\n\n  <script>\n  mocha.ui('bdd');\n  mocha.timeout(5000);\n\n  // Expose chai's expect globally.\n  self.expect = chai.expect;\n\n  self.expectError = async (func, errorName, finalCb) => {\n    let caughtError = null;\n    try {\n      const result = func();\n      if (result && result instanceof Promise) {\n        await result;\n      }\n    } catch (err) {\n      caughtError = err;\n    }\n\n    if (!caughtError) {\n      throw new Error('Expected error to be thrown but function ran correctly.');\n    }\n\n    if (caughtError.constructor.name !== 'WorkboxError') {\n      console.warn(`Unexpected error thrown.`, caughtError);\n    }\n\n    expect(caughtError.constructor.name).to.equal('WorkboxError');\n    expect(caughtError.name).to.equal(errorName);\n\n    if (finalCb) {\n      return finalCb(caughtError);\n    }\n  };\n  </script>\n\n  <script>\n  addEventListener('load', () => {\n    const runner = mocha.run();\n    const reports = [];\n\n    runner.on('fail', (test, err) => {\n      console.error(err.stack);\n\n      const flattenTitles = (test) => {\n        const titles = [test.title];\n        while (test.parent.title){\n          titles.push(test.parent.title);\n          test = test.parent;\n        }\n        return titles.reverse().join(' ');\n      };\n\n      reports.push({\n        name: flattenTitles(test),\n        result: false,\n        message: err.message,\n        stack: err.stack,\n      });\n    });\n\n    runner.on('end', () => {\n      window.mochaResults = runner.stats;\n      window.mochaResults.reports = reports;\n    });\n  }, {once: true});\n  </script>\n\n  {% block tests %}\n    {% for testModule in testModules %}\n      <script type=\"module\" src=\"{{ testModule }}\"></script>\n    {% endfor %}\n  {% endblock %}\n\n</body>\n</html>\n"
  },
  {
    "path": "infra/testing/validator/service-worker-runtime.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst assert = require('assert');\nconst chai = require('chai');\nconst chaiMatchPattern = require('chai-match-pattern');\nconst fse = require('fs-extra');\nconst makeServiceWorkerEnv = require('service-worker-mock');\nconst sinon = require('sinon');\nconst vm = require('vm');\n\nchai.use(chaiMatchPattern);\nconst {expect} = chai;\n\n// See https://github.com/chaijs/chai/issues/697\nfunction stringifyFunctionsInArray(arr) {\n  return arr.map((item) =>\n    typeof item === 'function' ? item.toString() : item,\n  );\n}\n\nfunction setupSpiesAndContextForInjectManifest() {\n  const cacheableResponsePluginSpy = sinon.spy();\n  class CacheableResponsePlugin {\n    constructor(...args) {\n      cacheableResponsePluginSpy(...args);\n    }\n  }\n\n  const cacheExpirationPluginSpy = sinon.spy();\n  class CacheExpirationPlugin {\n    constructor(...args) {\n      cacheExpirationPluginSpy(...args);\n    }\n  }\n\n  const importScripts = sinon.spy();\n\n  const addEventListener = sinon.stub();\n\n  const workbox = {\n    cacheableResponse: {\n      CacheableResponsePlugin: CacheableResponsePlugin,\n    },\n    expiration: {\n      CacheExpirationPlugin: CacheExpirationPlugin,\n    },\n    googleAnalytics: {\n      initialize: sinon.spy(),\n    },\n    precaching: {\n      // To make testing easier, hardcode this fake URL return value.\n      getCacheKeyForURL: sinon.stub().returns('/urlWithCacheKey'),\n      precacheAndRoute: sinon.spy(),\n      cleanupOutdatedCaches: sinon.spy(),\n    },\n    navigationPreload: {\n      enable: sinon.spy(),\n    },\n    routing: {\n      registerNavigationRoute: sinon.spy(),\n      registerRoute: sinon.spy(),\n    },\n    core: {\n      clientsClaim: sinon.spy(),\n      setCacheNameDetails: sinon.spy(),\n    },\n    setConfig: sinon.spy(),\n    // To make testing easier, return the name of the strategy.\n    strategies: {\n      CacheFirst: sinon.stub().returns({name: 'CacheFirst'}),\n      NetworkFirst: sinon.stub().returns({name: 'NetworkFirst'}),\n    },\n  };\n\n  const context = Object.assign(\n    {\n      importScripts,\n      workbox,\n    },\n    makeServiceWorkerEnv(),\n  );\n  context.self.addEventListener = addEventListener;\n  context.self.skipWaiting = sinon.spy();\n\n  const methodsToSpies = {\n    importScripts,\n    cacheableResponsePlugin: cacheableResponsePluginSpy,\n    cleanupOutdatedCaches: workbox.precaching.cleanupOutdatedCaches,\n    cacheExpirationPlugin: cacheExpirationPluginSpy,\n    CacheFirst: workbox.strategies.CacheFirst,\n    clientsClaim: workbox.core.clientsClaim,\n    getCacheKeyForURL: workbox.precaching.getCacheKeyForURL,\n    googleAnalyticsInitialize: workbox.googleAnalytics.initialize,\n    NetworkFirst: workbox.strategies.NetworkFirst,\n    navigationPreloadEnable: workbox.navigationPreload.enable,\n    precacheAndRoute: workbox.precaching.precacheAndRoute,\n    registerNavigationRoute: workbox.routing.registerNavigationRoute,\n    registerRoute: workbox.routing.registerRoute,\n    setCacheNameDetails: workbox.core.setCacheNameDetails,\n    setConfig: workbox.setConfig,\n    skipWaiting: context.self.skipWaiting,\n  };\n\n  return {addEventListener, context, methodsToSpies};\n}\n\nfunction setupSpiesAndContextForGenerateSW() {\n  const addEventListener = sinon.spy();\n  const importScripts = sinon.spy();\n\n  const workboxContext = {\n    importScripts,\n    CacheFirst: sinon.stub().returns({name: 'CacheFirst'}),\n    clientsClaim: sinon.spy(),\n    createHandlerBoundToURL: sinon.stub().returns('/urlWithCacheKey'),\n    enable: sinon.spy(),\n    initialize: sinon.spy(),\n    NavigationRoute: sinon.stub().returns({name: 'NavigationRoute'}),\n    NetworkFirst: sinon.stub().returns({name: 'NetworkFirst'}),\n    BroadcastUpdatePlugin: sinon.spy(),\n    CacheableResponsePlugin: sinon.spy(),\n    ExpirationPlugin: sinon.spy(),\n    PrecacheFallbackPlugin: sinon.spy(),\n    precacheAndRoute: sinon.spy(),\n    registerRoute: sinon.spy(),\n    setCacheNameDetails: sinon.spy(),\n    skipWaiting: sinon.spy(),\n  };\n\n  const context = Object.assign(\n    {\n      importScripts,\n      define: (scripts, callback) => {\n        importScripts(...scripts);\n        callback(workboxContext);\n      },\n    },\n    makeServiceWorkerEnv(),\n  );\n  context.self.addEventListener = addEventListener;\n  context.self.skipWaiting = workboxContext.skipWaiting;\n\n  return {addEventListener, context, methodsToSpies: workboxContext};\n}\n\nfunction validateMethodCalls({methodsToSpies, expectedMethodCalls, context}) {\n  for (const [method, spy] of Object.entries(methodsToSpies)) {\n    if (spy.called) {\n      const args = spy.args.map((arg) =>\n        Array.isArray(arg) ? stringifyFunctionsInArray(arg) : arg,\n      );\n\n      expect(args, `while testing method calls for ${method}`).to.matchPattern(\n        expectedMethodCalls[method],\n      );\n    } else {\n      expect(\n        expectedMethodCalls[method],\n        `while testing method calls for ${method}`,\n      ).to.be.undefined;\n    }\n  }\n\n  // Special validation for __WB_DISABLE_DEV_LOGS, which is a boolean\n  // assignment, so we can't stub it out.\n  if ('__WB_DISABLE_DEV_LOGS' in expectedMethodCalls) {\n    expect(context.self.__WB_DISABLE_DEV_LOGS).to.eql(\n      expectedMethodCalls.__WB_DISABLE_DEV_LOGS,\n      `__WB_DISABLE_DEV_LOGS`,\n    );\n  }\n}\n\n/**\n * This is used in the service worker generation tests to validate core\n * service worker functionality. While we don't fully emulate a real service\n * worker runtime, we set up spies/stubs to listen for certain method calls,\n * run the code in a VM sandbox, and then verify that the service worker\n * made the expected method calls.\n *\n * If any of the expected method calls + parameter combinations were not made,\n * this method will reject with a description of what failed.\n *\n * @param {string} [swFile]\n * @param {string} [swString]\n * @param {Object} expectedMethodCalls\n * @return {Promise} Resolves if all of the expected method calls were made.\n */\nmodule.exports = async ({\n  addEventListenerValidation,\n  entryPoint,\n  expectedMethodCalls,\n  swFile,\n  swString,\n}) => {\n  assert(\n    (swFile || swString) && !(swFile && swString),\n    `Set swFile or swString, but not both.`,\n  );\n\n  if (swFile) {\n    swString = await fse.readFile(swFile, 'utf8');\n  }\n\n  const {addEventListener, context, methodsToSpies} =\n    entryPoint === 'injectManifest'\n      ? setupSpiesAndContextForInjectManifest()\n      : setupSpiesAndContextForGenerateSW();\n\n  vm.runInNewContext(swString, context);\n\n  if (expectedMethodCalls) {\n    validateMethodCalls({methodsToSpies, expectedMethodCalls, context});\n  }\n\n  // Optionally check the usage of addEventListener().\n  if (addEventListenerValidation) {\n    addEventListenerValidation(addEventListener);\n  }\n};\n"
  },
  {
    "path": "infra/testing/wait-until.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nmodule.exports = async (fn, retries = 20, intervalMillis = 50) => {\n  for (let i = 0; i < retries; i++) {\n    const result = await fn();\n    if (result) {\n      return;\n    }\n    await new Promise((resolve) => setTimeout(resolve, intervalMillis));\n  }\n  throw new Error(`${fn} didn't return true after ${retries} retries.`);\n};\n"
  },
  {
    "path": "infra/testing/webdriver/IframeManager.js",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst PREFIX = 'iframe-';\nconst waitUntil = require('../wait-until');\n\nclass Client {\n  constructor(driver, id) {\n    this._driver = driver;\n    this._id = id;\n  }\n\n  async executeAsyncScript(code) {\n    const value = await this._driver.executeAsyncScript(\n      (id, code, cb) => {\n        const iframe = document.querySelector(`#${id}`);\n        Promise.resolve(iframe.contentWindow.eval(code))\n          .then((value) => cb(value))\n          .catch((err) => cb(err.toString()));\n      },\n      this._id,\n      code,\n    );\n\n    return value;\n  }\n\n  async wait(code) {\n    await waitUntil(() => this.executeAsyncScript(code));\n  }\n\n  remove() {\n    this._driver.executeScript((id) => {\n      const el = document.querySelector(`#${id}`);\n      document.body.removeChild(el);\n    }, this._id);\n  }\n}\n\n/**\n * Wraps methods in the underlying webdriver API to create, switch between,\n * and close tabs in a browser.\n */\nclass IframeManager {\n  /**\n   * @param {WebDriver} driver\n   *\n   * @private\n   */\n  constructor(driver) {\n    this._driver = driver;\n    this._clients = new Set();\n  }\n\n  async createIframeClient(url) {\n    const iframeId = await this._driver.executeAsyncScript(\n      (url, prefix, cb) => {\n        const el = document.createElement('iframe');\n        if (!('iframeCount' in window)) {\n          window.iframeCount = 1;\n        }\n        const id = `${prefix}${window.iframeCount++}`;\n        el.addEventListener('load', () => {\n          cb(id);\n        });\n        el.src = url;\n        el.id = id;\n        document.body.appendChild(el);\n      },\n      url,\n      PREFIX,\n    );\n\n    return new Client(this._driver, iframeId);\n  }\n}\n\nmodule.exports = {IframeManager};\n"
  },
  {
    "path": "infra/testing/webdriver/executeAsyncAndCatch.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Store local references of these globals.\nconst {webdriver} = global.__workbox;\n\n/**\n * Executes the passed function (and args) async and logs any errors that\n * occur. Errors are assumed to be passed to the callback as an object\n * with the `error` property.\n *\n * @param {...*} args\n * @return {*}\n */\nconst executeAsyncAndCatch = async (...args) => {\n  const result = await webdriver.executeAsyncScript(...args);\n\n  if (result && result.error) {\n    console.error(result.error);\n    throw new Error('Error executing async script');\n  }\n  return result;\n};\n\nmodule.exports = {executeAsyncAndCatch};\n"
  },
  {
    "path": "infra/testing/webdriver/runUnitTests.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst waitUntil = require('../wait-until');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\nconst runUnitTests = async (testPath) => {\n  await webdriver.get(server.getAddress() + testPath);\n\n  // Wait until the mocha tests are finished.\n  await waitUntil(\n    async () => {\n      return await webdriver.executeScript(() => self.mochaResults);\n    },\n    120,\n    500,\n  ); // Retry for 60 seconds.\n\n  const results = await webdriver.executeScript(() => self.mochaResults);\n\n  if (results.failures > 0) {\n    console.log(`\\n${results.failures} test failure(s):`);\n\n    for (const report of results.reports) {\n      console.log('');\n      console.log('Name     : ', report.name);\n      console.log('Message  : ', report.message);\n      console.log('Error    : ', report.stack);\n    }\n    console.log('');\n\n    throw new Error('Unit tests failed, see logs above for details');\n  }\n};\n\nmodule.exports = {runUnitTests};\n"
  },
  {
    "path": "infra/testing/webdriver/unregisterAllSWs.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {executeAsyncAndCatch} = require('./executeAsyncAndCatch');\n\n/**\n * Unregisters any active SWs so the next page load can start clean.\n * Note: a new page load is needed before controlling SWs stop being active.\n */\nconst unregisterAllSWs = async () => {\n  await executeAsyncAndCatch(async (cb) => {\n    try {\n      const regs = await navigator.serviceWorker.getRegistrations();\n      for (const reg of regs) {\n        await reg.unregister();\n      }\n      cb();\n    } catch (error) {\n      cb({error: error.stack});\n    }\n  });\n};\n\nmodule.exports = {unregisterAllSWs};\n"
  },
  {
    "path": "infra/testing/webdriver/windowLoaded.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {executeAsyncAndCatch} = require('./executeAsyncAndCatch');\n\n/**\n * Waits for the current window to load if it's not already loaded.\n */\nconst windowLoaded = async () => {\n  // Wait for the window to load, so the `Workbox` global is available.\n  await executeAsyncAndCatch(async (cb) => {\n    const loaded = () => {\n      if (!window.Workbox) {\n        cb({\n          error: `window.Workbox is undefined; location is ${location.href}`,\n        });\n      } else {\n        cb();\n      }\n    };\n\n    try {\n      if (document.readyState === 'complete') {\n        loaded();\n      } else {\n        addEventListener('load', () => loaded());\n      }\n    } catch (error) {\n      cb({error: error.stack});\n    }\n  });\n};\n\nmodule.exports = {windowLoaded};\n"
  },
  {
    "path": "infra/testing/webpack-build-check.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nfunction joinMessages(errorsOrWarnings) {\n  if (errorsOrWarnings[0].message) {\n    return errorsOrWarnings.map((item) => item.message).join('\\n');\n  } else {\n    return errorsOrWarnings.join('\\n');\n  }\n}\n\nmodule.exports = (webpackError, stats) => {\n  if (webpackError) {\n    throw new Error(webpackError.message);\n  }\n\n  const statsJson = stats.toJson('verbose');\n\n  if (statsJson.errors.length > 0) {\n    throw new Error(joinMessages(statsJson.errors));\n  }\n\n  if (statsJson.warnings.length > 0) {\n    throw new Error(joinMessages(statsJson.warnings));\n  }\n};\n"
  },
  {
    "path": "infra/type-overrides.d.ts",
    "content": "// TODO(philipwalton): remove these once this PR makes its way to a release:\n// https://github.com/microsoft/TSJS-lib-generator/pull/701\n\ninterface IDBIndex {\n  openCursor(\n    range?: IDBValidKey | IDBKeyRange | null,\n    direction?: IDBCursorDirection,\n  ): IDBRequest<IDBCursorWithValue | null>;\n  openKeyCursor(\n    range?: IDBValidKey | IDBKeyRange | null,\n    direction?: IDBCursorDirection,\n  ): IDBRequest<IDBCursor | null>;\n}\n\ninterface IDBObjectStore {\n  openCursor(\n    range?: IDBValidKey | IDBKeyRange | null,\n    direction?: IDBCursorDirection,\n  ): IDBRequest<IDBCursorWithValue | null>;\n  openKeyCursor(\n    query?: IDBValidKey | IDBKeyRange | null,\n    direction?: IDBCursorDirection,\n  ): IDBRequest<IDBCursor | null>;\n}\n\n// TODO(philipwalton): remove these once this PR makes its way to a release:\n// https://github.com/microsoft/TSJS-lib-generator/pull/740\n\ninterface CacheStorage {\n  match(\n    request: RequestInfo,\n    options?: MultiCacheQueryOptions,\n  ): Promise<Response | undefined>;\n}\n\n// TODO(philipwalton): remove these once this bug is fixed:\n// https://github.com/microsoft/TypeScript/issues/32435\n\ninterface Headers {\n  [Symbol.iterator](): IterableIterator<[string, string]>;\n  /**\n   * Returns an iterator allowing to go through all key/value pairs contained in this object.\n   */\n  entries(): IterableIterator<[string, string]>;\n  /**\n   * Returns an iterator allowing to go through all keys of the key/value pairs contained in this object.\n   */\n  keys(): IterableIterator<string>;\n  /**\n   * Returns an iterator allowing to go through all values of the key/value pairs contained in this object.\n   */\n  values(): IterableIterator<string>;\n}\n\ninterface URLSearchParams {\n  [Symbol.iterator](): IterableIterator<[string, string]>;\n  /**\n   * Returns an array of key, value pairs for every entry in the search params.\n   */\n  entries(): IterableIterator<[string, string]>;\n  /**\n   * Returns a list of keys in the search params.\n   */\n  keys(): IterableIterator<string>;\n  /**\n   * Returns a list of values in the search params.\n   */\n  values(): IterableIterator<string>;\n}\n"
  },
  {
    "path": "infra/utils/AsyncDebounce.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nclass AsyncDebounce {\n  constructor(fn) {\n    this._fn = fn;\n    this._needsRecall = false;\n  }\n\n  call() {\n    if (!this._promise) {\n      /* eslint-disable no-async-promise-executor */\n      this._promise = new Promise(async (resolve) => {\n        do {\n          this._needsRecall = false;\n          await this._fn.call();\n        } while (this._needsRecall !== false);\n\n        this._promise = null;\n        resolve();\n      });\n    } else {\n      this._needsRecall = true;\n    }\n    return this._promise;\n  }\n}\n\nmodule.exports = {AsyncDebounce};\n"
  },
  {
    "path": "infra/utils/log-helper.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst chalk = require('chalk');\n\nconst prefix = function () {\n  return chalk.inverse(`[Workbox]:`);\n};\n\nmodule.exports = {\n  highlight: chalk.bgCyan,\n\n  debug: (...args) => {\n    console.log(prefix(), chalk.dim(...args));\n  },\n\n  log: (...args) => {\n    console.log(prefix(), ...args);\n  },\n\n  warn: (...args) => {\n    console.warn(prefix(), chalk.yellow(...args));\n  },\n\n  error: (...args) => {\n    console.error('\\n');\n    console.error(prefix(), chalk.red(...args));\n    console.error('\\n');\n  },\n};\n"
  },
  {
    "path": "javascript.eslintrc.js",
    "content": "module.exports = {\n  extends: ['eslint:recommended', 'google'],\n  env: {\n    serviceworker: true,\n    browser: true,\n    node: true,\n    es6: true,\n  },\n  parserOptions: {\n    ecmaVersion: 2017,\n    sourceType: 'module',\n  },\n  globals: {\n    BroadcastChannel: false,\n    Comlink: false,\n    expect: true,\n    sinon: false,\n    SyncEvent: false,\n    workbox: false,\n    Workbox: true,\n    WorkboxSW: false,\n  },\n  rules: {\n    'indent': 0,\n    'jsdoc/check-types': 2,\n    'jsdoc/newline-after-description': 2,\n    'operator-linebreak': 0,\n    'space-before-function-paren': 0,\n    'max-len': [\n      2,\n      {\n        code: 80,\n        tabWidth: 2,\n        ignoreComments: true,\n        ignorePattern: '^\\\\s*import',\n        ignoreUrls: true,\n      },\n    ],\n  },\n  plugins: ['jsdoc'],\n  settings: {\n    jsdoc: {\n      preferredTypes: {\n        object: 'Object',\n      },\n    },\n  },\n  overrides: [\n    {\n      files: ['test/**/*.{js,mjs}'],\n      env: {\n        mocha: true,\n      },\n      globals: {\n        expectError: false,\n        waitUntil: false,\n        SW_NAMESPACES: false,\n      },\n      rules: {\n        'max-len': 0,\n        'require-jsdoc': 0,\n        'valid-jsdoc': 0,\n        'no-invalid-this': 0,\n      },\n    },\n    {\n      files: [\n        'infra/testing/webdriver/executeAsyncAndCatch.js',\n        'infra/testing/webdriver/runUnitTests.js',\n        'infra/utils/log-helper.js',\n        'packages/workbox-core/_private/logger.mjs',\n        'packages/workbox-sw/_default.mjs',\n        'packages/workbox-cli/src/lib/logger.js',\n        'test/workbox-window/integration/test.js',\n        'test/workbox-window/window/test-Workbox.mjs',\n      ],\n      rules: {\n        'no-console': 0,\n      },\n    },\n    {\n      files: ['infra/**/*.js'],\n      rules: {\n        'max-len': 0,\n      },\n    },\n    {\n      files: ['gulp-tasks/**/*.js', 'infra/**/*.js', 'test/**/*.js'],\n      rules: {\n        'camelcase': 0,\n        'require-jsdoc': 0,\n        'valid-jsdoc': 0,\n      },\n    },\n    {\n      files: ['infra/testing/**/*'],\n      env: {\n        mocha: true,\n      },\n    },\n    {\n      files: ['test/*/static/**/*.js'],\n      rules: {\n        'no-console': 0,\n        'no-unused-vars': 0,\n        'no-undef': 0,\n      },\n    },\n    {\n      files: ['packages/workbox-build/src/templates/**/*.js'],\n      rules: {\n        'max-len': 0,\n      },\n    },\n    {\n      files: ['packages/workbox-sw/**/*'],\n      globals: {\n        workbox: false,\n      },\n    },\n    {\n      files: ['infra/testing/env-it.js'],\n      rules: {\n        'no-invalid-this': 0,\n      },\n    },\n    {\n      files: [\n        'gulp-tasks/**/*.{mjs,js}',\n        'infra/**/*.{mjs,js}',\n        'packages/**/*.{mjs,js}',\n        'test/**/*.{mjs,js}',\n      ],\n      plugins: ['header'],\n      rules: {\n        'header/header': [2, 'block', {pattern: 'Copyright \\\\d{4} Google LLC'}],\n      },\n    },\n    {\n      files: ['demos/**/*.js'],\n      rules: {\n        'no-console': 0,\n      },\n    },\n  ],\n  // eslint can't parse some of these files.\n  ignorePatterns: ['**/wasm-project/**'],\n};\n"
  },
  {
    "path": "jsdoc.conf",
    "content": "{\n  \"source\": {\n    \"include\": [\n      \"packages\"\n    ],\n    \"exclude\": [\n      \"packages/workbox-cli\"\n    ],\n    \"includePattern\": \".+\\\\.(js(doc|x)?|mjs)$\",\n    \"excludePattern\": \"((^|\\\\/|\\\\\\\\)packages\\\\/.*\\\\/build(\\\\/|\\\\\\\\)|(^|\\\\/|\\\\\\\\)test(\\\\/|\\\\\\\\)|(^|\\\\/|\\\\\\\\)node_modules(\\\\/|\\\\\\\\)|(^|\\\\/|\\\\\\\\)demo(\\\\/|\\\\\\\\))\"\n  },\n  \"opts\": {\n    \"recurse\": true\n  }\n}\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"lerna\": \"5.6.2\",\n  \"packages\": [\"packages/*\"],\n  \"version\": \"7.4.0\"\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"bugs\": {\n    \"url\": \"https://github.com/GoogleChrome/workbox/issues\"\n  },\n  \"description\": \"Top-level scripts and dependencies for the workbox monorepo. Not meant to be published to npm.\",\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.24.1\",\n    \"@babel/core\": \"^7.24.4\",\n    \"@babel/preset-env\": \"^7.24.4\",\n    \"@google-cloud/storage\": \"^5.20.5\",\n    \"@octokit/rest\": \"^19.0.13\",\n    \"@rollup/plugin-babel\": \"^6.1.0\",\n    \"@rollup/plugin-commonjs\": \"^29.0.0\",\n    \"@rollup/plugin-multi-entry\": \"^7.1.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-replace\": \"^6.0.3\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@rollup/plugin-typescript\": \"^12.3.0\",\n    \"@types/babel__core\": \"^7.20.5\",\n    \"@types/babel__preset-env\": \"^7.9.6\",\n    \"@types/common-tags\": \"^1.8.4\",\n    \"@types/eslint\": \"^8.56.10\",\n    \"@types/estree\": \"^1.0.5\",\n    \"@types/fs-extra\": \"^9.0.13\",\n    \"@types/lodash\": \"^4.17.0\",\n    \"@types/node\": \"^20.14.8\",\n    \"@types/stringify-object\": \"^3.3.1\",\n    \"@typescript-eslint/eslint-plugin\": \"^5.62.0\",\n    \"@typescript-eslint/parser\": \"^5.62.0\",\n    \"acorn\": \"^8.11.3\",\n    \"babel-plugin-transform-async-to-promises\": \"^0.8.18\",\n    \"babylon\": \"^6.18.0\",\n    \"body-parser\": \"^1.20.2\",\n    \"bytes\": \"^3.1.2\",\n    \"camelcase\": \"^6.3.0\",\n    \"chai\": \"^4.4.1\",\n    \"chai-as-promised\": \"^7.1.1\",\n    \"chai-match-pattern\": \"^1.3.0\",\n    \"chalk\": \"^4.1.2\",\n    \"clear-module\": \"^4.1.2\",\n    \"comlink\": \"^4.4.1\",\n    \"common-tags\": \"^1.8.2\",\n    \"copy-webpack-plugin\": \"^6.4.1\",\n    \"coveralls\": \"^3.1.1\",\n    \"del\": \"^5.1.0\",\n    \"depcheck\": \"^1.4.7\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-google\": \"^0.14.0\",\n    \"eslint-plugin-header\": \"^3.1.1\",\n    \"eslint-plugin-jsdoc\": \"^30.7.13\",\n    \"execa\": \"^4.1.0\",\n    \"express\": \"^4.19.2\",\n    \"fs-extra\": \"^9.1.0\",\n    \"glob\": \"^11.0.1\",\n    \"globby\": \"^11.1.0\",\n    \"gulp\": \"^4.0.2\",\n    \"gzip-size\": \"^5.1.1\",\n    \"html-webpack-plugin-v4\": \"npm:html-webpack-plugin@^4.5.0\",\n    \"html-webpack-plugin-v5\": \"npm:html-webpack-plugin@^5.0.0-alpha.14\",\n    \"husky\": \"^7.0.4\",\n    \"idb\": \"^7.1.1\",\n    \"jsdoc\": \"^3.6.11\",\n    \"jsdoc-baseline\": \"^0.1.5\",\n    \"lerna\": \"^5.6.2\",\n    \"lint-staged\": \"^11.2.6\",\n    \"memory-fs\": \"^0.5.0\",\n    \"minimist\": \"^1.2.8\",\n    \"mocha\": \"^9.2.2\",\n    \"module-alias\": \"^2.2.3\",\n    \"nunjucks\": \"^3.2.4\",\n    \"nyc\": \"^15.1.0\",\n    \"prettier\": \"^2.8.8\",\n    \"proxyquire\": \"^2.1.3\",\n    \"require-dir\": \"^1.2.0\",\n    \"rollup\": \"^4.53.3\",\n    \"selenium-assistant\": \"^6.1.1\",\n    \"semver\": \"^7.6.0\",\n    \"serve-index\": \"^1.9.1\",\n    \"service-worker-mock\": \"^1.9.3\",\n    \"sinon\": \"^9.0.3\",\n    \"tempy\": \"^0.7.1\",\n    \"type-fest\": \"^3.13.1\",\n    \"typescript\": \"^4.9.5\",\n    \"typescript-json-schema\": \"^0.63.0\",\n    \"upath\": \"^1.2.0\",\n    \"webpack-v4\": \"npm:webpack@^4.44.2\",\n    \"webpack-v5\": \"npm:webpack@^5.91.0\",\n    \"worker-plugin\": \"^5.0.1\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"homepage\": \"https://github.com/GoogleChrome/workbox#readme\",\n  \"license\": \"MIT\",\n  \"lint-staged\": {\n    \"*\": \"prettier --ignore-unknown --write\"\n  },\n  \"name\": \"workbox\",\n  \"nyc\": {\n    \"exclude\": [\n      \"packages/workbox-core/models/messages/messages.mjs\"\n    ],\n    \"extension\": [\n      \".mjs\",\n      \".js\"\n    ],\n    \"include\": [\n      \"packages/**/*\"\n    ]\n  },\n  \"private\": true,\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/GoogleChrome/workbox.git\"\n  },\n  \"scripts\": {\n    \"build\": \"gulp build\",\n    \"gulp\": \"gulp\",\n    \"lint\": \"gulp lint\",\n    \"lint-staged\": \"lint-staged\",\n    \"prepare\": \"husky install\",\n    \"prettier\": \"prettier --ignore-unknown --write .\",\n    \"test_integration\": \"gulp test_integration\",\n    \"test_node\": \"gulp test_node\",\n    \"test_server\": \"gulp test_server\",\n    \"version\": \"gulp build && git add -A packages\"\n  },\n  \"version\": \"\"\n}\n"
  },
  {
    "path": "packages/workbox-background-sync/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-background-sync\n"
  },
  {
    "path": "packages/workbox-background-sync/package.json",
    "content": "{\n  \"name\": \"workbox-background-sync\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"Queues failed requests and uses the Background Sync API to replay them when the network is available\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"background\",\n    \"sync\",\n    \"workbox-plugin\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.backgroundSync\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"idb\": \"^7.0.1\",\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-background-sync/src/BackgroundSyncPlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport {Queue, QueueOptions} from './Queue.js';\nimport './_version.js';\n\n/**\n * A class implementing the `fetchDidFail` lifecycle callback. This makes it\n * easier to add failed requests to a background sync Queue.\n *\n * @memberof workbox-background-sync\n */\nclass BackgroundSyncPlugin implements WorkboxPlugin {\n  private readonly _queue: Queue;\n\n  /**\n   * @param {string} name See the {@link workbox-background-sync.Queue}\n   *     documentation for parameter details.\n   * @param {Object} [options] See the\n   *     {@link workbox-background-sync.Queue} documentation for\n   *     parameter details.\n   */\n  constructor(name: string, options?: QueueOptions) {\n    this._queue = new Queue(name, options);\n  }\n\n  /**\n   * @param {Object} options\n   * @param {Request} options.request\n   * @private\n   */\n  fetchDidFail: WorkboxPlugin['fetchDidFail'] = async ({request}) => {\n    await this._queue.pushRequest({request});\n  };\n}\n\nexport {BackgroundSyncPlugin};\n"
  },
  {
    "path": "packages/workbox-background-sync/src/Queue.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {QueueStore} from './lib/QueueStore.js';\nimport {QueueStoreEntry, UnidentifiedQueueStoreEntry} from './lib/QueueDb.js';\nimport {StorableRequest} from './lib/StorableRequest.js';\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\ninterface OnSyncCallbackOptions {\n  queue: Queue;\n}\n\ninterface OnSyncCallback {\n  (options: OnSyncCallbackOptions): void | Promise<void>;\n}\n\nexport interface QueueOptions {\n  forceSyncFallback?: boolean;\n  maxRetentionTime?: number;\n  onSync?: OnSyncCallback;\n}\n\ninterface QueueEntry {\n  request: Request;\n  timestamp?: number;\n  // We could use Record<string, unknown> as a type but that would be a breaking\n  // change, better do it in next major release.\n  // eslint-disable-next-line  @typescript-eslint/ban-types\n  metadata?: object;\n}\n\nconst TAG_PREFIX = 'workbox-background-sync';\nconst MAX_RETENTION_TIME = 60 * 24 * 7; // 7 days in minutes\n\nconst queueNames = new Set<string>();\n\n/**\n * Converts a QueueStore entry into the format exposed by Queue. This entails\n * converting the request data into a real request and omitting the `id` and\n * `queueName` properties.\n *\n * @param {UnidentifiedQueueStoreEntry} queueStoreEntry\n * @return {Queue}\n * @private\n */\nconst convertEntry = (\n  queueStoreEntry: UnidentifiedQueueStoreEntry,\n): QueueEntry => {\n  const queueEntry: QueueEntry = {\n    request: new StorableRequest(queueStoreEntry.requestData).toRequest(),\n    timestamp: queueStoreEntry.timestamp,\n  };\n  if (queueStoreEntry.metadata) {\n    queueEntry.metadata = queueStoreEntry.metadata;\n  }\n  return queueEntry;\n};\n\n/**\n * A class to manage storing failed requests in IndexedDB and retrying them\n * later. All parts of the storing and replaying process are observable via\n * callbacks.\n *\n * @memberof workbox-background-sync\n */\nclass Queue {\n  private readonly _name: string;\n  private readonly _onSync: OnSyncCallback;\n  private readonly _maxRetentionTime: number;\n  private readonly _queueStore: QueueStore;\n  private readonly _forceSyncFallback: boolean;\n  private _syncInProgress = false;\n  private _requestsAddedDuringSync = false;\n\n  /**\n   * Creates an instance of Queue with the given options\n   *\n   * @param {string} name The unique name for this queue. This name must be\n   *     unique as it's used to register sync events and store requests\n   *     in IndexedDB specific to this instance. An error will be thrown if\n   *     a duplicate name is detected.\n   * @param {Object} [options]\n   * @param {Function} [options.onSync] A function that gets invoked whenever\n   *     the 'sync' event fires. The function is invoked with an object\n   *     containing the `queue` property (referencing this instance), and you\n   *     can use the callback to customize the replay behavior of the queue.\n   *     When not set the `replayRequests()` method is called.\n   *     Note: if the replay fails after a sync event, make sure you throw an\n   *     error, so the browser knows to retry the sync event later.\n   * @param {number} [options.maxRetentionTime=7 days] The amount of time (in\n   *     minutes) a request may be retried. After this amount of time has\n   *     passed, the request will be deleted from the queue.\n   * @param {boolean} [options.forceSyncFallback=false] If `true`, instead\n   *     of attempting to use background sync events, always attempt to replay\n   *     queued request at service worker startup. Most folks will not need\n   *     this, unless you explicitly target a runtime like Electron that\n   *     exposes the interfaces for background sync, but does not have a working\n   *     implementation.\n   */\n  constructor(\n    name: string,\n    {forceSyncFallback, onSync, maxRetentionTime}: QueueOptions = {},\n  ) {\n    // Ensure the store name is not already being used\n    if (queueNames.has(name)) {\n      throw new WorkboxError('duplicate-queue-name', {name});\n    } else {\n      queueNames.add(name);\n    }\n\n    this._name = name;\n    this._onSync = onSync || this.replayRequests;\n    this._maxRetentionTime = maxRetentionTime || MAX_RETENTION_TIME;\n    this._forceSyncFallback = Boolean(forceSyncFallback);\n    this._queueStore = new QueueStore(this._name);\n\n    this._addSyncListener();\n  }\n\n  /**\n   * @return {string}\n   */\n  get name(): string {\n    return this._name;\n  }\n\n  /**\n   * Stores the passed request in IndexedDB (with its timestamp and any\n   * metadata) at the end of the queue.\n   *\n   * @param {QueueEntry} entry\n   * @param {Request} entry.request The request to store in the queue.\n   * @param {Object} [entry.metadata] Any metadata you want associated with the\n   *     stored request. When requests are replayed you'll have access to this\n   *     metadata object in case you need to modify the request beforehand.\n   * @param {number} [entry.timestamp] The timestamp (Epoch time in\n   *     milliseconds) when the request was first added to the queue. This is\n   *     used along with `maxRetentionTime` to remove outdated requests. In\n   *     general you don't need to set this value, as it's automatically set\n   *     for you (defaulting to `Date.now()`), but you can update it if you\n   *     don't want particular requests to expire.\n   */\n  async pushRequest(entry: QueueEntry): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(entry, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'Queue',\n        funcName: 'pushRequest',\n        paramName: 'entry',\n      });\n      assert!.isInstance(entry.request, Request, {\n        moduleName: 'workbox-background-sync',\n        className: 'Queue',\n        funcName: 'pushRequest',\n        paramName: 'entry.request',\n      });\n    }\n\n    await this._addRequest(entry, 'push');\n  }\n\n  /**\n   * Stores the passed request in IndexedDB (with its timestamp and any\n   * metadata) at the beginning of the queue.\n   *\n   * @param {QueueEntry} entry\n   * @param {Request} entry.request The request to store in the queue.\n   * @param {Object} [entry.metadata] Any metadata you want associated with the\n   *     stored request. When requests are replayed you'll have access to this\n   *     metadata object in case you need to modify the request beforehand.\n   * @param {number} [entry.timestamp] The timestamp (Epoch time in\n   *     milliseconds) when the request was first added to the queue. This is\n   *     used along with `maxRetentionTime` to remove outdated requests. In\n   *     general you don't need to set this value, as it's automatically set\n   *     for you (defaulting to `Date.now()`), but you can update it if you\n   *     don't want particular requests to expire.\n   */\n  async unshiftRequest(entry: QueueEntry): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(entry, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'Queue',\n        funcName: 'unshiftRequest',\n        paramName: 'entry',\n      });\n      assert!.isInstance(entry.request, Request, {\n        moduleName: 'workbox-background-sync',\n        className: 'Queue',\n        funcName: 'unshiftRequest',\n        paramName: 'entry.request',\n      });\n    }\n\n    await this._addRequest(entry, 'unshift');\n  }\n\n  /**\n   * Removes and returns the last request in the queue (along with its\n   * timestamp and any metadata). The returned object takes the form:\n   * `{request, timestamp, metadata}`.\n   *\n   * @return {Promise<QueueEntry | undefined>}\n   */\n  async popRequest(): Promise<QueueEntry | undefined> {\n    return this._removeRequest('pop');\n  }\n\n  /**\n   * Removes and returns the first request in the queue (along with its\n   * timestamp and any metadata). The returned object takes the form:\n   * `{request, timestamp, metadata}`.\n   *\n   * @return {Promise<QueueEntry | undefined>}\n   */\n  async shiftRequest(): Promise<QueueEntry | undefined> {\n    return this._removeRequest('shift');\n  }\n\n  /**\n   * Returns all the entries that have not expired (per `maxRetentionTime`).\n   * Any expired entries are removed from the queue.\n   *\n   * @return {Promise<Array<QueueEntry>>}\n   */\n  async getAll(): Promise<Array<QueueEntry>> {\n    const allEntries = await this._queueStore.getAll();\n    const now = Date.now();\n\n    const unexpiredEntries = [];\n    for (const entry of allEntries) {\n      // Ignore requests older than maxRetentionTime. Call this function\n      // recursively until an unexpired request is found.\n      const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;\n      if (now - entry.timestamp > maxRetentionTimeInMs) {\n        await this._queueStore.deleteEntry(entry.id);\n      } else {\n        unexpiredEntries.push(convertEntry(entry));\n      }\n    }\n\n    return unexpiredEntries;\n  }\n\n  /**\n   * Returns the number of entries present in the queue.\n   * Note that expired entries (per `maxRetentionTime`) are also included in this count.\n   *\n   * @return {Promise<number>}\n   */\n  async size(): Promise<number> {\n    return await this._queueStore.size();\n  }\n\n  /**\n   * Adds the entry to the QueueStore and registers for a sync event.\n   *\n   * @param {Object} entry\n   * @param {Request} entry.request\n   * @param {Object} [entry.metadata]\n   * @param {number} [entry.timestamp=Date.now()]\n   * @param {string} operation ('push' or 'unshift')\n   * @private\n   */\n  async _addRequest(\n    {request, metadata, timestamp = Date.now()}: QueueEntry,\n    operation: 'push' | 'unshift',\n  ): Promise<void> {\n    const storableRequest = await StorableRequest.fromRequest(request.clone());\n    const entry: UnidentifiedQueueStoreEntry = {\n      requestData: storableRequest.toObject(),\n      timestamp,\n    };\n\n    // Only include metadata if it's present.\n    if (metadata) {\n      entry.metadata = metadata;\n    }\n\n    switch (operation) {\n      case 'push':\n        await this._queueStore.pushEntry(entry);\n        break;\n      case 'unshift':\n        await this._queueStore.unshiftEntry(entry);\n        break;\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        `Request for '${getFriendlyURL(request.url)}' has ` +\n          `been added to background sync queue '${this._name}'.`,\n      );\n    }\n\n    // Don't register for a sync if we're in the middle of a sync. Instead,\n    // we wait until the sync is complete and call register if\n    // `this._requestsAddedDuringSync` is true.\n    if (this._syncInProgress) {\n      this._requestsAddedDuringSync = true;\n    } else {\n      await this.registerSync();\n    }\n  }\n\n  /**\n   * Removes and returns the first or last (depending on `operation`) entry\n   * from the QueueStore that's not older than the `maxRetentionTime`.\n   *\n   * @param {string} operation ('pop' or 'shift')\n   * @return {Object|undefined}\n   * @private\n   */\n  async _removeRequest(\n    operation: 'pop' | 'shift',\n  ): Promise<QueueEntry | undefined> {\n    const now = Date.now();\n    let entry: QueueStoreEntry | undefined;\n    switch (operation) {\n      case 'pop':\n        entry = await this._queueStore.popEntry();\n        break;\n      case 'shift':\n        entry = await this._queueStore.shiftEntry();\n        break;\n    }\n\n    if (entry) {\n      // Ignore requests older than maxRetentionTime. Call this function\n      // recursively until an unexpired request is found.\n      const maxRetentionTimeInMs = this._maxRetentionTime * 60 * 1000;\n      if (now - entry.timestamp > maxRetentionTimeInMs) {\n        return this._removeRequest(operation);\n      }\n\n      return convertEntry(entry);\n    } else {\n      return undefined;\n    }\n  }\n\n  /**\n   * Loops through each request in the queue and attempts to re-fetch it.\n   * If any request fails to re-fetch, it's put back in the same position in\n   * the queue (which registers a retry for the next sync event).\n   */\n  async replayRequests(): Promise<void> {\n    let entry;\n    while ((entry = await this.shiftRequest())) {\n      try {\n        await fetch(entry.request.clone());\n\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `Request for '${getFriendlyURL(entry.request.url)}' ` +\n              `has been replayed in queue '${this._name}'`,\n          );\n        }\n      } catch (error) {\n        await this.unshiftRequest(entry);\n\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `Request for '${getFriendlyURL(entry.request.url)}' ` +\n              `failed to replay, putting it back in queue '${this._name}'`,\n          );\n        }\n        throw new WorkboxError('queue-replay-failed', {name: this._name});\n      }\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        `All requests in queue '${this.name}' have successfully ` +\n          `replayed; the queue is now empty!`,\n      );\n    }\n  }\n\n  /**\n   * Registers a sync event with a tag unique to this instance.\n   */\n  async registerSync(): Promise<void> {\n    // See https://github.com/GoogleChrome/workbox/issues/2393\n    if ('sync' in self.registration && !this._forceSyncFallback) {\n      try {\n        await self.registration.sync.register(`${TAG_PREFIX}:${this._name}`);\n      } catch (err) {\n        // This means the registration failed for some reason, possibly due to\n        // the user disabling it.\n        if (process.env.NODE_ENV !== 'production') {\n          logger.warn(\n            `Unable to register sync event for '${this._name}'.`,\n            err,\n          );\n        }\n      }\n    }\n  }\n\n  /**\n   * In sync-supporting browsers, this adds a listener for the sync event.\n   * In non-sync-supporting browsers, or if _forceSyncFallback is true, this\n   * will retry the queue on service worker startup.\n   *\n   * @private\n   */\n  private _addSyncListener() {\n    // See https://github.com/GoogleChrome/workbox/issues/2393\n    if ('sync' in self.registration && !this._forceSyncFallback) {\n      self.addEventListener('sync', (event: SyncEvent) => {\n        if (event.tag === `${TAG_PREFIX}:${this._name}`) {\n          if (process.env.NODE_ENV !== 'production') {\n            logger.log(\n              `Background sync for tag '${event.tag}' ` + `has been received`,\n            );\n          }\n\n          const syncComplete = async () => {\n            this._syncInProgress = true;\n\n            let syncError;\n            try {\n              await this._onSync({queue: this});\n            } catch (error) {\n              if (error instanceof Error) {\n                syncError = error;\n\n                // Rethrow the error. Note: the logic in the finally clause\n                // will run before this gets rethrown.\n                throw syncError;\n              }\n            } finally {\n              // New items may have been added to the queue during the sync,\n              // so we need to register for a new sync if that's happened...\n              // Unless there was an error during the sync, in which\n              // case the browser will automatically retry later, as long\n              // as `event.lastChance` is not true.\n              if (\n                this._requestsAddedDuringSync &&\n                !(syncError && !event.lastChance)\n              ) {\n                await this.registerSync();\n              }\n\n              this._syncInProgress = false;\n              this._requestsAddedDuringSync = false;\n            }\n          };\n          event.waitUntil(syncComplete());\n        }\n      });\n    } else {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.log(`Background sync replaying without background sync event`);\n      }\n      // If the browser doesn't support background sync, or the developer has\n      // opted-in to not using it, retry every time the service worker starts up\n      // as a fallback.\n      void this._onSync({queue: this});\n    }\n  }\n\n  /**\n   * Returns the set of queue names. This is primarily used to reset the list\n   * of queue names in tests.\n   *\n   * @return {Set<string>}\n   *\n   * @private\n   */\n  static get _queueNames(): Set<string> {\n    return queueNames;\n  }\n}\n\nexport {Queue};\n"
  },
  {
    "path": "packages/workbox-background-sync/src/QueueStore.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n// This is a temporary workaround to expose something from ./lib/ via our\n// top-level public API.\n// TODO: In Workbox v7, move the actual code from ./lib/ to this file.\nexport {QueueStore} from './lib/QueueStore';\n"
  },
  {
    "path": "packages/workbox-background-sync/src/StorableRequest.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n// This is a temporary workaround to expose something from ./lib/ via our\n// top-level public API.\n// TODO: In Workbox v7, move the actual code from ./lib/ to this file.\nexport {StorableRequest} from './lib/StorableRequest';\n"
  },
  {
    "path": "packages/workbox-background-sync/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:background-sync:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-background-sync/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {BackgroundSyncPlugin} from './BackgroundSyncPlugin.js';\nimport {Queue, QueueOptions} from './Queue.js';\nimport {QueueStore} from './QueueStore.js';\nimport {StorableRequest} from './StorableRequest.js';\n\nimport './_version.js';\n\n// See https://github.com/GoogleChrome/workbox/issues/2946\ninterface SyncManager {\n  getTags(): Promise<string[]>;\n  register(tag: string): Promise<void>;\n}\n\ndeclare global {\n  interface ServiceWorkerRegistration {\n    readonly sync: SyncManager;\n  }\n\n  interface SyncEvent extends ExtendableEvent {\n    readonly lastChance: boolean;\n    readonly tag: string;\n  }\n\n  interface ServiceWorkerGlobalScopeEventMap {\n    sync: SyncEvent;\n  }\n}\n\n/**\n * @module workbox-background-sync\n */\nexport {BackgroundSyncPlugin, Queue, QueueOptions, QueueStore, StorableRequest};\n"
  },
  {
    "path": "packages/workbox-background-sync/src/lib/QueueDb.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {openDB, DBSchema, IDBPDatabase} from 'idb';\nimport {RequestData} from './StorableRequest.js';\nimport '../_version.js';\n\ninterface QueueDBSchema extends DBSchema {\n  requests: {\n    key: number;\n    value: QueueStoreEntry;\n    indexes: {queueName: string};\n  };\n}\n\nconst DB_VERSION = 3;\nconst DB_NAME = 'workbox-background-sync';\nconst REQUEST_OBJECT_STORE_NAME = 'requests';\nconst QUEUE_NAME_INDEX = 'queueName';\n\nexport interface UnidentifiedQueueStoreEntry {\n  requestData: RequestData;\n  timestamp: number;\n  id?: number;\n  queueName?: string;\n  // We could use Record<string, unknown> as a type but that would be a breaking\n  // change, better do it in next major release.\n  // eslint-disable-next-line  @typescript-eslint/ban-types\n  metadata?: object;\n}\n\nexport interface QueueStoreEntry extends UnidentifiedQueueStoreEntry {\n  id: number;\n}\n\n/**\n * A class to interact directly an IndexedDB created specifically to save and\n * retrieve QueueStoreEntries. This class encapsulates all the schema details\n * to store the representation of a Queue.\n *\n * @private\n */\n\nexport class QueueDb {\n  private _db: IDBPDatabase<QueueDBSchema> | null = null;\n\n  /**\n   * Add QueueStoreEntry to underlying db.\n   *\n   * @param {UnidentifiedQueueStoreEntry} entry\n   */\n  async addEntry(entry: UnidentifiedQueueStoreEntry): Promise<void> {\n    const db = await this.getDb();\n    const tx = db.transaction(REQUEST_OBJECT_STORE_NAME, 'readwrite', {\n      durability: 'relaxed',\n    });\n    await tx.store.add(entry as QueueStoreEntry);\n    await tx.done;\n  }\n\n  /**\n   * Returns the first entry id in the ObjectStore.\n   *\n   * @return {number | undefined}\n   */\n  async getFirstEntryId(): Promise<number | undefined> {\n    const db = await this.getDb();\n    const cursor = await db\n      .transaction(REQUEST_OBJECT_STORE_NAME)\n      .store.openCursor();\n    return cursor?.value.id;\n  }\n\n  /**\n   * Get all the entries filtered by index\n   *\n   * @param queueName\n   * @return {Promise<QueueStoreEntry[]>}\n   */\n  async getAllEntriesByQueueName(\n    queueName: string,\n  ): Promise<QueueStoreEntry[]> {\n    const db = await this.getDb();\n    const results = await db.getAllFromIndex(\n      REQUEST_OBJECT_STORE_NAME,\n      QUEUE_NAME_INDEX,\n      IDBKeyRange.only(queueName),\n    );\n    return results ? results : new Array<QueueStoreEntry>();\n  }\n\n  /**\n   * Returns the number of entries filtered by index\n   *\n   * @param queueName\n   * @return {Promise<number>}\n   */\n  async getEntryCountByQueueName(queueName: string): Promise<number> {\n    const db = await this.getDb();\n    return db.countFromIndex(\n      REQUEST_OBJECT_STORE_NAME,\n      QUEUE_NAME_INDEX,\n      IDBKeyRange.only(queueName),\n    );\n  }\n\n  /**\n   * Deletes a single entry by id.\n   *\n   * @param {number} id the id of the entry to be deleted\n   */\n  async deleteEntry(id: number): Promise<void> {\n    const db = await this.getDb();\n    await db.delete(REQUEST_OBJECT_STORE_NAME, id);\n  }\n\n  /**\n   *\n   * @param queueName\n   * @returns {Promise<QueueStoreEntry | undefined>}\n   */\n  async getFirstEntryByQueueName(\n    queueName: string,\n  ): Promise<QueueStoreEntry | undefined> {\n    return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), 'next');\n  }\n\n  /**\n   *\n   * @param queueName\n   * @returns {Promise<QueueStoreEntry | undefined>}\n   */\n  async getLastEntryByQueueName(\n    queueName: string,\n  ): Promise<QueueStoreEntry | undefined> {\n    return await this.getEndEntryFromIndex(IDBKeyRange.only(queueName), 'prev');\n  }\n\n  /**\n   * Returns either the first or the last entries, depending on direction.\n   * Filtered by index.\n   *\n   * @param {IDBCursorDirection} direction\n   * @param {IDBKeyRange} query\n   * @return {Promise<QueueStoreEntry | undefined>}\n   * @private\n   */\n  async getEndEntryFromIndex(\n    query: IDBKeyRange,\n    direction: IDBCursorDirection,\n  ): Promise<QueueStoreEntry | undefined> {\n    const db = await this.getDb();\n\n    const cursor = await db\n      .transaction(REQUEST_OBJECT_STORE_NAME)\n      .store.index(QUEUE_NAME_INDEX)\n      .openCursor(query, direction);\n    return cursor?.value;\n  }\n\n  /**\n   * Returns an open connection to the database.\n   *\n   * @private\n   */\n  private async getDb() {\n    if (!this._db) {\n      this._db = await openDB(DB_NAME, DB_VERSION, {\n        upgrade: this._upgradeDb,\n      });\n    }\n    return this._db;\n  }\n\n  /**\n   * Upgrades QueueDB\n   *\n   * @param {IDBPDatabase<QueueDBSchema>} db\n   * @param {number} oldVersion\n   * @private\n   */\n  private _upgradeDb(db: IDBPDatabase<QueueDBSchema>, oldVersion: number) {\n    if (oldVersion > 0 && oldVersion < DB_VERSION) {\n      if (db.objectStoreNames.contains(REQUEST_OBJECT_STORE_NAME)) {\n        db.deleteObjectStore(REQUEST_OBJECT_STORE_NAME);\n      }\n    }\n\n    const objStore = db.createObjectStore(REQUEST_OBJECT_STORE_NAME, {\n      autoIncrement: true,\n      keyPath: 'id',\n    });\n    objStore.createIndex(QUEUE_NAME_INDEX, QUEUE_NAME_INDEX, {unique: false});\n  }\n}\n"
  },
  {
    "path": "packages/workbox-background-sync/src/lib/QueueStore.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {\n  UnidentifiedQueueStoreEntry,\n  QueueStoreEntry,\n  QueueDb,\n} from './QueueDb.js';\nimport '../_version.js';\n\n/**\n * A class to manage storing requests from a Queue in IndexedDB,\n * indexed by their queue name for easier access.\n *\n * Most developers will not need to access this class directly;\n * it is exposed for advanced use cases.\n */\nexport class QueueStore {\n  private readonly _queueName: string;\n  private readonly _queueDb: QueueDb;\n\n  /**\n   * Associates this instance with a Queue instance, so entries added can be\n   * identified by their queue name.\n   *\n   * @param {string} queueName\n   */\n  constructor(queueName: string) {\n    this._queueName = queueName;\n    this._queueDb = new QueueDb();\n  }\n\n  /**\n   * Append an entry last in the queue.\n   *\n   * @param {Object} entry\n   * @param {Object} entry.requestData\n   * @param {number} [entry.timestamp]\n   * @param {Object} [entry.metadata]\n   */\n  async pushEntry(entry: UnidentifiedQueueStoreEntry): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(entry, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'QueueStore',\n        funcName: 'pushEntry',\n        paramName: 'entry',\n      });\n      assert!.isType(entry.requestData, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'QueueStore',\n        funcName: 'pushEntry',\n        paramName: 'entry.requestData',\n      });\n    }\n\n    // Don't specify an ID since one is automatically generated.\n    delete entry.id;\n    entry.queueName = this._queueName;\n\n    await this._queueDb.addEntry(entry);\n  }\n\n  /**\n   * Prepend an entry first in the queue.\n   *\n   * @param {Object} entry\n   * @param {Object} entry.requestData\n   * @param {number} [entry.timestamp]\n   * @param {Object} [entry.metadata]\n   */\n  async unshiftEntry(entry: UnidentifiedQueueStoreEntry): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(entry, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'QueueStore',\n        funcName: 'unshiftEntry',\n        paramName: 'entry',\n      });\n      assert!.isType(entry.requestData, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'QueueStore',\n        funcName: 'unshiftEntry',\n        paramName: 'entry.requestData',\n      });\n    }\n\n    const firstId = await this._queueDb.getFirstEntryId();\n\n    if (firstId) {\n      // Pick an ID one less than the lowest ID in the object store.\n      entry.id = firstId - 1;\n    } else {\n      // Otherwise let the auto-incrementor assign the ID.\n      delete entry.id;\n    }\n    entry.queueName = this._queueName;\n\n    await this._queueDb.addEntry(entry);\n  }\n\n  /**\n   * Removes and returns the last entry in the queue matching the `queueName`.\n   *\n   * @return {Promise<QueueStoreEntry|undefined>}\n   */\n  async popEntry(): Promise<QueueStoreEntry | undefined> {\n    return this._removeEntry(\n      await this._queueDb.getLastEntryByQueueName(this._queueName),\n    );\n  }\n\n  /**\n   * Removes and returns the first entry in the queue matching the `queueName`.\n   *\n   * @return {Promise<QueueStoreEntry|undefined>}\n   */\n  async shiftEntry(): Promise<QueueStoreEntry | undefined> {\n    return this._removeEntry(\n      await this._queueDb.getFirstEntryByQueueName(this._queueName),\n    );\n  }\n\n  /**\n   * Returns all entries in the store matching the `queueName`.\n   *\n   * @param {Object} options See {@link workbox-background-sync.Queue~getAll}\n   * @return {Promise<Array<Object>>}\n   */\n  async getAll(): Promise<QueueStoreEntry[]> {\n    return await this._queueDb.getAllEntriesByQueueName(this._queueName);\n  }\n\n  /**\n   * Returns the number of entries in the store matching the `queueName`.\n   *\n   * @param {Object} options See {@link workbox-background-sync.Queue~size}\n   * @return {Promise<number>}\n   */\n  async size(): Promise<number> {\n    return await this._queueDb.getEntryCountByQueueName(this._queueName);\n  }\n\n  /**\n   * Deletes the entry for the given ID.\n   *\n   * WARNING: this method does not ensure the deleted entry belongs to this\n   * queue (i.e. matches the `queueName`). But this limitation is acceptable\n   * as this class is not publicly exposed. An additional check would make\n   * this method slower than it needs to be.\n   *\n   * @param {number} id\n   */\n  async deleteEntry(id: number): Promise<void> {\n    await this._queueDb.deleteEntry(id);\n  }\n\n  /**\n   * Removes and returns the first or last entry in the queue (based on the\n   * `direction` argument) matching the `queueName`.\n   *\n   * @return {Promise<QueueStoreEntry|undefined>}\n   * @private\n   */\n  async _removeEntry(\n    entry?: QueueStoreEntry,\n  ): Promise<QueueStoreEntry | undefined> {\n    if (entry) {\n      await this.deleteEntry(entry.id);\n    }\n    return entry;\n  }\n}\n"
  },
  {
    "path": "packages/workbox-background-sync/src/lib/StorableRequest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {MapLikeObject} from 'workbox-core/types.js';\nimport '../_version.js';\n\ntype SerializableProperties =\n  | 'method'\n  | 'referrer'\n  | 'referrerPolicy'\n  | 'mode'\n  | 'credentials'\n  | 'cache'\n  | 'redirect'\n  | 'integrity'\n  | 'keepalive';\n\nconst serializableProperties: SerializableProperties[] = [\n  'method',\n  'referrer',\n  'referrerPolicy',\n  'mode',\n  'credentials',\n  'cache',\n  'redirect',\n  'integrity',\n  'keepalive',\n];\n\nexport interface RequestData extends MapLikeObject {\n  url: string;\n  headers: MapLikeObject;\n  body?: ArrayBuffer;\n}\n\n/**\n * A class to make it easier to serialize and de-serialize requests so they\n * can be stored in IndexedDB.\n *\n * Most developers will not need to access this class directly;\n * it is exposed for advanced use cases.\n */\nclass StorableRequest {\n  private readonly _requestData: RequestData;\n\n  /**\n   * Converts a Request object to a plain object that can be structured\n   * cloned or JSON-stringified.\n   *\n   * @param {Request} request\n   * @return {Promise<StorableRequest>}\n   */\n  static async fromRequest(request: Request): Promise<StorableRequest> {\n    const requestData: RequestData = {\n      url: request.url,\n      headers: {},\n    };\n\n    // Set the body if present.\n    if (request.method !== 'GET') {\n      // Use ArrayBuffer to support non-text request bodies.\n      // NOTE: we can't use Blobs becuse Safari doesn't support storing\n      // Blobs in IndexedDB in some cases:\n      // https://github.com/dfahlander/Dexie.js/issues/618#issuecomment-398348457\n      requestData.body = await request.clone().arrayBuffer();\n    }\n\n    // Convert the headers from an iterable to an object.\n    for (const [key, value] of request.headers.entries()) {\n      requestData.headers[key] = value;\n    }\n\n    // Add all other serializable request properties\n    for (const prop of serializableProperties) {\n      if (request[prop] !== undefined) {\n        requestData[prop] = request[prop];\n      }\n    }\n\n    return new StorableRequest(requestData);\n  }\n\n  /**\n   * Accepts an object of request data that can be used to construct a\n   * `Request` but can also be stored in IndexedDB.\n   *\n   * @param {Object} requestData An object of request data that includes the\n   *     `url` plus any relevant properties of\n   *     [requestInit]{@link https://fetch.spec.whatwg.org/#requestinit}.\n   */\n  constructor(requestData: RequestData) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(requestData, 'object', {\n        moduleName: 'workbox-background-sync',\n        className: 'StorableRequest',\n        funcName: 'constructor',\n        paramName: 'requestData',\n      });\n      assert!.isType(requestData.url, 'string', {\n        moduleName: 'workbox-background-sync',\n        className: 'StorableRequest',\n        funcName: 'constructor',\n        paramName: 'requestData.url',\n      });\n    }\n\n    // If the request's mode is `navigate`, convert it to `same-origin` since\n    // navigation requests can't be constructed via script.\n    if (requestData['mode'] === 'navigate') {\n      requestData['mode'] = 'same-origin';\n    }\n\n    this._requestData = requestData;\n  }\n\n  /**\n   * Returns a deep clone of the instances `_requestData` object.\n   *\n   * @return {Object}\n   */\n  toObject(): RequestData {\n    const requestData = Object.assign({}, this._requestData);\n    requestData.headers = Object.assign({}, this._requestData.headers);\n    if (requestData.body) {\n      requestData.body = requestData.body.slice(0);\n    }\n\n    return requestData;\n  }\n\n  /**\n   * Converts this instance to a Request.\n   *\n   * @return {Request}\n   */\n  toRequest(): Request {\n    return new Request(this._requestData.url, this._requestData);\n  }\n\n  /**\n   * Creates and returns a deep clone of the instance.\n   *\n   * @return {StorableRequest}\n   */\n  clone(): StorableRequest {\n    return new StorableRequest(this.toObject());\n  }\n}\n\nexport {StorableRequest};\n"
  },
  {
    "path": "packages/workbox-background-sync/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-broadcast-update/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-broadcast-update\n"
  },
  {
    "path": "packages/workbox-broadcast-update/package.json",
    "content": "{\n  \"name\": \"workbox-broadcast-update\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A service worker helper library that uses the Broadcast Channel API to announce when a cached response has updated\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"workbox-plugin\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.broadcastUpdate\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-broadcast-update/src/BroadcastCacheUpdate.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {timeout} from 'workbox-core/_private/timeout.js';\nimport {resultingClientExists} from 'workbox-core/_private/resultingClientExists.js';\nimport {CacheDidUpdateCallbackParam} from 'workbox-core/types.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {responsesAreSame} from './responsesAreSame.js';\nimport {\n  CACHE_UPDATED_MESSAGE_META,\n  CACHE_UPDATED_MESSAGE_TYPE,\n  DEFAULT_HEADERS_TO_CHECK,\n  NOTIFY_ALL_CLIENTS,\n} from './utils/constants.js';\n\nimport './_version.js';\n\n// UA-sniff Safari: https://stackoverflow.com/questions/7944460/detect-safari-browser\n// TODO(philipwalton): remove once this Safari bug fix has been released.\n// https://bugs.webkit.org/show_bug.cgi?id=201169\nconst isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\nexport interface BroadcastCacheUpdateOptions {\n  headersToCheck?: string[];\n  generatePayload?: (\n    options: CacheDidUpdateCallbackParam,\n  ) => Record<string, any>;\n  notifyAllClients?: boolean;\n}\n\n/**\n * Generates the default payload used in update messages. By default the\n * payload includes the `cacheName` and `updatedURL` fields.\n *\n * @return Object\n * @private\n */\nfunction defaultPayloadGenerator(\n  data: CacheDidUpdateCallbackParam,\n): Record<string, any> {\n  return {\n    cacheName: data.cacheName,\n    updatedURL: data.request.url,\n  };\n}\n\n/**\n * Uses the `postMessage()` API to inform any open windows/tabs when a cached\n * response has been updated.\n *\n * For efficiency's sake, the underlying response bodies are not compared;\n * only specific response headers are checked.\n *\n * @memberof workbox-broadcast-update\n */\nclass BroadcastCacheUpdate {\n  private readonly _headersToCheck: string[];\n  private readonly _generatePayload: (\n    options: CacheDidUpdateCallbackParam,\n  ) => Record<string, any>;\n  private readonly _notifyAllClients: boolean;\n\n  /**\n   * Construct a BroadcastCacheUpdate instance with a specific `channelName` to\n   * broadcast messages on\n   *\n   * @param {Object} [options]\n   * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n   *     A list of headers that will be used to determine whether the responses\n   *     differ.\n   * @param {string} [options.generatePayload] A function whose return value\n   *     will be used as the `payload` field in any cache update messages sent\n   *     to the window clients.\n   * @param {boolean} [options.notifyAllClients=true] If true (the default) then\n   *     all open clients will receive a message. If false, then only the client\n   *     that make the original request will be notified of the update.\n   */\n  constructor({\n    generatePayload,\n    headersToCheck,\n    notifyAllClients,\n  }: BroadcastCacheUpdateOptions = {}) {\n    this._headersToCheck = headersToCheck || DEFAULT_HEADERS_TO_CHECK;\n    this._generatePayload = generatePayload || defaultPayloadGenerator;\n    this._notifyAllClients = notifyAllClients ?? NOTIFY_ALL_CLIENTS;\n  }\n\n  /**\n   * Compares two [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n   * and sends a message (via `postMessage()`) to all window clients if the\n   * responses differ. Neither of the Responses can be\n   * [opaque](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).\n   *\n   * The message that's posted has the following format (where `payload` can\n   * be customized via the `generatePayload` option the instance is created\n   * with):\n   *\n   * ```\n   * {\n   *   type: 'CACHE_UPDATED',\n   *   meta: 'workbox-broadcast-update',\n   *   payload: {\n   *     cacheName: 'the-cache-name',\n   *     updatedURL: 'https://example.com/'\n   *   }\n   * }\n   * ```\n   *\n   * @param {Object} options\n   * @param {Response} [options.oldResponse] Cached response to compare.\n   * @param {Response} options.newResponse Possibly updated response to compare.\n   * @param {Request} options.request The request.\n   * @param {string} options.cacheName Name of the cache the responses belong\n   *     to. This is included in the broadcast message.\n   * @param {Event} options.event event The event that triggered\n   *     this possible cache update.\n   * @return {Promise} Resolves once the update is sent.\n   */\n  async notifyIfUpdated(options: CacheDidUpdateCallbackParam): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(options.cacheName, 'string', {\n        moduleName: 'workbox-broadcast-update',\n        className: 'BroadcastCacheUpdate',\n        funcName: 'notifyIfUpdated',\n        paramName: 'cacheName',\n      });\n      assert!.isInstance(options.newResponse, Response, {\n        moduleName: 'workbox-broadcast-update',\n        className: 'BroadcastCacheUpdate',\n        funcName: 'notifyIfUpdated',\n        paramName: 'newResponse',\n      });\n      assert!.isInstance(options.request, Request, {\n        moduleName: 'workbox-broadcast-update',\n        className: 'BroadcastCacheUpdate',\n        funcName: 'notifyIfUpdated',\n        paramName: 'request',\n      });\n    }\n\n    // Without two responses there is nothing to compare.\n    if (!options.oldResponse) {\n      return;\n    }\n\n    if (\n      !responsesAreSame(\n        options.oldResponse,\n        options.newResponse,\n        this._headersToCheck,\n      )\n    ) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.log(\n          `Newer response found (and cached) for:`,\n          options.request.url,\n        );\n      }\n\n      const messageData = {\n        type: CACHE_UPDATED_MESSAGE_TYPE,\n        meta: CACHE_UPDATED_MESSAGE_META,\n        payload: this._generatePayload(options),\n      };\n\n      // For navigation requests, wait until the new window client exists\n      // before sending the message\n      if (options.request.mode === 'navigate') {\n        let resultingClientId: string | undefined;\n        if (options.event instanceof FetchEvent) {\n          resultingClientId = options.event.resultingClientId;\n        }\n\n        const resultingWin = await resultingClientExists(resultingClientId);\n\n        // Safari does not currently implement postMessage buffering and\n        // there's no good way to feature detect that, so to increase the\n        // chances of the message being delivered in Safari, we add a timeout.\n        // We also do this if `resultingClientExists()` didn't return a client,\n        // which means it timed out, so it's worth waiting a bit longer.\n        if (!resultingWin || isSafari) {\n          // 3500 is chosen because (according to CrUX data) 80% of mobile\n          // websites hit the DOMContentLoaded event in less than 3.5 seconds.\n          // And presumably sites implementing service worker are on the\n          // higher end of the performance spectrum.\n          await timeout(3500);\n        }\n      }\n\n      if (this._notifyAllClients) {\n        const windows = await self.clients.matchAll({type: 'window'});\n        for (const win of windows) {\n          win.postMessage(messageData);\n        }\n      } else {\n        // See https://github.com/GoogleChrome/workbox/issues/2895\n        if (options.event instanceof FetchEvent) {\n          const client = await self.clients.get(options.event.clientId);\n          client?.postMessage(messageData);\n        }\n      }\n    }\n  }\n}\n\nexport {BroadcastCacheUpdate};\n"
  },
  {
    "path": "packages/workbox-broadcast-update/src/BroadcastUpdatePlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {dontWaitFor} from 'workbox-core/_private/dontWaitFor.js';\nimport {WorkboxPlugin} from 'workbox-core/types.js';\n\nimport {\n  BroadcastCacheUpdate,\n  BroadcastCacheUpdateOptions,\n} from './BroadcastCacheUpdate.js';\n\nimport './_version.js';\n\n/**\n * This plugin will automatically broadcast a message whenever a cached response\n * is updated.\n *\n * @memberof workbox-broadcast-update\n */\nclass BroadcastUpdatePlugin implements WorkboxPlugin {\n  private readonly _broadcastUpdate: BroadcastCacheUpdate;\n\n  /**\n   * Construct a {@link workbox-broadcast-update.BroadcastUpdate} instance with\n   * the passed options and calls its `notifyIfUpdated` method whenever the\n   * plugin's `cacheDidUpdate` callback is invoked.\n   *\n   * @param {Object} [options]\n   * @param {Array<string>} [options.headersToCheck=['content-length', 'etag', 'last-modified']]\n   *     A list of headers that will be used to determine whether the responses\n   *     differ.\n   * @param {string} [options.generatePayload] A function whose return value\n   *     will be used as the `payload` field in any cache update messages sent\n   *     to the window clients.\n   */\n  constructor(options?: BroadcastCacheUpdateOptions) {\n    this._broadcastUpdate = new BroadcastCacheUpdate(options);\n  }\n\n  /**\n   * A \"lifecycle\" callback that will be triggered automatically by the\n   * `workbox-sw` and `workbox-runtime-caching` handlers when an entry is\n   * added to a cache.\n   *\n   * @private\n   * @param {Object} options The input object to this function.\n   * @param {string} options.cacheName Name of the cache being updated.\n   * @param {Response} [options.oldResponse] The previous cached value, if any.\n   * @param {Response} options.newResponse The new value in the cache.\n   * @param {Request} options.request The request that triggered the update.\n   * @param {Request} options.event The event that triggered the update.\n   */\n  cacheDidUpdate: WorkboxPlugin['cacheDidUpdate'] = async (options) => {\n    dontWaitFor(this._broadcastUpdate.notifyIfUpdated(options));\n  };\n}\n\nexport {BroadcastUpdatePlugin};\n"
  },
  {
    "path": "packages/workbox-broadcast-update/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:broadcast-update:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-broadcast-update/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {\n  BroadcastCacheUpdate,\n  BroadcastCacheUpdateOptions,\n} from './BroadcastCacheUpdate.js';\nimport {BroadcastUpdatePlugin} from './BroadcastUpdatePlugin.js';\nimport {responsesAreSame} from './responsesAreSame.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-broadcast-update\n */\n\nexport {\n  BroadcastCacheUpdate,\n  BroadcastCacheUpdateOptions,\n  BroadcastUpdatePlugin,\n  responsesAreSame,\n};\n"
  },
  {
    "path": "packages/workbox-broadcast-update/src/responsesAreSame.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport './_version.js';\n\n/**\n * Given two `Response's`, compares several header values to see if they are\n * the same or not.\n *\n * @param {Response} firstResponse\n * @param {Response} secondResponse\n * @param {Array<string>} headersToCheck\n * @return {boolean}\n *\n * @memberof workbox-broadcast-update\n */\nconst responsesAreSame = (\n  firstResponse: Response,\n  secondResponse: Response,\n  headersToCheck: string[],\n): boolean => {\n  if (process.env.NODE_ENV !== 'production') {\n    if (\n      !(firstResponse instanceof Response && secondResponse instanceof Response)\n    ) {\n      throw new WorkboxError('invalid-responses-are-same-args');\n    }\n  }\n\n  const atLeastOneHeaderAvailable = headersToCheck.some((header) => {\n    return (\n      firstResponse.headers.has(header) && secondResponse.headers.has(header)\n    );\n  });\n\n  if (!atLeastOneHeaderAvailable) {\n    if (process.env.NODE_ENV !== 'production') {\n      logger.warn(\n        `Unable to determine where the response has been updated ` +\n          `because none of the headers that would be checked are present.`,\n      );\n      logger.debug(\n        `Attempting to compare the following: `,\n        firstResponse,\n        secondResponse,\n        headersToCheck,\n      );\n    }\n\n    // Just return true, indicating the that responses are the same, since we\n    // can't determine otherwise.\n    return true;\n  }\n\n  return headersToCheck.every((header) => {\n    const headerStateComparison =\n      firstResponse.headers.has(header) === secondResponse.headers.has(header);\n    const headerValueComparison =\n      firstResponse.headers.get(header) === secondResponse.headers.get(header);\n\n    return headerStateComparison && headerValueComparison;\n  });\n};\n\nexport {responsesAreSame};\n"
  },
  {
    "path": "packages/workbox-broadcast-update/src/utils/constants.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nexport const CACHE_UPDATED_MESSAGE_TYPE = 'CACHE_UPDATED';\nexport const CACHE_UPDATED_MESSAGE_META = 'workbox-broadcast-update';\nexport const NOTIFY_ALL_CLIENTS = true;\nexport const DEFAULT_HEADERS_TO_CHECK: string[] = [\n  'content-length',\n  'etag',\n  'last-modified',\n];\n"
  },
  {
    "path": "packages/workbox-broadcast-update/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-build/.ncurc.js",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// We use `npx npm-check-updates` to find updates to dependencies.\n// Some dependencies have breaking changes that we can't resolve.\n// This config file excludes those dependencies from the checks\n// until we're able to remediate our code to deal with them.\nmodule.exports = {\n  reject: [\n    // joi v16 is the last release to support Node v10:\n    // https://github.com/sideway/joi/issues/2262\n    '@hapi/joi',\n  ],\n};\n"
  },
  {
    "path": "packages/workbox-build/README.md",
    "content": "This module's documentation can be found at https://developer.chrome.com/docs/workbox/modules/workbox-build\n"
  },
  {
    "path": "packages/workbox-build/package.json",
    "content": "{\n  \"name\": \"workbox-build\",\n  \"version\": \"7.4.0\",\n  \"description\": \"A module that integrates into your build process, helping you generate a manifest of local files that workbox-sw should precache.\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"caching\",\n    \"fetch requests\",\n    \"offline\",\n    \"file manifest\"\n  ],\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/GoogleChrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"dependencies\": {\n    \"@apideck/better-ajv-errors\": \"^0.3.1\",\n    \"@babel/core\": \"^7.24.4\",\n    \"@babel/preset-env\": \"^7.11.0\",\n    \"@babel/runtime\": \"^7.11.2\",\n    \"@rollup/plugin-babel\": \"^6.1.0\",\n    \"@rollup/plugin-node-resolve\": \"^16.0.3\",\n    \"@rollup/plugin-replace\": \"^6.0.3\",\n    \"@rollup/plugin-terser\": \"^0.4.4\",\n    \"@trickfilm400/rollup-plugin-off-main-thread\": \"^3.0.0-pre1\",\n    \"ajv\": \"^8.6.0\",\n    \"common-tags\": \"^1.8.0\",\n    \"fast-json-stable-stringify\": \"^2.1.0\",\n    \"fs-extra\": \"^9.0.1\",\n    \"glob\": \"^11.0.1\",\n    \"lodash\": \"^4.17.20\",\n    \"pretty-bytes\": \"^5.3.0\",\n    \"rollup\": \"^4.53.3\",\n    \"source-map\": \"^0.8.0-beta.0\",\n    \"stringify-object\": \"^3.3.0\",\n    \"strip-comments\": \"^2.0.1\",\n    \"tempy\": \"^0.6.0\",\n    \"upath\": \"^1.2.0\",\n    \"workbox-background-sync\": \"7.4.0\",\n    \"workbox-broadcast-update\": \"7.4.0\",\n    \"workbox-cacheable-response\": \"7.4.0\",\n    \"workbox-core\": \"7.4.0\",\n    \"workbox-expiration\": \"7.4.0\",\n    \"workbox-google-analytics\": \"7.4.0\",\n    \"workbox-navigation-preload\": \"7.4.0\",\n    \"workbox-precaching\": \"7.4.0\",\n    \"workbox-range-requests\": \"7.4.0\",\n    \"workbox-recipes\": \"7.4.0\",\n    \"workbox-routing\": \"7.4.0\",\n    \"workbox-strategies\": \"7.4.0\",\n    \"workbox-streams\": \"7.4.0\",\n    \"workbox-sw\": \"7.4.0\",\n    \"workbox-window\": \"7.4.0\"\n  },\n  \"main\": \"build/index.js\",\n  \"workbox\": {\n    \"packageType\": \"node_ts\"\n  },\n  \"types\": \"build/index.d.ts\",\n  \"devDependencies\": {\n    \"@types/node\": \"^20.14.8\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/_types.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.mjs';\n\n/**\n * @typedef {Object} ManifestEntry\n * @property {string} url The URL to the asset in the manifest.\n * @property {string} revision The revision details for the file. This should be\n * either a hash generated based on the file contents, or `null` if there is\n * versioning already included in the URL.\n * @property {string} [integrity] Integrity metadata that will be used when\n * making the network request for the URL.\n *\n * @memberof module:workbox-build\n */\n\n/**\n * @typedef {Object} ManifestTransformResult\n * @property {Array<module:workbox-build.ManifestEntry>} manifest\n * @property {Array<string>|undefined} warnings\n *\n * @memberof module:workbox-build\n */\n\n/**\n * @typedef {Object} RuntimeCachingEntry\n *\n * @property {string|module:workbox-routing~handlerCallback} handler\n * Either the name of one of the [built-in strategy classes]{@link module:workbox-strategies},\n * or custom handler callback to use when the generated route matches.\n *\n * @property {string|RegExp|module:workbox-routing~matchCallback} urlPattern\n * The value that will be passed to [`registerRoute()`]{@link module:workbox-routing.registerRoute},\n * used to determine whether the generated route will match a given request.\n *\n * @property {string} [method='GET'] The\n * [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) that\n * will match the generated route.\n *\n * @property {Object} [options]\n *\n * @property {Object} [options.backgroundSync]\n *\n * @property {string} [options.backgroundSync.name] The `name` property to use\n * when creating the\n * [`BackgroundSyncPlugin`]{@link module:workbox-background-sync.BackgroundSyncPlugin}.\n *\n * @property {Object} [options.backgroundSync.options] The `options` property\n * to use when creating the\n * [`BackgroundSyncPlugin`]{@link module:workbox-background-sync.BackgroundSyncPlugin}.\n *\n * @property {Object} [options.broadcastUpdate]\n *\n * @property {string} [options.broadcastUpdate.channelName] The `channelName`\n * property to use when creating the\n * [`BroadcastCacheUpdatePlugin`]{@link module:workbox-broadcast-update.BroadcastUpdatePlugin}.\n *\n * @property {Object} [options.broadcastUpdate.options] The `options` property\n * to use when creating the\n * [`BroadcastCacheUpdatePlugin`]{@link module:workbox-broadcast-update.BroadcastUpdatePlugin}.\n *\n * @property {Object} [options.cacheableResponse]\n *\n * @property {Object} [options.cacheableResponse.headers] The `headers` property\n * to use when creating the\n * [`CacheableResponsePlugin`]{@link module:workbox-cacheable-response.CacheableResponsePlugin}.\n *\n * @property {Array<number>} [options.cacheableResponse.statuses] `statuses`\n * property to use when creating the\n * [`CacheableResponsePlugin`]{@link module:workbox-cacheable-response.CacheableResponsePlugin}.\n *\n * @property {string} [options.cacheName] The `cacheName` to use when\n * constructing one of the\n * [Workbox strategy classes]{@link module:workbox-strategies}.\n *\n * @property {Object} [options.fetchOptions] The `fetchOptions` property value\n * to use when constructing one of the\n * [Workbox strategy classes]{@link module:workbox-strategies}.\n *\n * @property {Object} [options.expiration]\n *\n * @property {number} [options.expiration.maxAgeSeconds] The `maxAgeSeconds`\n * property to use when creating the\n * [`ExpirationPlugin`]{@link module:workbox-expiration.ExpirationPlugin}.\n *\n * @property {number} [options.expiration.maxEntries] The `maxEntries`\n * property to use when creating the\n * [`ExpirationPlugin`]{@link module:workbox-expiration.ExpirationPlugin}.\n *\n * @property {Object} [options.precacheFallback]\n *\n * @property {string} [options.precacheFallback.fallbackURL] The `fallbackURL`\n * property to use when creating the\n * [`PrecacheFallbackPlugin`]{@link module:workbox-precaching.PrecacheFallbackPlugin}.\n *\n * @property {boolean} [options.rangeRequests] Set to `true` to add the\n * [`RangeRequestsPlugin`]{@link module:workbox-range-requests.RangeRequestsPlugin}\n * for the strategy being configured.\n *\n * @property {Object} [options.matchOptions] The `matchOptions` property value\n * to use when constructing one of the\n * [Workbox strategy classes]{@link module:workbox-strategies}.\n *\n * @property {number} [options.networkTimeoutSeconds] The\n * `networkTimeoutSeconds` property value to use when creating a\n * [`NetworkFirst`]{@link module:workbox-strategies.NetworkFirst} strategy.\n *\n * @property {Array<Object>} [options.plugins]\n * One or more [additional plugins](https://developers.google.com/web/tools/workbox/guides/using-plugins#custom_plugins)\n * to apply to the handler. Useful when you want a plugin that doesn't have a\n * \"shortcut\" configuration.\n *\n * @memberof module:workbox-build\n */\n"
  },
  {
    "path": "packages/workbox-build/src/cdn-details.json",
    "content": "{\n  \"origin\": \"https://storage.googleapis.com\",\n  \"bucketName\": \"workbox-cdn\",\n  \"releasesDir\": \"releases\",\n  \"latestVersion\": \"7.4.0\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/generate-sw.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport upath from 'upath';\n\nimport {BuildResult, GetManifestOptions, GenerateSWOptions} from './types';\nimport {getFileManifestEntries} from './lib/get-file-manifest-entries';\nimport {rebasePath} from './lib/rebase-path';\nimport {validateGenerateSWOptions} from './lib/validate-options';\nimport {writeSWUsingDefaultTemplate} from './lib/write-sw-using-default-template';\n\n/**\n * This method creates a list of URLs to precache, referred to as a \"precache\n * manifest\", based on the options you provide.\n *\n * It also takes in additional options that configures the service worker's\n * behavior, like any `runtimeCaching` rules it should use.\n *\n * Based on the precache manifest and the additional configuration, it writes\n * a ready-to-use service worker file to disk at `swDest`.\n *\n * ```\n * // The following lists some common options; see the rest of the documentation\n * // for the full set of options and defaults.\n * const {count, size, warnings} = await generateSW({\n *   dontCacheBustURLsMatching: [new RegExp('...')],\n *   globDirectory: '...',\n *   globPatterns: ['...', '...'],\n *   maximumFileSizeToCacheInBytes: ...,\n *   navigateFallback: '...',\n *   runtimeCaching: [{\n *     // Routing via a matchCallback function:\n *     urlPattern: ({request, url}) => ...,\n *     handler: '...',\n *     options: {\n *       cacheName: '...',\n *       expiration: {\n *         maxEntries: ...,\n *       },\n *     },\n *   }, {\n *     // Routing via a RegExp:\n *     urlPattern: new RegExp('...'),\n *     handler: '...',\n *     options: {\n *       cacheName: '...',\n *       plugins: [..., ...],\n *     },\n *   }],\n *   skipWaiting: ...,\n *   swDest: '...',\n * });\n * ```\n *\n * @memberof workbox-build\n */\nexport async function generateSW(\n  config: GenerateSWOptions,\n): Promise<BuildResult> {\n  const options = validateGenerateSWOptions(config);\n  let entriesResult;\n\n  if (options.globDirectory) {\n    // Make sure we leave swDest out of the precache manifest.\n    options.globIgnores!.push(\n      rebasePath({\n        baseDirectory: options.globDirectory,\n        file: options.swDest,\n      }),\n    );\n\n    // If we create an extra external runtime file, ignore that, too.\n    // See https://rollupjs.org/guide/en/#outputchunkfilenames for naming.\n    if (!options.inlineWorkboxRuntime) {\n      const swDestDir = upath.dirname(options.swDest);\n      const workboxRuntimeFile = upath.join(swDestDir, 'workbox-*.js');\n      options.globIgnores!.push(\n        rebasePath({\n          baseDirectory: options.globDirectory,\n          file: workboxRuntimeFile,\n        }),\n      );\n    }\n\n    // We've previously asserted that options.globDirectory is set, so this\n    // should be a safe cast.\n    entriesResult = await getFileManifestEntries(options as GetManifestOptions);\n  } else {\n    entriesResult = {\n      count: 0,\n      manifestEntries: [],\n      size: 0,\n      warnings: [],\n    };\n  }\n\n  const filePaths = await writeSWUsingDefaultTemplate(\n    Object.assign(\n      {\n        manifestEntries: entriesResult.manifestEntries,\n      },\n      options,\n    ),\n  );\n\n  return {\n    filePaths,\n    count: entriesResult.count,\n    size: entriesResult.size,\n    warnings: entriesResult.warnings,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/get-manifest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getFileManifestEntries} from './lib/get-file-manifest-entries';\nimport {GetManifestOptions, GetManifestResult} from './types';\nimport {validateGetManifestOptions} from './lib/validate-options';\n\n/**\n * This method returns a list of URLs to precache, referred to as a \"precache\n * manifest\", along with details about the number of entries and their size,\n * based on the options you provide.\n *\n * ```\n * // The following lists some common options; see the rest of the documentation\n * // for the full set of options and defaults.\n * const {count, manifestEntries, size, warnings} = await getManifest({\n *   dontCacheBustURLsMatching: [new RegExp('...')],\n *   globDirectory: '...',\n *   globPatterns: ['...', '...'],\n *   maximumFileSizeToCacheInBytes: ...,\n * });\n * ```\n *\n * @memberof workbox-build\n */\nexport async function getManifest(\n  config: GetManifestOptions,\n): Promise<GetManifestResult> {\n  const options = validateGetManifestOptions(config);\n\n  return await getFileManifestEntries(options);\n}\n"
  },
  {
    "path": "packages/workbox-build/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {copyWorkboxLibraries} from './lib/copy-workbox-libraries';\nimport {getModuleURL} from './lib/cdn-utils';\nimport {generateSW} from './generate-sw';\nimport {getManifest} from './get-manifest';\nimport {injectManifest} from './inject-manifest';\n\n/**\n * @module workbox-build\n */\nexport {\n  copyWorkboxLibraries,\n  generateSW,\n  getManifest,\n  getModuleURL,\n  injectManifest,\n};\n\nexport * from './types';\n"
  },
  {
    "path": "packages/workbox-build/src/inject-manifest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RawSourceMap} from 'source-map';\nimport assert from 'assert';\nimport fse from 'fs-extra';\nimport stringify from 'fast-json-stable-stringify';\nimport upath from 'upath';\n\nimport {BuildResult, InjectManifestOptions} from './types';\nimport {errors} from './lib/errors';\nimport {escapeRegExp} from './lib/escape-regexp';\nimport {getFileManifestEntries} from './lib/get-file-manifest-entries';\nimport {getSourceMapURL} from './lib/get-source-map-url';\nimport {rebasePath} from './lib/rebase-path';\nimport {replaceAndUpdateSourceMap} from './lib/replace-and-update-source-map';\nimport {translateURLToSourcemapPaths} from './lib/translate-url-to-sourcemap-paths';\nimport {validateInjectManifestOptions} from './lib/validate-options';\n\n/**\n * This method creates a list of URLs to precache, referred to as a \"precache\n * manifest\", based on the options you provide.\n *\n * The manifest is injected into the `swSrc` file, and the placeholder string\n * `injectionPoint` determines where in the file the manifest should go.\n *\n * The final service worker file, with the manifest injected, is written to\n * disk at `swDest`.\n *\n * This method will not compile or bundle your `swSrc` file; it just handles\n * injecting the manifest.\n *\n * ```\n * // The following lists some common options; see the rest of the documentation\n * // for the full set of options and defaults.\n * const {count, size, warnings} = await injectManifest({\n *   dontCacheBustURLsMatching: [new RegExp('...')],\n *   globDirectory: '...',\n *   globPatterns: ['...', '...'],\n *   maximumFileSizeToCacheInBytes: ...,\n *   swDest: '...',\n *   swSrc: '...',\n * });\n * ```\n *\n * @memberof workbox-build\n */\nexport async function injectManifest(\n  config: InjectManifestOptions,\n): Promise<BuildResult> {\n  const options = validateInjectManifestOptions(config);\n\n  // Make sure we leave swSrc and swDest out of the precache manifest.\n  for (const file of [options.swSrc, options.swDest]) {\n    options.globIgnores!.push(\n      rebasePath({\n        file,\n        baseDirectory: options.globDirectory,\n      }),\n    );\n  }\n\n  const globalRegexp = new RegExp(escapeRegExp(options.injectionPoint!), 'g');\n\n  const {count, size, manifestEntries, warnings} = await getFileManifestEntries(\n    options,\n  );\n  let swFileContents: string;\n  try {\n    swFileContents = await fse.readFile(options.swSrc, 'utf8');\n  } catch (error) {\n    throw new Error(\n      `${errors['invalid-sw-src']} ${\n        error instanceof Error && error.message ? error.message : ''\n      }`,\n    );\n  }\n\n  const injectionResults = swFileContents.match(globalRegexp);\n  // See https://github.com/GoogleChrome/workbox/issues/2230\n  const injectionPoint = options.injectionPoint ? options.injectionPoint : '';\n  if (!injectionResults) {\n    if (upath.resolve(options.swSrc) === upath.resolve(options.swDest)) {\n      throw new Error(`${errors['same-src-and-dest']} ${injectionPoint}`);\n    }\n    throw new Error(`${errors['injection-point-not-found']} ${injectionPoint}`);\n  }\n\n  assert(\n    injectionResults.length === 1,\n    `${errors['multiple-injection-points']} ${injectionPoint}`,\n  );\n\n  const manifestString = stringify(manifestEntries);\n  const filesToWrite: {[key: string]: string} = {};\n\n  const url = getSourceMapURL(swFileContents);\n  // See https://github.com/GoogleChrome/workbox/issues/2957\n  const {destPath, srcPath, warning} = translateURLToSourcemapPaths(\n    url,\n    options.swSrc,\n    options.swDest,\n  );\n  if (warning) {\n    warnings.push(warning);\n  }\n\n  // If our swSrc file contains a sourcemap, we would invalidate that\n  // mapping if we just replaced injectionPoint with the stringified manifest.\n  // Instead, we need to update the swDest contents as well as the sourcemap\n  // (assuming it's a real file, not a data: URL) at the same time.\n  // See https://github.com/GoogleChrome/workbox/issues/2235\n  // and https://github.com/GoogleChrome/workbox/issues/2648\n  if (srcPath && destPath) {\n    const originalMap = (await fse.readJSON(srcPath, {\n      encoding: 'utf8',\n    })) as RawSourceMap;\n\n    const {map, source} = await replaceAndUpdateSourceMap({\n      originalMap,\n      jsFilename: upath.basename(options.swDest),\n      originalSource: swFileContents,\n      replaceString: manifestString,\n      searchString: options.injectionPoint!,\n    });\n\n    filesToWrite[options.swDest] = source;\n    filesToWrite[destPath] = map;\n  } else {\n    // If there's no sourcemap associated with swSrc, a simple string\n    // replacement will suffice.\n    filesToWrite[options.swDest] = swFileContents.replace(\n      globalRegexp,\n      manifestString,\n    );\n  }\n\n  for (const [file, contents] of Object.entries(filesToWrite)) {\n    try {\n      await fse.mkdirp(upath.dirname(file));\n    } catch (error: unknown) {\n      throw new Error(\n        errors['unable-to-make-sw-directory'] +\n          ` '${error instanceof Error && error.message ? error.message : ''}'`,\n      );\n    }\n\n    await fse.writeFile(file, contents);\n  }\n\n  return {\n    count,\n    size,\n    warnings,\n    // Use upath.resolve() to make all the paths absolute.\n    filePaths: Object.keys(filesToWrite).map((f) => upath.resolve(f)),\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/additional-manifest-entries-transform.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {errors} from './errors';\nimport {ManifestEntry} from '../types';\n\ntype AdditionalManifestEntriesTransform = {\n  (manifest: Array<ManifestEntry & {size: number}>): {\n    manifest: Array<ManifestEntry & {size: number}>;\n    warnings: string[];\n  };\n};\n\nexport function additionalManifestEntriesTransform(\n  additionalManifestEntries: Array<ManifestEntry | string>,\n): AdditionalManifestEntriesTransform {\n  return (manifest: Array<ManifestEntry & {size: number}>) => {\n    const warnings: Array<string> = [];\n    const stringEntries = new Set<string>();\n\n    for (const additionalEntry of additionalManifestEntries) {\n      // Warn about either a string or an object that lacks a revision property.\n      // (An object with a revision property set to null is okay.)\n      if (typeof additionalEntry === 'string') {\n        stringEntries.add(additionalEntry);\n        manifest.push({\n          revision: null,\n          size: 0,\n          url: additionalEntry,\n        });\n      } else {\n        if (additionalEntry && additionalEntry.revision === undefined) {\n          stringEntries.add(additionalEntry.url);\n        }\n        manifest.push(Object.assign({size: 0}, additionalEntry));\n      }\n    }\n\n    if (stringEntries.size > 0) {\n      let urls = '\\n';\n      for (const stringEntry of stringEntries) {\n        urls += `  - ${stringEntry}\\n`;\n      }\n\n      warnings.push(errors['string-entry-warning'] + urls);\n    }\n\n    return {\n      manifest,\n      warnings,\n    };\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/bundle.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {babel} from '@rollup/plugin-babel';\nimport {nodeResolve} from '@rollup/plugin-node-resolve';\nimport {rollup, Plugin} from 'rollup';\nimport terser from '@rollup/plugin-terser';\nimport {writeFile} from 'fs-extra';\nimport omt from '@trickfilm400/rollup-plugin-off-main-thread';\nimport presetEnv from '@babel/preset-env';\nimport replace from '@rollup/plugin-replace';\nimport tempy from 'tempy';\nimport upath from 'upath';\n\nimport {GeneratePartial, RequiredSWDestPartial} from '../types';\n\ninterface NameAndContents {\n  contents: string | Uint8Array;\n  name: string;\n}\n\nexport async function bundle({\n  babelPresetEnvTargets,\n  inlineWorkboxRuntime,\n  mode,\n  sourcemap,\n  swDest,\n  unbundledCode,\n}: Omit<GeneratePartial, 'runtimeCaching'> &\n  RequiredSWDestPartial & {unbundledCode: string}): Promise<\n  Array<NameAndContents>\n> {\n  // We need to write this to the \"real\" file system, as Rollup won't read from\n  // a custom file system.\n  const {dir, base} = upath.parse(swDest);\n\n  const temporaryFile = tempy.file({name: base});\n  await writeFile(temporaryFile, unbundledCode);\n\n  const plugins = [\n    nodeResolve(),\n    replace({\n      // See https://github.com/GoogleChrome/workbox/issues/2769\n      'preventAssignment': true,\n      'process.env.NODE_ENV': JSON.stringify(mode),\n    }),\n    babel({\n      babelHelpers: 'bundled',\n      // Disable the logic that checks for local Babel config files:\n      // https://github.com/GoogleChrome/workbox/issues/2111\n      babelrc: false,\n      configFile: false,\n      presets: [\n        [\n          presetEnv,\n          {\n            targets: {\n              browsers: babelPresetEnvTargets,\n            },\n            loose: true,\n          },\n        ],\n      ],\n    }),\n  ];\n\n  if (mode === 'production') {\n    plugins.push(\n      terser({\n        mangle: {\n          toplevel: true,\n          properties: {\n            regex: /(^_|_$)/,\n          },\n        },\n      }),\n    );\n  }\n\n  const rollupConfig: {\n    input: string;\n    plugins: Array<Plugin>;\n  } = {\n    plugins,\n    input: temporaryFile,\n  };\n\n  // Rollup will inline the runtime by default. If we don't want that, we need\n  // to add in some additional config.\n  if (!inlineWorkboxRuntime) {\n    // No lint for omt(), library has no types.\n    // eslint-disable-next-line  @typescript-eslint/no-unsafe-call\n    rollupConfig.plugins.unshift(omt());\n  }\n\n  const bundle = await rollup(rollupConfig);\n\n  const outputOptions: {\n    sourcemap?: boolean;\n    format: 'es' | 'amd';\n    manualChunks?: (id: string) => string | undefined;\n    chunkFileNames?: string;\n    hashCharacters?: 'base64' | 'hex';\n  } = {\n    sourcemap,\n    // Using an external Workbox runtime requires 'amd'.\n    format: inlineWorkboxRuntime ? 'es' : 'amd',\n  };\n\n  if (!inlineWorkboxRuntime) {\n    outputOptions.manualChunks = (id) => {\n      return id.includes('workbox') ? 'workbox' : undefined;\n    };\n    outputOptions.hashCharacters = 'hex';\n  }\n\n  const {output} = await bundle.generate(outputOptions);\n  const files: Array<NameAndContents> = [];\n  for (const chunkOrAsset of output) {\n    if (chunkOrAsset.type === 'asset') {\n      if (!files.some((f) => f.name === chunkOrAsset.fileName)) {\n        files.push({\n          name: chunkOrAsset.fileName,\n          contents: chunkOrAsset.source,\n        });\n      }\n    } else {\n      let code = chunkOrAsset.code;\n\n      if (chunkOrAsset.map) {\n        const sourceMapFile = chunkOrAsset.fileName + '.map';\n        code += `//# sourceMappingURL=${sourceMapFile}\\n`;\n        if (!files.some((f) => f.name === sourceMapFile)) {\n          files.push({\n            name: sourceMapFile,\n            contents: chunkOrAsset.map.toString(),\n          });\n        }\n      }\n\n      if (!files.some((f) => f.name === chunkOrAsset.fileName)) {\n        files.push({\n          name: chunkOrAsset.fileName,\n          contents: code,\n        });\n      }\n    }\n  }\n\n  // Make sure that if there was a directory portion included in swDest, it's\n  // preprended to all of the generated files.\n  return files.map((file) => {\n    file.name = upath.format({\n      dir,\n      base: file.name,\n      ext: '',\n      name: '',\n      root: '',\n    });\n    return file;\n  });\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/cdn-utils.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {ok} from 'assert';\n\nimport {BuildType, WorkboxPackageJSON} from '../types';\nimport {errors} from './errors';\nimport * as cdn from '../cdn-details.json';\n\nfunction getVersionedURL(): string {\n  return `${getCDNPrefix()}/${cdn.latestVersion}`;\n}\n\nfunction getCDNPrefix() {\n  return `${cdn.origin}/${cdn.bucketName}/${cdn.releasesDir}`;\n}\n\nexport function getModuleURL(moduleName: string, buildType: BuildType): string {\n  ok(moduleName, errors['no-module-name']);\n\n  if (buildType) {\n    // eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment\n    const pkgJson: WorkboxPackageJSON = require(`${moduleName}/package.json`);\n    if (buildType === 'dev' && pkgJson.workbox && pkgJson.workbox.prodOnly) {\n      // This is not due to a public-facing exception, so just throw an Error(),\n      // without creating an entry in errors.js.\n      throw Error(`The 'dev' build of ${moduleName} is not available.`);\n    }\n    return `${getVersionedURL()}/${moduleName}.${buildType.slice(0, 4)}.js`;\n  }\n  return `${getVersionedURL()}/${moduleName}.js`;\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/copy-workbox-libraries.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\nimport upath from 'upath';\n\nimport {WorkboxPackageJSON} from '../types';\nimport {errors} from './errors';\n\n// Used to filter the libraries to copy based on our package.json dependencies.\nconst WORKBOX_PREFIX = 'workbox-';\n\n// The directory within each package containing the final bundles.\nconst BUILD_DIR = 'build';\n\n/**\n * This copies over a set of runtime libraries used by Workbox into a\n * local directory, which should be deployed alongside your service worker file.\n *\n * As an alternative to deploying these local copies, you could instead use\n * Workbox from its official CDN URL.\n *\n * This method is exposed for the benefit of developers using\n * {@link workbox-build.injectManifest} who would\n * prefer not to use the CDN copies of Workbox. Developers using\n * {@link workbox-build.generateSW} don't need to\n * explicitly call this method.\n *\n * @param {string} destDirectory The path to the parent directory under which\n * the new directory of libraries will be created.\n * @return {Promise<string>} The name of the newly created directory.\n *\n * @alias workbox-build.copyWorkboxLibraries\n */\nexport async function copyWorkboxLibraries(\n  destDirectory: string,\n): Promise<string> {\n  // eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment\n  const thisPkg: WorkboxPackageJSON = require('../../package.json');\n  // Use the version string from workbox-build in the name of the parent\n  // directory. This should be safe, because lerna will bump workbox-build's\n  // pkg.version whenever one of the dependent libraries gets bumped, and we\n  // care about versioning the dependent libraries.\n  const workboxDirectoryName = `workbox-v${\n    thisPkg.version ? thisPkg.version : ''\n  }`;\n  const workboxDirectoryPath = upath.join(destDirectory, workboxDirectoryName);\n  await fse.ensureDir(workboxDirectoryPath);\n\n  const copyPromises: Array<Promise<void>> = [];\n  const librariesToCopy = Object.keys(thisPkg.dependencies || {}).filter(\n    (dependency) => dependency.startsWith(WORKBOX_PREFIX),\n  );\n\n  for (const library of librariesToCopy) {\n    // Get the path to the package on the user's filesystem by require-ing\n    // the package's `package.json` file via the node resolution algorithm.\n    const libraryPath = upath.dirname(\n      require.resolve(`${library}/package.json`),\n    );\n\n    const buildPath = upath.join(libraryPath, BUILD_DIR);\n\n    // fse.copy() copies all the files in a directory, not the directory itself.\n    // See https://github.com/jprichardson/node-fs-extra/blob/master/docs/copy.md#copysrc-dest-options-callback\n    copyPromises.push(fse.copy(buildPath, workboxDirectoryPath));\n  }\n\n  try {\n    await Promise.all(copyPromises);\n    return workboxDirectoryName;\n  } catch (error) {\n    throw Error(\n      `${errors['unable-to-copy-workbox-libraries']} ${\n        error instanceof Error ? error.toString() : ''\n      }`,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/errors.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {oneLine as ol} from 'common-tags';\n\nexport const errors = {\n  'unable-to-get-rootdir': `Unable to get the root directory of your web app.`,\n  'no-extension': ol`Unable to detect a usable extension for a file in your web\n    app directory.`,\n  'invalid-file-manifest-name': ol`The File Manifest Name must have at least one\n    character.`,\n  'unable-to-get-file-manifest-name': 'Unable to get a file manifest name.',\n  'invalid-sw-dest': `The 'swDest' value must be a valid path.`,\n  'unable-to-get-sw-name': 'Unable to get a service worker file name.',\n  'unable-to-get-save-config': ol`An error occurred when asking to save details\n    in a config file.`,\n  'unable-to-get-file-hash': ol`An error occurred when attempting to create a\n    file hash.`,\n  'unable-to-get-file-size': ol`An error occurred when attempting to get a file\n    size.`,\n  'unable-to-glob-files': 'An error occurred when globbing for files.',\n  'unable-to-make-manifest-directory': ol`Unable to make output directory for\n    file manifest.`,\n  'read-manifest-template-failure': 'Unable to read template for file manifest',\n  'populating-manifest-tmpl-failed': ol`An error occurred when populating the\n    file manifest template.`,\n  'manifest-file-write-failure': 'Unable to write the file manifest.',\n  'unable-to-make-sw-directory': ol`Unable to make the directories to output\n    the service worker path.`,\n  'read-sw-template-failure': ol`Unable to read the service worker template\n    file.`,\n  'sw-write-failure': 'Unable to write the service worker file.',\n  'sw-write-failure-directory': ol`Unable to write the service worker file;\n    'swDest' should be a full path to the file, not a path to a directory.`,\n  'unable-to-copy-workbox-libraries': ol`One or more of the Workbox libraries\n    could not be copied over to the destination directory: `,\n  'invalid-generate-sw-input': ol`The input to generateSW() must be an object.`,\n  'invalid-glob-directory': ol`The supplied globDirectory must be a path as a\n    string.`,\n  'invalid-dont-cache-bust': ol`The supplied 'dontCacheBustURLsMatching'\n    parameter must be a RegExp.`,\n  'invalid-exclude-files': 'The excluded files should be an array of strings.',\n  'invalid-get-manifest-entries-input': ol`The input to\n    'getFileManifestEntries()' must be an object.`,\n  'invalid-manifest-path': ol`The supplied manifest path is not a string with\n    at least one character.`,\n  'invalid-manifest-entries': ol`The manifest entries must be an array of\n    strings or JavaScript objects containing a url parameter.`,\n  'invalid-manifest-format': ol`The value of the 'format' option passed to\n    generateFileManifest() must be either 'iife' (the default) or 'es'.`,\n  'invalid-static-file-globs': ol`The 'globPatterns' value must be an array\n    of strings.`,\n  'invalid-templated-urls': ol`The 'templatedURLs' value should be an object\n    that maps URLs to either a string, or to an array of glob patterns.`,\n  'templated-url-matches-glob': ol`One of the 'templatedURLs' URLs is already\n    being tracked via 'globPatterns': `,\n  'invalid-glob-ignores': ol`The 'globIgnores' parameter must be an array of\n    glob pattern strings.`,\n  'manifest-entry-bad-url': ol`The generated manifest contains an entry without\n    a URL string. This is likely an error with workbox-build.`,\n  'modify-url-prefix-bad-prefixes': ol`The 'modifyURLPrefix' parameter must be\n    an object with string key value pairs.`,\n  'invalid-inject-manifest-arg': ol`The input to 'injectManifest()' must be an\n    object.`,\n  'injection-point-not-found': ol`Unable to find a place to inject the manifest.\n    Please ensure that your service worker file contains the following: `,\n  'multiple-injection-points': ol`Please ensure that your 'swSrc' file contains\n    only one match for the following: `,\n  'populating-sw-tmpl-failed': ol`Unable to generate service worker from\n    template.`,\n  'useless-glob-pattern': ol`One of the glob patterns doesn't match any files.\n    Please remove or fix the following: `,\n  'bad-template-urls-asset': ol`There was an issue using one of the provided\n    'templatedURLs'.`,\n  'invalid-runtime-caching': ol`The 'runtimeCaching' parameter must an an\n    array of objects with at least a 'urlPattern' and 'handler'.`,\n  'static-file-globs-deprecated': ol`'staticFileGlobs' is deprecated.\n    Please use 'globPatterns' instead.`,\n  'dynamic-url-deprecated': ol`'dynamicURLToDependencies' is deprecated.\n    Please use 'templatedURLs' instead.`,\n  'urlPattern-is-required': ol`The 'urlPattern' option is required when using\n    'runtimeCaching'.`,\n  'handler-is-required': ol`The 'handler' option is required when using\n    runtimeCaching.`,\n  'invalid-generate-file-manifest-arg': ol`The input to generateFileManifest()\n    must be an Object.`,\n  'invalid-sw-src': `The 'swSrc' file can't be read.`,\n  'same-src-and-dest': ol`Unable to find a place to inject the manifest. This is\n    likely because swSrc and swDest are configured to the same file.\n    Please ensure that your swSrc file contains the following:`,\n  'only-regexp-routes-supported': ol`Please use a regular expression object as\n    the urlPattern parameter. (Express-style routes are not currently\n    supported.)`,\n  'bad-runtime-caching-config': ol`An unknown configuration option was used\n    with runtimeCaching: `,\n  'invalid-network-timeout-seconds': ol`When using networkTimeoutSeconds, you\n    must set the handler to 'NetworkFirst'.`,\n  'no-module-name': ol`You must provide a moduleName parameter when calling\n    getModuleURL().`,\n  'bad-manifest-transforms-return-value': ol`The return value from a\n    manifestTransform should be an object with 'manifest' and optionally\n    'warnings' properties.`,\n  'string-entry-warning': ol`Some items were passed to additionalManifestEntries\n    without revisioning info. This is generally NOT safe. Learn more at\n    https://bit.ly/wb-precache.`,\n  'no-manifest-entries-or-runtime-caching': ol`Couldn't find configuration for\n    either precaching or runtime caching. Please ensure that the various glob\n    options are set to match one or more files, and/or configure the\n    runtimeCaching option.`,\n  'cant-find-sourcemap': ol`The swSrc file refers to a sourcemap that can't be\n    opened:`,\n  'nav-preload-runtime-caching': ol`When using navigationPreload, you must also\n    configure a runtimeCaching route that will use the preloaded response.`,\n  'cache-name-required': ol`When using cache expiration, you must also\n    configure a custom cacheName.`,\n  'manifest-transforms': ol`When using manifestTransforms, you must provide\n    an array of functions.`,\n  'invalid-handler-string': ol`The handler name provided is not valid: `,\n};\n"
  },
  {
    "path": "packages/workbox-build/src/lib/escape-regexp.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions\nexport function escapeRegExp(str: string): string {\n  return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-composite-details.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport crypto from 'crypto';\n\nimport {FileDetails} from '../types';\n\nexport function getCompositeDetails(\n  compositeURL: string,\n  dependencyDetails: Array<FileDetails>,\n): FileDetails {\n  let totalSize = 0;\n  let compositeHash = '';\n\n  for (const fileDetails of dependencyDetails) {\n    totalSize += fileDetails.size;\n    compositeHash += fileDetails.hash;\n  }\n\n  const md5 = crypto.createHash('md5');\n  md5.update(compositeHash);\n  const hashOfHashes = md5.digest('hex');\n\n  return {\n    file: compositeURL,\n    hash: hashOfHashes,\n    size: totalSize,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-file-details.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {globSync} from 'glob';\nimport upath from 'upath';\n\nimport {errors} from './errors';\nimport {getFileSize} from './get-file-size';\nimport {getFileHash} from './get-file-hash';\n\nimport {GlobPartial} from '../types';\n\ninterface FileDetails {\n  file: string;\n  hash: string;\n  size: number;\n}\n\nexport function getFileDetails({\n  globDirectory,\n  globFollow,\n  globIgnores,\n  globPattern,\n}: Omit<GlobPartial, 'globDirectory' | 'globPatterns' | 'templatedURLs'> & {\n  // This will only be called when globDirectory is not undefined.\n  globDirectory: string;\n  globPattern: string;\n}): {\n  globbedFileDetails: Array<FileDetails>;\n  warning: string;\n} {\n  let globbedFiles: Array<string>;\n  let warning = '';\n\n  try {\n    globbedFiles = globSync(globPattern, {\n      cwd: globDirectory,\n      follow: globFollow,\n      ignore: globIgnores,\n    });\n  } catch (err) {\n    throw new Error(\n      errors['unable-to-glob-files'] +\n        ` '${err instanceof Error && err.message ? err.message : ''}'`,\n    );\n  }\n\n  if (globbedFiles.length === 0) {\n    warning =\n      errors['useless-glob-pattern'] +\n      ' ' +\n      JSON.stringify({globDirectory, globPattern, globIgnores}, null, 2);\n  }\n\n  const globbedFileDetails: Array<FileDetails> = [];\n  for (const file of globbedFiles) {\n    const fullPath = upath.join(globDirectory, file);\n    const fileSize = getFileSize(fullPath);\n    if (fileSize !== null) {\n      const fileHash = getFileHash(fullPath);\n      globbedFileDetails.push({\n        file: `${upath.relative(globDirectory, fullPath)}`,\n        hash: fileHash,\n        size: fileSize,\n      });\n    }\n  }\n\n  return {globbedFileDetails, warning};\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-file-hash.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\n\nimport {getStringHash} from './get-string-hash';\nimport {errors} from './errors';\n\nexport function getFileHash(file: string): string {\n  try {\n    const buffer = fse.readFileSync(file);\n    return getStringHash(buffer);\n  } catch (err) {\n    throw new Error(\n      errors['unable-to-get-file-hash'] +\n        ` '${err instanceof Error && err.message ? err.message : ''}'`,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-file-manifest-entries.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\n\nimport {GetManifestResult, FileDetails, GetManifestOptions} from '../types';\nimport {errors} from './errors';\nimport {getCompositeDetails} from './get-composite-details';\nimport {getFileDetails} from './get-file-details';\nimport {getStringDetails} from './get-string-details';\nimport {transformManifest} from './transform-manifest';\n\nexport async function getFileManifestEntries({\n  additionalManifestEntries,\n  dontCacheBustURLsMatching,\n  globDirectory,\n  globFollow,\n  globIgnores,\n  globPatterns = [],\n  manifestTransforms,\n  maximumFileSizeToCacheInBytes,\n  modifyURLPrefix,\n  templatedURLs,\n}: GetManifestOptions): Promise<GetManifestResult> {\n  const warnings: Array<string> = [];\n  const allFileDetails = new Map<string, FileDetails>();\n\n  try {\n    for (const globPattern of globPatterns) {\n      const {globbedFileDetails, warning} = getFileDetails({\n        globDirectory,\n        globFollow,\n        globIgnores,\n        globPattern,\n      });\n\n      if (warning) {\n        warnings.push(warning);\n      }\n\n      for (const details of globbedFileDetails) {\n        if (details && !allFileDetails.has(details.file)) {\n          allFileDetails.set(details.file, details);\n        }\n      }\n    }\n  } catch (error) {\n    // If there's an exception thrown while globbing, then report\n    // it back as a warning, and don't consider it fatal.\n    if (error instanceof Error && error.message) {\n      warnings.push(error.message);\n    }\n  }\n\n  if (templatedURLs) {\n    for (const url of Object.keys(templatedURLs)) {\n      assert(!allFileDetails.has(url), errors['templated-url-matches-glob']);\n\n      const dependencies = templatedURLs[url];\n      if (Array.isArray(dependencies)) {\n        const details = dependencies.reduce<Array<FileDetails>>(\n          (previous, globPattern) => {\n            try {\n              const {globbedFileDetails, warning} = getFileDetails({\n                globDirectory,\n                globFollow,\n                globIgnores,\n                globPattern,\n              });\n\n              if (warning) {\n                warnings.push(warning);\n              }\n\n              return previous.concat(globbedFileDetails);\n            } catch (error) {\n              const debugObj: {[key: string]: Array<string>} = {};\n              debugObj[url] = dependencies;\n              throw new Error(\n                `${errors['bad-template-urls-asset']} ` +\n                  `'${globPattern}' from '${JSON.stringify(debugObj)}':\\n` +\n                  `${error instanceof Error ? error.toString() : ''}`,\n              );\n            }\n          },\n          [],\n        );\n        if (details.length === 0) {\n          throw new Error(\n            `${errors['bad-template-urls-asset']} The glob ` +\n              `pattern '${dependencies.toString()}' did not match anything.`,\n          );\n        }\n        allFileDetails.set(url, getCompositeDetails(url, details));\n      } else if (typeof dependencies === 'string') {\n        allFileDetails.set(url, getStringDetails(url, dependencies));\n      }\n    }\n  }\n\n  const transformedManifest = await transformManifest({\n    additionalManifestEntries,\n    dontCacheBustURLsMatching,\n    manifestTransforms,\n    maximumFileSizeToCacheInBytes,\n    modifyURLPrefix,\n    fileDetails: Array.from(allFileDetails.values()),\n  });\n\n  transformedManifest.warnings.push(...warnings);\n\n  return transformedManifest;\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-file-size.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\n\nimport {errors} from './errors';\n\nexport function getFileSize(file: string): number | null {\n  try {\n    const stat = fse.statSync(file);\n    if (!stat.isFile()) {\n      return null;\n    }\n    return stat.size;\n  } catch (err) {\n    throw new Error(\n      errors['unable-to-get-file-size'] +\n        ` '${err instanceof Error && err.message ? err.message : ''}'`,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-source-map-url.ts",
    "content": "/*\n  Copyright 2022 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Adapted from https://github.com/lydell/source-map-url/blob/master/source-map-url.js\n// See https://github.com/GoogleChrome/workbox/issues/3019\nconst innerRegex = /[#@] sourceMappingURL=([^\\s'\"]*)/;\nconst regex = RegExp(\n  '(?:' +\n    '/\\\\*' +\n    '(?:\\\\s*\\r?\\n(?://)?)?' +\n    '(?:' +\n    innerRegex.source +\n    ')' +\n    '\\\\s*' +\n    '\\\\*/' +\n    '|' +\n    '//(?:' +\n    innerRegex.source +\n    ')' +\n    ')' +\n    '\\\\s*',\n);\n\nexport function getSourceMapURL(srcContents: string): string | null {\n  const match = srcContents.match(regex);\n  return match ? match[1] || match[2] || '' : null;\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-string-details.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {FileDetails} from '../types';\nimport {getStringHash} from './get-string-hash';\n\nexport function getStringDetails(url: string, str: string): FileDetails {\n  return {\n    file: url,\n    hash: getStringHash(str),\n    size: str.length,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/get-string-hash.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport crypto from 'crypto';\n\nexport function getStringHash(input: crypto.BinaryLike): string {\n  const md5 = crypto.createHash('md5');\n  md5.update(input);\n  return md5.digest('hex');\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/maximum-size-transform.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport prettyBytes from 'pretty-bytes';\n\nimport {ManifestTransform} from '../types';\n\nexport function maximumSizeTransform(\n  maximumFileSizeToCacheInBytes: number,\n): ManifestTransform {\n  return (originalManifest) => {\n    const warnings: Array<string> = [];\n    const manifest = originalManifest.filter((entry) => {\n      if (entry.size <= maximumFileSizeToCacheInBytes) {\n        return true;\n      }\n\n      warnings.push(\n        `${entry.url} is ${prettyBytes(entry.size)}, and won't ` +\n          `be precached. Configure maximumFileSizeToCacheInBytes to change ` +\n          `this limit.`,\n      );\n      return false;\n    });\n\n    return {manifest, warnings};\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/modify-url-prefix-transform.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {errors} from './errors';\nimport {escapeRegExp} from './escape-regexp';\nimport {ManifestTransform} from '../types';\n\nexport function modifyURLPrefixTransform(modifyURLPrefix: {\n  [key: string]: string;\n}): ManifestTransform {\n  if (\n    !modifyURLPrefix ||\n    typeof modifyURLPrefix !== 'object' ||\n    Array.isArray(modifyURLPrefix)\n  ) {\n    throw new Error(errors['modify-url-prefix-bad-prefixes']);\n  }\n\n  // If there are no entries in modifyURLPrefix, just return an identity\n  // function as a shortcut.\n  if (Object.keys(modifyURLPrefix).length === 0) {\n    return (manifest) => {\n      return {manifest};\n    };\n  }\n\n  for (const key of Object.keys(modifyURLPrefix)) {\n    if (typeof modifyURLPrefix[key] !== 'string') {\n      throw new Error(errors['modify-url-prefix-bad-prefixes']);\n    }\n  }\n\n  // Escape the user input so it's safe to use in a regex.\n  const safeModifyURLPrefixes = Object.keys(modifyURLPrefix).map(escapeRegExp);\n  // Join all the `modifyURLPrefix` keys so a single regex can be used.\n  const prefixMatchesStrings = safeModifyURLPrefixes.join('|');\n  // Add `^` to the front the prefix matches so it only matches the start of\n  // a string.\n  const modifyRegex = new RegExp(`^(${prefixMatchesStrings})`);\n\n  return (originalManifest) => {\n    const manifest = originalManifest.map((entry) => {\n      if (typeof entry.url !== 'string') {\n        throw new Error(errors['manifest-entry-bad-url']);\n      }\n\n      entry.url = entry.url.replace(modifyRegex, (match) => {\n        return modifyURLPrefix[match];\n      });\n\n      return entry;\n    });\n\n    return {manifest};\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/module-registry.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {oneLine as ol} from 'common-tags';\nimport upath from 'upath';\n\n/**\n * Class for keeping track of which Workbox modules are used by the generated\n * service worker script.\n *\n * @private\n */\nexport class ModuleRegistry {\n  private readonly _modulesUsed: Map<string, {moduleName: string; pkg: string}>;\n  /**\n   * @private\n   */\n  constructor() {\n    this._modulesUsed = new Map();\n  }\n\n  /**\n   * @return {Array<string>} A list of all of the import statements that are\n   * needed for the modules being used.\n   * @private\n   */\n  getImportStatements(): Array<string> {\n    const workboxModuleImports: Array<string> = [];\n\n    for (const [localName, {moduleName, pkg}] of this._modulesUsed) {\n      // By default require.resolve returns the resolved path of the 'main'\n      // field, which might be deeper than the package root. To work around\n      // this, we can find the package's root by resolving its package.json and\n      // strip the '/package.json' from the resolved path.\n      const pkgJsonPath = require.resolve(`${pkg}/package.json`);\n      const pkgRoot = upath.dirname(pkgJsonPath);\n      const importStatement = ol`import {${moduleName} as ${localName}} from\n        '${pkgRoot}/${moduleName}.mjs';`;\n\n      workboxModuleImports.push(importStatement);\n    }\n\n    return workboxModuleImports;\n  }\n\n  /**\n   * @param {string} pkg The workbox package that the module belongs to.\n   * @param {string} moduleName The name of the module to import.\n   * @return {string} The local variable name that corresponds to that module.\n   * @private\n   */\n  getLocalName(pkg: string, moduleName: string): string {\n    return `${pkg.replace(/-/g, '_')}_${moduleName}`;\n  }\n\n  /**\n   * @param {string} pkg The workbox package that the module belongs to.\n   * @param {string} moduleName The name of the module to import.\n   * @return {string} The local variable name that corresponds to that module.\n   * @private\n   */\n  use(pkg: string, moduleName: string): string {\n    const localName = this.getLocalName(pkg, moduleName);\n    this._modulesUsed.set(localName, {moduleName, pkg});\n\n    return localName;\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/no-revision-for-urls-matching-transform.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {errors} from './errors';\nimport {ManifestTransform} from '../types';\n\nexport function noRevisionForURLsMatchingTransform(\n  regexp: RegExp,\n): ManifestTransform {\n  if (!(regexp instanceof RegExp)) {\n    throw new Error(errors['invalid-dont-cache-bust']);\n  }\n\n  return (originalManifest) => {\n    const manifest = originalManifest.map((entry) => {\n      if (typeof entry.url !== 'string') {\n        throw new Error(errors['manifest-entry-bad-url']);\n      }\n\n      if (entry.url.match(regexp)) {\n        entry.revision = null;\n      }\n\n      return entry;\n    });\n\n    return {manifest};\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/populate-sw-template.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport template from 'lodash/template';\n\nimport {errors} from './errors';\nimport {GeneratePartial, ManifestEntry} from '../types';\nimport {ModuleRegistry} from './module-registry';\nimport {runtimeCachingConverter} from './runtime-caching-converter';\nimport {stringifyWithoutComments} from './stringify-without-comments';\nimport {swTemplate} from '../templates/sw-template';\n\nexport function populateSWTemplate({\n  cacheId,\n  cleanupOutdatedCaches,\n  clientsClaim,\n  directoryIndex,\n  disableDevLogs,\n  ignoreURLParametersMatching,\n  importScripts,\n  manifestEntries = [],\n  navigateFallback,\n  navigateFallbackDenylist,\n  navigateFallbackAllowlist,\n  navigationPreload,\n  offlineGoogleAnalytics,\n  runtimeCaching = [],\n  skipWaiting,\n}: GeneratePartial & {manifestEntries?: Array<ManifestEntry>}): string {\n  // There needs to be at least something to precache, or else runtime caching.\n  if (!(manifestEntries?.length > 0 || runtimeCaching.length > 0)) {\n    throw new Error(errors['no-manifest-entries-or-runtime-caching']);\n  }\n\n  // These are all options that can be passed to the precacheAndRoute() method.\n  const precacheOptions = {\n    directoryIndex,\n    // An array of RegExp objects can't be serialized by JSON.stringify()'s\n    // default behavior, so if it's given, convert it manually.\n    ignoreURLParametersMatching: ignoreURLParametersMatching\n      ? ([] as Array<RegExp>)\n      : undefined,\n  };\n\n  let precacheOptionsString = JSON.stringify(precacheOptions, null, 2);\n  if (ignoreURLParametersMatching) {\n    precacheOptionsString = precacheOptionsString.replace(\n      `\"ignoreURLParametersMatching\": []`,\n      `\"ignoreURLParametersMatching\": [` +\n        `${ignoreURLParametersMatching.join(', ')}]`,\n    );\n  }\n\n  let offlineAnalyticsConfigString: string | undefined = undefined;\n  if (offlineGoogleAnalytics) {\n    // If offlineGoogleAnalytics is a truthy value, we need to convert it to the\n    // format expected by the template.\n    offlineAnalyticsConfigString =\n      offlineGoogleAnalytics === true\n        ? // If it's the literal value true, then use an empty config string.\n          '{}'\n        : // Otherwise, convert the config object into a more complex string, taking\n          // into account the fact that functions might need to be stringified.\n          stringifyWithoutComments(offlineGoogleAnalytics);\n  }\n\n  const moduleRegistry = new ModuleRegistry();\n\n  try {\n    const populatedTemplate = template(swTemplate)({\n      cacheId,\n      cleanupOutdatedCaches,\n      clientsClaim,\n      disableDevLogs,\n      importScripts,\n      manifestEntries,\n      navigateFallback,\n      navigateFallbackDenylist,\n      navigateFallbackAllowlist,\n      navigationPreload,\n      offlineAnalyticsConfigString,\n      precacheOptionsString,\n      runtimeCaching: runtimeCachingConverter(moduleRegistry, runtimeCaching),\n      skipWaiting,\n      use: moduleRegistry.use.bind(moduleRegistry),\n    });\n\n    const workboxImportStatements = moduleRegistry.getImportStatements();\n\n    // We need the import statements for all of the Workbox runtime modules\n    // prepended, so that the correct bundle can be created.\n    return workboxImportStatements.join('\\n') + populatedTemplate;\n  } catch (error) {\n    throw new Error(\n      `${errors['populating-sw-tmpl-failed']} '${\n        error instanceof Error && error.message ? error.message : ''\n      }'`,\n    );\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/rebase-path.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport upath from 'upath';\n\nexport function rebasePath({\n  baseDirectory,\n  file,\n}: {\n  baseDirectory: string;\n  file: string;\n}): string {\n  // The initial path is relative to the current directory, so make it absolute.\n  const absolutePath = upath.resolve(file);\n\n  // Convert the absolute path so that it's relative to the baseDirectory.\n  const relativePath = upath.relative(baseDirectory, absolutePath);\n\n  // Remove any leading ./ as it won't work in a glob pattern.\n  const normalizedPath = upath.normalize(relativePath);\n\n  return normalizedPath;\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/replace-and-update-source-map.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RawSourceMap, SourceMapConsumer, SourceMapGenerator} from 'source-map';\n\n/**\n * Adapted from https://github.com/nsams/sourcemap-aware-replace, with modern\n * JavaScript updates, along with additional properties copied from originalMap.\n *\n * @param {Object} options\n * @param {string} options.jsFilename The name for the file whose contents\n * correspond to originalSource.\n * @param {Object} options.originalMap The sourcemap for originalSource,\n * prior to any replacements.\n * @param {string} options.originalSource The source code, prior to any\n * replacements.\n * @param {string} options.replaceString A string to swap in for searchString.\n * @param {string} options.searchString A string in originalSource to replace.\n * Only the first occurrence will be replaced.\n * @return {{source: string, map: string}} An object containing both\n * originalSource with the replacement applied, and the modified originalMap.\n *\n * @private\n */\nexport async function replaceAndUpdateSourceMap({\n  jsFilename,\n  originalMap,\n  originalSource,\n  replaceString,\n  searchString,\n}: {\n  jsFilename: string;\n  originalMap: RawSourceMap;\n  originalSource: string;\n  replaceString: string;\n  searchString: string;\n}): Promise<{map: string; source: string}> {\n  const generator = new SourceMapGenerator({\n    file: jsFilename,\n  });\n\n  const consumer = await new SourceMapConsumer(originalMap);\n\n  let pos: number;\n  let src = originalSource;\n  const replacements: Array<{line: number; column: number}> = [];\n  let lineNum = 0;\n  let filePos = 0;\n\n  const lines = src.split('\\n');\n  for (let line of lines) {\n    lineNum++;\n    let searchPos = 0;\n    while ((pos = line.indexOf(searchString, searchPos)) !== -1) {\n      src =\n        src.substring(0, filePos + pos) +\n        replaceString +\n        src.substring(filePos + pos + searchString.length);\n      line =\n        line.substring(0, pos) +\n        replaceString +\n        line.substring(pos + searchString.length);\n      replacements.push({line: lineNum, column: pos});\n      searchPos = pos + replaceString.length;\n    }\n    filePos += line.length + 1;\n  }\n\n  replacements.reverse();\n\n  consumer.eachMapping((mapping) => {\n    for (const replacement of replacements) {\n      if (\n        replacement.line === mapping.generatedLine &&\n        mapping.generatedColumn > replacement.column\n      ) {\n        const offset = searchString.length - replaceString.length;\n        mapping.generatedColumn -= offset;\n      }\n    }\n\n    if (mapping.source) {\n      const newMapping = {\n        generated: {\n          line: mapping.generatedLine,\n          column: mapping.generatedColumn,\n        },\n        original: {\n          line: mapping.originalLine,\n          column: mapping.originalColumn,\n        },\n        source: mapping.source,\n      };\n      return generator.addMapping(newMapping);\n    }\n\n    return mapping;\n  });\n\n  consumer.destroy();\n  // JSON.parse returns any.\n  // eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment\n  const updatedSourceMap: RawSourceMap = Object.assign(\n    JSON.parse(generator.toString()),\n    {\n      names: originalMap.names,\n      sourceRoot: originalMap.sourceRoot,\n      sources: originalMap.sources,\n      sourcesContent: originalMap.sourcesContent,\n    },\n  );\n\n  return {\n    map: JSON.stringify(updatedSourceMap),\n    source: src,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/runtime-caching-converter.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {oneLine as ol} from 'common-tags';\n\nimport {errors} from './errors';\nimport {ModuleRegistry} from './module-registry';\nimport {RuntimeCaching} from '../types';\nimport {stringifyWithoutComments} from './stringify-without-comments';\n\n/**\n * Given a set of options that configures runtime caching behavior, convert it\n * to the equivalent Workbox method calls.\n *\n * @param {ModuleRegistry} moduleRegistry\n * @param {Object} options See\n *        https://developers.google.com/web/tools/workbox/modules/workbox-build#generateSW-runtimeCaching\n * @return {string} A JSON string representing the equivalent options.\n *\n * @private\n */\nfunction getOptionsString(\n  moduleRegistry: ModuleRegistry,\n  options: RuntimeCaching['options'] = {},\n) {\n  const plugins: Array<string> = [];\n  const handlerOptions: {[key in keyof typeof options]: any} = {};\n\n  for (const optionName of Object.keys(options) as Array<\n    keyof typeof options\n  >) {\n    if (options[optionName] === undefined) {\n      continue;\n    }\n\n    switch (optionName) {\n      // Using a library here because JSON.stringify won't handle functions.\n      case 'plugins': {\n        plugins.push(...options.plugins!.map(stringifyWithoutComments));\n        break;\n      }\n\n      // These are the option properties that we want to pull out, so that\n      // they're passed to the handler constructor.\n      case 'cacheName':\n      case 'networkTimeoutSeconds':\n      case 'fetchOptions':\n      case 'matchOptions': {\n        handlerOptions[optionName] = options[optionName];\n        break;\n      }\n\n      // The following cases are all shorthands for creating a plugin with a\n      // given configuration.\n      case 'backgroundSync': {\n        const name = options.backgroundSync!.name;\n        const plugin = moduleRegistry.use(\n          'workbox-background-sync',\n          'BackgroundSyncPlugin',\n        );\n\n        let pluginCode = `new ${plugin}(${JSON.stringify(name)}`;\n        if (options.backgroundSync!.options) {\n          pluginCode += `, ${stringifyWithoutComments(\n            options.backgroundSync!.options,\n          )}`;\n        }\n        pluginCode += `)`;\n\n        plugins.push(pluginCode);\n        break;\n      }\n\n      case 'broadcastUpdate': {\n        const channelName = options.broadcastUpdate!.channelName;\n        const opts = Object.assign(\n          {channelName},\n          options.broadcastUpdate!.options,\n        );\n        const plugin = moduleRegistry.use(\n          'workbox-broadcast-update',\n          'BroadcastUpdatePlugin',\n        );\n\n        plugins.push(`new ${plugin}(${stringifyWithoutComments(opts)})`);\n        break;\n      }\n\n      case 'cacheableResponse': {\n        const plugin = moduleRegistry.use(\n          'workbox-cacheable-response',\n          'CacheableResponsePlugin',\n        );\n\n        plugins.push(\n          `new ${plugin}(${stringifyWithoutComments(\n            options.cacheableResponse!,\n          )})`,\n        );\n        break;\n      }\n\n      case 'expiration': {\n        const plugin = moduleRegistry.use(\n          'workbox-expiration',\n          'ExpirationPlugin',\n        );\n\n        plugins.push(\n          `new ${plugin}(${stringifyWithoutComments(options.expiration!)})`,\n        );\n        break;\n      }\n\n      case 'precacheFallback': {\n        const plugin = moduleRegistry.use(\n          'workbox-precaching',\n          'PrecacheFallbackPlugin',\n        );\n\n        plugins.push(\n          `new ${plugin}(${stringifyWithoutComments(\n            options.precacheFallback!,\n          )})`,\n        );\n        break;\n      }\n\n      case 'rangeRequests': {\n        const plugin = moduleRegistry.use(\n          'workbox-range-requests',\n          'RangeRequestsPlugin',\n        );\n\n        // There are no configuration options for the constructor.\n        plugins.push(`new ${plugin}()`);\n        break;\n      }\n\n      default: {\n        throw new Error(\n          // In the default case optionName is typed as 'never'.\n          // eslint-disable-next-line @typescript-eslint/restrict-template-expressions\n          `${errors['bad-runtime-caching-config']} ${optionName}`,\n        );\n      }\n    }\n  }\n\n  if (Object.keys(handlerOptions).length > 0 || plugins.length > 0) {\n    const optionsString = JSON.stringify(handlerOptions).slice(1, -1);\n    return ol`{\n      ${optionsString ? optionsString + ',' : ''}\n      plugins: [${plugins.join(', ')}]\n    }`;\n  } else {\n    return '';\n  }\n}\n\nexport function runtimeCachingConverter(\n  moduleRegistry: ModuleRegistry,\n  runtimeCaching: Array<RuntimeCaching>,\n): Array<string> {\n  return runtimeCaching\n    .map((entry) => {\n      const method = entry.method || 'GET';\n\n      if (!entry.urlPattern) {\n        throw new Error(errors['urlPattern-is-required']);\n      }\n\n      if (!entry.handler) {\n        throw new Error(errors['handler-is-required']);\n      }\n\n      if (\n        entry.options &&\n        entry.options.networkTimeoutSeconds &&\n        entry.handler !== 'NetworkFirst'\n      ) {\n        throw new Error(errors['invalid-network-timeout-seconds']);\n      }\n\n      // urlPattern might be a string, a RegExp object, or a function.\n      // If it's a string, it needs to be quoted.\n      const matcher =\n        typeof entry.urlPattern === 'string'\n          ? JSON.stringify(entry.urlPattern)\n          : entry.urlPattern;\n\n      const registerRoute = moduleRegistry.use(\n        'workbox-routing',\n        'registerRoute',\n      );\n      if (typeof entry.handler === 'string') {\n        const optionsString = getOptionsString(moduleRegistry, entry.options);\n        const handler = moduleRegistry.use('workbox-strategies', entry.handler);\n        const strategyString = `new ${handler}(${optionsString})`;\n\n        return `${registerRoute}(${matcher.toString()}, ${strategyString}, '${method}');\\n`;\n      } else if (typeof entry.handler === 'function') {\n        return `${registerRoute}(${matcher.toString()}, ${entry.handler.toString()}, '${method}');\\n`;\n      }\n\n      // '' will be filtered out.\n      return '';\n    })\n    .filter((entry) => Boolean(entry));\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/stringify-without-comments.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport objectStringify from 'stringify-object';\nimport stripComments from 'strip-comments';\n\nexport function stringifyWithoutComments(obj: {[key: string]: any}): string {\n  return objectStringify(obj, {\n    // See https://github.com/yeoman/stringify-object#transformobject-property-originalresult\n    transform: (_obj: {[key: string]: any}, _prop, str) => {\n      if (typeof _prop !== 'symbol' && typeof _obj[_prop] === 'function') {\n        // Can't typify correctly stripComments\n        return stripComments(str); // eslint-disable-line\n      }\n      return str;\n    },\n  });\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/transform-manifest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {\n  BasePartial,\n  FileDetails,\n  ManifestEntry,\n  ManifestTransform,\n} from '../types';\nimport {additionalManifestEntriesTransform} from './additional-manifest-entries-transform';\nimport {errors} from './errors';\nimport {maximumSizeTransform} from './maximum-size-transform';\nimport {modifyURLPrefixTransform} from './modify-url-prefix-transform';\nimport {noRevisionForURLsMatchingTransform} from './no-revision-for-urls-matching-transform';\n\n/**\n * A `ManifestTransform` function can be used to modify the modify the `url` or\n * `revision` properties of some or all of the\n * {@link workbox-build.ManifestEntry} in the manifest.\n *\n * Deleting the `revision` property of an entry will cause\n * the corresponding `url` to be precached without cache-busting parameters\n * applied, which is to say, it implies that the URL itself contains\n * proper versioning info. If the `revision` property is present, it must be\n * set to a string.\n *\n * @example A transformation that prepended the origin of a CDN for any\n * URL starting with '/assets/' could be implemented as:\n *\n * const cdnTransform = async (manifestEntries) => {\n *   const manifest = manifestEntries.map(entry => {\n *     const cdnOrigin = 'https://example.com';\n *     if (entry.url.startsWith('/assets/')) {\n *       entry.url = cdnOrigin + entry.url;\n *     }\n *     return entry;\n *   });\n *   return {manifest, warnings: []};\n * };\n *\n * @example A transformation that nulls the revision field when the\n * URL contains an 8-character hash surrounded by '.', indicating that it\n * already contains revision information:\n *\n * const removeRevisionTransform = async (manifestEntries) => {\n *   const manifest = manifestEntries.map(entry => {\n *     const hashRegExp = /\\.\\w{8}\\./;\n *     if (entry.url.match(hashRegExp)) {\n *       entry.revision = null;\n *     }\n *     return entry;\n *   });\n *   return {manifest, warnings: []};\n * };\n *\n * @callback ManifestTransform\n * @param {Array<workbox-build.ManifestEntry>} manifestEntries The full\n * array of entries, prior to the current transformation.\n * @param {Object} [compilation] When used in the webpack plugins, this param\n * will be set to the current `compilation`.\n * @return {Promise<workbox-build.ManifestTransformResult>}\n * The array of entries with the transformation applied, and optionally, any\n * warnings that should be reported back to the build tool.\n *\n * @memberof workbox-build\n */\n\ninterface ManifestTransformResultWithWarnings {\n  count: number;\n  size: number;\n  manifestEntries: ManifestEntry[];\n  warnings: string[];\n}\nexport async function transformManifest({\n  additionalManifestEntries,\n  dontCacheBustURLsMatching,\n  fileDetails,\n  manifestTransforms,\n  maximumFileSizeToCacheInBytes,\n  modifyURLPrefix,\n  transformParam,\n}: BasePartial & {\n  fileDetails: Array<FileDetails>;\n  // When this is called by the webpack plugin, transformParam will be the\n  // current webpack compilation.\n  transformParam?: unknown;\n}): Promise<ManifestTransformResultWithWarnings> {\n  const allWarnings: Array<string> = [];\n\n  // Take the array of fileDetail objects and convert it into an array of\n  // {url, revision, size} objects, with \\ replaced with /.\n  const normalizedManifest = fileDetails.map((fileDetails) => {\n    return {\n      url: fileDetails.file.replace(/\\\\/g, '/'),\n      revision: fileDetails.hash,\n      size: fileDetails.size,\n    };\n  });\n\n  const transformsToApply: Array<ManifestTransform> = [];\n\n  if (maximumFileSizeToCacheInBytes) {\n    transformsToApply.push(maximumSizeTransform(maximumFileSizeToCacheInBytes));\n  }\n\n  if (modifyURLPrefix) {\n    transformsToApply.push(modifyURLPrefixTransform(modifyURLPrefix));\n  }\n\n  if (dontCacheBustURLsMatching) {\n    transformsToApply.push(\n      noRevisionForURLsMatchingTransform(dontCacheBustURLsMatching),\n    );\n  }\n\n  // Run any manifestTransforms functions second-to-last.\n  if (manifestTransforms) {\n    transformsToApply.push(...manifestTransforms);\n  }\n\n  // Run additionalManifestEntriesTransform last.\n  if (additionalManifestEntries) {\n    transformsToApply.push(\n      additionalManifestEntriesTransform(additionalManifestEntries),\n    );\n  }\n\n  let transformedManifest: Array<ManifestEntry & {size: number}> =\n    normalizedManifest;\n  for (const transform of transformsToApply) {\n    const result = await transform(transformedManifest, transformParam);\n    if (!('manifest' in result)) {\n      throw new Error(errors['bad-manifest-transforms-return-value']);\n    }\n\n    transformedManifest = result.manifest;\n    allWarnings.push(...(result.warnings || []));\n  }\n\n  // Generate some metadata about the manifest before we clear out the size\n  // properties from each entry.\n  const count = transformedManifest.length;\n  let size = 0;\n  for (const manifestEntry of transformedManifest as Array<\n    ManifestEntry & {size?: number}\n  >) {\n    size += manifestEntry.size || 0;\n    delete manifestEntry.size;\n  }\n\n  return {\n    count,\n    size,\n    manifestEntries: transformedManifest as Array<ManifestEntry>,\n    warnings: allWarnings,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/translate-url-to-sourcemap-paths.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\nimport upath from 'upath';\n\nimport {errors} from './errors';\n\nexport function translateURLToSourcemapPaths(\n  url: string | null,\n  swSrc: string,\n  swDest: string,\n): {\n  destPath: string | undefined;\n  srcPath: string | undefined;\n  warning: string | undefined;\n} {\n  let destPath: string | undefined = undefined;\n  let srcPath: string | undefined = undefined;\n  let warning: string | undefined = undefined;\n\n  if (url && !url.startsWith('data:')) {\n    const possibleSrcPath = upath.resolve(upath.dirname(swSrc), url);\n    if (fse.existsSync(possibleSrcPath)) {\n      srcPath = possibleSrcPath;\n      destPath = upath.resolve(upath.dirname(swDest), url);\n    } else {\n      warning = `${errors['cant-find-sourcemap']} ${possibleSrcPath}`;\n    }\n  }\n\n  return {destPath, srcPath, warning};\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/validate-options.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {betterAjvErrors} from '@apideck/better-ajv-errors';\nimport {oneLine as ol} from 'common-tags';\nimport Ajv, {JSONSchemaType} from 'ajv';\n\nimport {errors} from './errors';\n\nimport {\n  GenerateSWOptions,\n  GetManifestOptions,\n  InjectManifestOptions,\n  WebpackGenerateSWOptions,\n  WebpackInjectManifestOptions,\n} from '../types';\n\ntype MethodNames =\n  | 'GenerateSW'\n  | 'GetManifest'\n  | 'InjectManifest'\n  | 'WebpackGenerateSW'\n  | 'WebpackInjectManifest';\n\nconst ajv = new Ajv({\n  useDefaults: true,\n});\n\nconst DEFAULT_EXCLUDE_VALUE = [/\\.map$/, /^manifest.*\\.js$/];\n\nexport class WorkboxConfigError extends Error {\n  constructor(message?: string) {\n    super(message);\n    Object.setPrototypeOf(this, new.target.prototype);\n  }\n}\n\n// Some methods need to do follow-up validation using the JSON schema,\n// so return both the validated options and then schema.\nfunction validate<T>(\n  input: unknown,\n  methodName: MethodNames,\n): [T, JSONSchemaType<T>] {\n  // Don't mutate input: https://github.com/GoogleChrome/workbox/issues/2158\n  const inputCopy = Object.assign({}, input);\n  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n  const jsonSchema: JSONSchemaType<T> = require(`../schema/${methodName}Options.json`);\n  const validate = ajv.compile(jsonSchema);\n  if (validate(inputCopy)) {\n    // All methods support manifestTransforms, so validate it here.\n    ensureValidManifestTransforms(inputCopy);\n    return [inputCopy, jsonSchema];\n  }\n\n  const betterErrors = betterAjvErrors({\n    basePath: methodName,\n    data: input,\n    errors: validate.errors,\n    // This is needed as JSONSchema6 is expected, but JSONSchemaType works.\n    // eslint-disable-next-line  @typescript-eslint/no-unsafe-assignment\n    schema: jsonSchema as any,\n  });\n  const messages = betterErrors.map(\n    (err) => ol`[${err.path}] ${err.message}.\n    ${err.suggestion ? err.suggestion : ''}`,\n  );\n\n  throw new WorkboxConfigError(messages.join('\\n\\n'));\n}\n\nfunction ensureValidManifestTransforms(\n  options:\n    | GenerateSWOptions\n    | GetManifestOptions\n    | InjectManifestOptions\n    | WebpackGenerateSWOptions\n    | WebpackInjectManifestOptions,\n): void {\n  if (\n    'manifestTransforms' in options &&\n    !(\n      Array.isArray(options.manifestTransforms) &&\n      options.manifestTransforms.every((item) => typeof item === 'function')\n    )\n  ) {\n    throw new WorkboxConfigError(errors['manifest-transforms']);\n  }\n}\n\nfunction ensureValidNavigationPreloadConfig(\n  options: GenerateSWOptions | WebpackGenerateSWOptions,\n): void {\n  if (\n    options.navigationPreload &&\n    (!Array.isArray(options.runtimeCaching) ||\n      options.runtimeCaching.length === 0)\n  ) {\n    throw new WorkboxConfigError(errors['nav-preload-runtime-caching']);\n  }\n}\n\nfunction ensureValidCacheExpiration(\n  options: GenerateSWOptions | WebpackGenerateSWOptions,\n): void {\n  for (const runtimeCaching of options.runtimeCaching || []) {\n    if (\n      runtimeCaching.options?.expiration &&\n      !runtimeCaching.options?.cacheName\n    ) {\n      throw new WorkboxConfigError(errors['cache-name-required']);\n    }\n  }\n}\n\nfunction ensureValidRuntimeCachingOrGlobDirectory(\n  options: GenerateSWOptions,\n): void {\n  if (\n    !options.globDirectory &&\n    (!Array.isArray(options.runtimeCaching) ||\n      options.runtimeCaching.length === 0)\n  ) {\n    throw new WorkboxConfigError(\n      errors['no-manifest-entries-or-runtime-caching'],\n    );\n  }\n}\n\n// This is... messy, because we can't rely on the built-in ajv validation for\n// runtimeCaching.handler, as it needs to accept {} (i.e. any) due to\n// https://github.com/GoogleChrome/workbox/pull/2899\n// So we need to perform validation when a string (not a function) is used.\nfunction ensureValidStringHandler(\n  options: GenerateSWOptions | WebpackGenerateSWOptions,\n  jsonSchema: JSONSchemaType<GenerateSWOptions | WebpackGenerateSWOptions>,\n): void {\n  let validHandlers: Array<string> = [];\n  /* eslint-disable */\n  for (const handler of jsonSchema.definitions?.RuntimeCaching?.properties\n    ?.handler?.anyOf || []) {\n    if ('enum' in handler) {\n      validHandlers = handler.enum;\n      break;\n    }\n  }\n  /* eslint-enable */\n\n  for (const runtimeCaching of options.runtimeCaching || []) {\n    if (\n      typeof runtimeCaching.handler === 'string' &&\n      !validHandlers.includes(runtimeCaching.handler)\n    ) {\n      throw new WorkboxConfigError(\n        errors['invalid-handler-string'] + runtimeCaching.handler,\n      );\n    }\n  }\n}\n\nexport function validateGenerateSWOptions(input: unknown): GenerateSWOptions {\n  const [validatedOptions, jsonSchema] = validate<GenerateSWOptions>(\n    input,\n    'GenerateSW',\n  );\n  ensureValidNavigationPreloadConfig(validatedOptions);\n  ensureValidCacheExpiration(validatedOptions);\n  ensureValidRuntimeCachingOrGlobDirectory(validatedOptions);\n  ensureValidStringHandler(validatedOptions, jsonSchema);\n\n  return validatedOptions;\n}\n\nexport function validateGetManifestOptions(input: unknown): GetManifestOptions {\n  const [validatedOptions] = validate<GetManifestOptions>(input, 'GetManifest');\n\n  return validatedOptions;\n}\n\nexport function validateInjectManifestOptions(\n  input: unknown,\n): InjectManifestOptions {\n  const [validatedOptions] = validate<InjectManifestOptions>(\n    input,\n    'InjectManifest',\n  );\n\n  return validatedOptions;\n}\n\n// The default `exclude: [/\\.map$/, /^manifest.*\\.js$/]` value can't be\n// represented in the JSON schema, so manually set it for the webpack options.\nexport function validateWebpackGenerateSWOptions(\n  input: unknown,\n): WebpackGenerateSWOptions {\n  const inputWithExcludeDefault = Object.assign(\n    {\n      // Make a copy, as exclude can be mutated when used.\n      exclude: Array.from(DEFAULT_EXCLUDE_VALUE),\n    },\n    input,\n  );\n  const [validatedOptions, jsonSchema] = validate<WebpackGenerateSWOptions>(\n    inputWithExcludeDefault,\n    'WebpackGenerateSW',\n  );\n\n  ensureValidNavigationPreloadConfig(validatedOptions);\n  ensureValidCacheExpiration(validatedOptions);\n  ensureValidStringHandler(validatedOptions, jsonSchema);\n\n  return validatedOptions;\n}\n\nexport function validateWebpackInjectManifestOptions(\n  input: unknown,\n): WebpackInjectManifestOptions {\n  const inputWithExcludeDefault = Object.assign(\n    {\n      // Make a copy, as exclude can be mutated when used.\n      exclude: Array.from(DEFAULT_EXCLUDE_VALUE),\n    },\n    input,\n  );\n  const [validatedOptions] = validate<WebpackInjectManifestOptions>(\n    inputWithExcludeDefault,\n    'WebpackInjectManifest',\n  );\n\n  return validatedOptions;\n}\n"
  },
  {
    "path": "packages/workbox-build/src/lib/write-sw-using-default-template.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\nimport upath from 'upath';\n\nimport {bundle} from './bundle';\nimport {errors} from './errors';\nimport {GenerateSWOptions, ManifestEntry} from '../types';\nimport {populateSWTemplate} from './populate-sw-template';\n\nexport async function writeSWUsingDefaultTemplate({\n  babelPresetEnvTargets,\n  cacheId,\n  cleanupOutdatedCaches,\n  clientsClaim,\n  directoryIndex,\n  disableDevLogs,\n  ignoreURLParametersMatching,\n  importScripts,\n  inlineWorkboxRuntime,\n  manifestEntries,\n  mode,\n  navigateFallback,\n  navigateFallbackDenylist,\n  navigateFallbackAllowlist,\n  navigationPreload,\n  offlineGoogleAnalytics,\n  runtimeCaching,\n  skipWaiting,\n  sourcemap,\n  swDest,\n}: GenerateSWOptions & {manifestEntries: Array<ManifestEntry>}): Promise<\n  Array<string>\n> {\n  const outputDir = upath.dirname(swDest);\n  try {\n    await fse.mkdirp(outputDir);\n  } catch (error) {\n    throw new Error(\n      `${errors['unable-to-make-sw-directory']}. ` +\n        `'${error instanceof Error && error.message ? error.message : ''}'`,\n    );\n  }\n\n  const unbundledCode = populateSWTemplate({\n    cacheId,\n    cleanupOutdatedCaches,\n    clientsClaim,\n    directoryIndex,\n    disableDevLogs,\n    ignoreURLParametersMatching,\n    importScripts,\n    manifestEntries,\n    navigateFallback,\n    navigateFallbackDenylist,\n    navigateFallbackAllowlist,\n    navigationPreload,\n    offlineGoogleAnalytics,\n    runtimeCaching,\n    skipWaiting,\n  });\n\n  try {\n    const files = await bundle({\n      babelPresetEnvTargets,\n      inlineWorkboxRuntime,\n      mode,\n      sourcemap,\n      swDest,\n      unbundledCode,\n    });\n\n    const filePaths: Array<string> = [];\n\n    for (const file of files) {\n      const filePath = upath.resolve(file.name);\n      filePaths.push(filePath);\n      await fse.writeFile(filePath, file.contents);\n    }\n\n    return filePaths;\n  } catch (error) {\n    const err = error as NodeJS.ErrnoException;\n    if (err.code === 'EISDIR') {\n      // See https://github.com/GoogleChrome/workbox/issues/612\n      throw new Error(errors['sw-write-failure-directory']);\n    }\n    throw new Error(`${errors['sw-write-failure']} '${err.message}'`);\n  }\n}\n"
  },
  {
    "path": "packages/workbox-build/src/rollup-plugin-off-main-thread.d.ts",
    "content": "declare module '@trickfilm400/rollup-plugin-off-main-thread';\n"
  },
  {
    "path": "packages/workbox-build/src/schema/GenerateSWOptions.json",
    "content": "{\n  \"additionalProperties\": false,\n  \"type\": \"object\",\n  \"properties\": {\n    \"additionalManifestEntries\": {\n      \"description\": \"A list of entries to be precached, in addition to any entries that are\\ngenerated as part of the build configuration.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/ManifestEntry\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"dontCacheBustURLsMatching\": {\n      \"description\": \"Assets that match this will be assumed to be uniquely versioned via their\\nURL, and exempted from the normal HTTP cache-busting that's done when\\npopulating the precache. While not required, it's recommended that if your\\nexisting build process already inserts a `[hash]` value into each filename,\\nyou provide a RegExp that will detect that, as it will reduce the bandwidth\\nconsumed when precaching.\",\n      \"$ref\": \"#/definitions/RegExp\"\n    },\n    \"manifestTransforms\": {\n      \"description\": \"One or more functions which will be applied sequentially against the\\ngenerated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\\nalso specified, their corresponding transformations will be applied first.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"maximumFileSizeToCacheInBytes\": {\n      \"description\": \"This value can be used to determine the maximum size of files that will be\\nprecached. This prevents you from inadvertently precaching very large files\\nthat might have accidentally matched one of your patterns.\",\n      \"default\": 2097152,\n      \"type\": \"number\"\n    },\n    \"modifyURLPrefix\": {\n      \"description\": \"An object mapping string prefixes to replacement string values. This can be\\nused to, e.g., remove or add a path prefix from a manifest entry if your\\nweb hosting setup doesn't match your local filesystem setup. As an\\nalternative with more flexibility, you can use the `manifestTransforms`\\noption and provide a function that modifies the entries in the manifest\\nusing whatever logic you provide.\\n\\nExample usage:\\n\\n```\\n// Replace a '/dist/' prefix with '/', and also prepend\\n// '/static' to every URL.\\nmodifyURLPrefix: {\\n  '/dist/': '/',\\n  '': '/static',\\n}\\n```\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globFollow\": {\n      \"description\": \"Determines whether or not symlinks are followed when generating the\\nprecache manifest. For more information, see the definition of `follow` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"globIgnores\": {\n      \"description\": \"A set of patterns matching files to always exclude when generating the\\nprecache manifest. For more information, see the definition of `ignore` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": [\n        \"**/node_modules/**/*\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globPatterns\": {\n      \"description\": \"Files matching any of these patterns will be included in the precache\\nmanifest. For more information, see the\\n[`glob` primer](https://github.com/isaacs/node-glob#glob-primer).\",\n      \"default\": [\n        \"**/*.{js,wasm,css,html}\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"templatedURLs\": {\n      \"description\": \"If a URL is rendered based on some server-side logic, its contents may\\ndepend on multiple files or on some other unique string value. The keys in\\nthis object are server-rendered URLs. If the values are an array of\\nstrings, they will be interpreted as `glob` patterns, and the contents of\\nany files matching the patterns will be used to uniquely version the URL.\\nIf used with a single string, it will be interpreted as unique versioning\\ninformation that you've generated for a given URL.\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"anyOf\": [\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            }\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"babelPresetEnvTargets\": {\n      \"description\": \"The [targets](https://babeljs.io/docs/en/babel-preset-env#targets) to pass\\nto `babel-preset-env` when transpiling the service worker bundle.\",\n      \"default\": [\n        \"chrome >= 56\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"cacheId\": {\n      \"description\": \"An optional ID to be prepended to cache names. This is primarily useful for\\nlocal development where multiple sites may be served from the same\\n`http://localhost:port` origin.\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"cleanupOutdatedCaches\": {\n      \"description\": \"Whether or not Workbox should attempt to identify and delete any precaches\\ncreated by older, incompatible versions.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"clientsClaim\": {\n      \"description\": \"Whether or not the service worker should [start controlling](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim)\\nany existing clients as soon as it activates.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"directoryIndex\": {\n      \"description\": \"If a navigation request for a URL ending in `/` fails to match a precached\\nURL, this value will be appended to the URL and that will be checked for a\\nprecache match. This should be set to what your web server is using for its\\ndirectory index.\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"disableDevLogs\": {\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"ignoreURLParametersMatching\": {\n      \"description\": \"Any search parameter names that match against one of the RegExp in this\\narray will be removed before looking for a precache match. This is useful\\nif your users might request URLs that contain, for example, URL parameters\\nused to track the source of the traffic. If not provided, the default value\\nis `[/^utm_/, /^fbclid$/]`.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"importScripts\": {\n      \"description\": \"A list of JavaScript files that should be passed to\\n[`importScripts()`](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts)\\ninside the generated service worker file. This is  useful when you want to\\nlet Workbox create your top-level service worker file, but want to include\\nsome additional code, such as a push event listener.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"inlineWorkboxRuntime\": {\n      \"description\": \"Whether the runtime code for the Workbox library should be included in the\\ntop-level service worker, or split into a separate file that needs to be\\ndeployed alongside the service worker. Keeping the runtime separate means\\nthat users will not have to re-download the Workbox code each time your\\ntop-level service worker changes.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"mode\": {\n      \"description\": \"If set to 'production', then an optimized service worker bundle that\\nexcludes debugging info will be produced. If not explicitly configured\\nhere, the `process.env.NODE_ENV` value will be used, and failing that, it\\nwill fall back to `'production'`.\",\n      \"default\": \"production\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"navigateFallback\": {\n      \"description\": \"If specified, all\\n[navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests)\\nfor URLs that aren't precached will be fulfilled with the HTML at the URL\\nprovided. You must pass in the URL of an HTML document that is listed in\\nyour precache manifest. This is meant to be used in a Single Page App\\nscenario, in which you want all navigations to use common\\n[App Shell HTML](https://developers.google.com/web/fundamentals/architecture/app-shell).\",\n      \"default\": null,\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"navigateFallbackAllowlist\": {\n      \"description\": \"An optional array of regular expressions that restricts which URLs the\\nconfigured `navigateFallback` behavior applies to. This is useful if only a\\nsubset of your site's URLs should be treated as being part of a\\n[Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\\nIf both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\\nconfigured, the denylist takes precedent.\\n\\n*Note*: These RegExps may be evaluated against every destination URL during\\na navigation. Avoid using\\n[complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\\nor else your users may see delays when navigating your site.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"navigateFallbackDenylist\": {\n      \"description\": \"An optional array of regular expressions that restricts which URLs the\\nconfigured `navigateFallback` behavior applies to. This is useful if only a\\nsubset of your site's URLs should be treated as being part of a\\n[Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\\nIf both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\\nconfigured, the denylist takes precedence.\\n\\n*Note*: These RegExps may be evaluated against every destination URL during\\na navigation. Avoid using\\n[complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\\nor else your users may see delays when navigating your site.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"navigationPreload\": {\n      \"description\": \"Whether or not to enable\\n[navigation preload](https://developers.google.com/web/tools/workbox/modules/workbox-navigation-preload)\\nin the generated service worker. When set to true, you must also use\\n`runtimeCaching` to set up an appropriate response strategy that will match\\nnavigation requests, and make use of the preloaded response.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"offlineGoogleAnalytics\": {\n      \"description\": \"Controls whether or not to include support for\\n[offline Google Analytics](https://developers.google.com/web/tools/workbox/guides/enable-offline-analytics).\\nWhen `true`, the call to `workbox-google-analytics`'s `initialize()` will\\nbe added to your generated service worker. When set to an `Object`, that\\nobject will be passed in to the `initialize()` call, allowing you to\\ncustomize the behavior.\",\n      \"default\": false,\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/GoogleAnalyticsInitializeOptions\"\n        },\n        {\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"runtimeCaching\": {\n      \"description\": \"When using Workbox's build tools to generate your service worker, you can\\nspecify one or more runtime caching configurations. These are then\\ntranslated to {@link workbox-routing.registerRoute} calls using the match\\nand handler configuration you define.\\n\\nFor all of the options, see the {@link workbox-build.RuntimeCaching}\\ndocumentation. The example below shows a typical configuration, with two\\nruntime routes defined:\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RuntimeCaching\"\n      }\n    },\n    \"skipWaiting\": {\n      \"description\": \"Whether to add an unconditional call to [`skipWaiting()`](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#skip_the_waiting_phase)\\nto the generated service worker. If `false`, then a `message` listener will\\nbe added instead, allowing client pages to trigger `skipWaiting()` by\\ncalling `postMessage({type: 'SKIP_WAITING'})` on a waiting service worker.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"sourcemap\": {\n      \"description\": \"Whether to create a sourcemap for the generated service worker files.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"swDest\": {\n      \"description\": \"The path and filename of the service worker file that will be created by\\nthe build process, relative to the current working directory. It must end\\nin '.js'.\",\n      \"type\": \"string\"\n    },\n    \"globDirectory\": {\n      \"description\": \"The local directory you wish to match `globPatterns` against. The path is\\nrelative to the current directory.\",\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\n    \"swDest\"\n  ],\n  \"definitions\": {\n    \"ManifestEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": [\n            \"null\",\n            \"string\"\n          ]\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"revision\",\n        \"url\"\n      ]\n    },\n    \"RegExp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        },\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"dotAll\",\n        \"flags\",\n        \"global\",\n        \"hasIndices\",\n        \"ignoreCase\",\n        \"lastIndex\",\n        \"multiline\",\n        \"source\",\n        \"sticky\",\n        \"unicode\"\n      ]\n    },\n    \"GoogleAnalyticsInitializeOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"cacheName\": {\n          \"type\": \"string\"\n        },\n        \"parameterOverrides\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"hitFilter\": {\n          \"type\": \"object\",\n          \"additionalProperties\": false\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RuntimeCaching\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"handler\": {\n          \"description\": \"This determines how the runtime route will generate a response.\\nTo use one of the built-in {@link workbox-strategies}, provide its name,\\nlike `'NetworkFirst'`.\\nAlternatively, this can be a {@link workbox-core.RouteHandler} callback\\nfunction with custom response logic.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RouteHandlerCallback\"\n            },\n            {\n              \"$ref\": \"#/definitions/RouteHandlerObject\"\n            },\n            {\n              \"enum\": [\n                \"CacheFirst\",\n                \"CacheOnly\",\n                \"NetworkFirst\",\n                \"NetworkOnly\",\n                \"StaleWhileRevalidate\"\n              ],\n              \"type\": \"string\"\n            }\n          ]\n        },\n        \"method\": {\n          \"description\": \"The HTTP method to match against. The default value of `'GET'` is normally\\nsufficient, unless you explicitly need to match `'POST'`, `'PUT'`, or\\nanother type of request.\",\n          \"default\": \"GET\",\n          \"enum\": [\n            \"DELETE\",\n            \"GET\",\n            \"HEAD\",\n            \"PATCH\",\n            \"POST\",\n            \"PUT\"\n          ],\n          \"type\": \"string\"\n        },\n        \"options\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"backgroundSync\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-background-sync.BackgroundSyncPlugin} instance to the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"options\": {\n                  \"$ref\": \"#/definitions/QueueOptions\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"name\"\n              ]\n            },\n            \"broadcastUpdate\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-broadcast-update.BroadcastUpdatePlugin} instance to the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"channelName\": {\n                  \"type\": \"string\"\n                },\n                \"options\": {\n                  \"$ref\": \"#/definitions/BroadcastCacheUpdateOptions\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"options\"\n              ]\n            },\n            \"cacheableResponse\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-cacheable-response.CacheableResponsePlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/CacheableResponseOptions\"\n            },\n            \"cacheName\": {\n              \"description\": \"If provided, this will set the `cacheName` property of the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": [\n                \"null\",\n                \"string\"\n              ]\n            },\n            \"expiration\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-expiration.ExpirationPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/ExpirationPluginOptions\"\n            },\n            \"networkTimeoutSeconds\": {\n              \"description\": \"If provided, this will set the `networkTimeoutSeconds` property of the\\n{@link workbox-strategies} configured in `handler`. Note that only\\n`'NetworkFirst'` and `'NetworkOnly'` support `networkTimeoutSeconds`.\",\n              \"type\": \"number\"\n            },\n            \"plugins\": {\n              \"description\": \"Configuring this allows the use of one or more Workbox plugins that\\ndon't have \\\"shortcut\\\" options (like `expiration` for\\n{@link workbox-expiration.ExpirationPlugin}). The plugins provided here\\nwill be added to the {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/WorkboxPlugin\"\n              }\n            },\n            \"precacheFallback\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-precaching.PrecacheFallbackPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"fallbackURL\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"fallbackURL\"\n              ]\n            },\n            \"rangeRequests\": {\n              \"description\": \"Enabling this will add a\\n{@link workbox-range-requests.RangeRequestsPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"boolean\"\n            },\n            \"fetchOptions\": {\n              \"description\": \"Configuring this will pass along the `fetchOptions` value to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/RequestInit\"\n            },\n            \"matchOptions\": {\n              \"description\": \"Configuring this will pass along the `matchOptions` value to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/CacheQueryOptions\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        \"urlPattern\": {\n          \"description\": \"This match criteria determines whether the configured handler will\\ngenerate a response for any requests that don't match one of the precached\\nURLs. If multiple `RuntimeCaching` routes are defined, then the first one\\nwhose `urlPattern` matches will be the one that responds.\\n\\nThis value directly maps to the first parameter passed to\\n{@link workbox-routing.registerRoute}. It's recommended to use a\\n{@link workbox-core.RouteMatchCallback} function for greatest flexibility.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RegExp\"\n            },\n            {\n              \"$ref\": \"#/definitions/RouteMatchCallback\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"handler\",\n        \"urlPattern\"\n      ]\n    },\n    \"RouteHandlerCallback\": {},\n    \"RouteHandlerObject\": {\n      \"description\": \"An object with a `handle` method of type `RouteHandlerCallback`.\\n\\nA `Route` object can be created with either an `RouteHandlerCallback`\\nfunction or this `RouteHandler` object. The benefit of the `RouteHandler`\\nis it can be extended (as is done by the `workbox-strategies` package).\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"handle\": {\n          \"$ref\": \"#/definitions/RouteHandlerCallback\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"handle\"\n      ]\n    },\n    \"QueueOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"forceSyncFallback\": {\n          \"type\": \"boolean\"\n        },\n        \"maxRetentionTime\": {\n          \"type\": \"number\"\n        },\n        \"onSync\": {\n          \"$ref\": \"#/definitions/OnSyncCallback\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"OnSyncCallback\": {},\n    \"BroadcastCacheUpdateOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"headersToCheck\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"generatePayload\": {\n          \"type\": \"object\",\n          \"additionalProperties\": false\n        },\n        \"notifyAllClients\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheableResponseOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"statuses\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"number\"\n          }\n        },\n        \"headers\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"ExpirationPluginOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"maxEntries\": {\n          \"type\": \"number\"\n        },\n        \"maxAgeSeconds\": {\n          \"type\": \"number\"\n        },\n        \"matchOptions\": {\n          \"$ref\": \"#/definitions/CacheQueryOptions\"\n        },\n        \"purgeOnQuotaError\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheQueryOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ignoreMethod\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreSearch\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreVary\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WorkboxPlugin\": {\n      \"description\": \"An object with optional lifecycle callback properties for the fetch and\\ncache operations.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"cacheDidUpdate\": {},\n        \"cachedResponseWillBeUsed\": {},\n        \"cacheKeyWillBeUsed\": {},\n        \"cacheWillUpdate\": {},\n        \"fetchDidFail\": {},\n        \"fetchDidSucceed\": {},\n        \"handlerDidComplete\": {},\n        \"handlerDidError\": {},\n        \"handlerDidRespond\": {},\n        \"handlerWillRespond\": {},\n        \"handlerWillStart\": {},\n        \"requestWillFetch\": {}\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheDidUpdateCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CachedResponseWillBeUsedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CacheKeyWillBeUsedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CacheWillUpdateCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"FetchDidFailCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"FetchDidSucceedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidCompleteCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidErrorCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidRespondCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerWillRespondCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerWillStartCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"RequestWillFetchCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"RequestInit\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"body\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/ArrayBuffer\"\n            },\n            {\n              \"$ref\": \"#/definitions/ArrayBufferView\"\n            },\n            {\n              \"$ref\": \"#/definitions/ReadableStream<any>\"\n            },\n            {\n              \"$ref\": \"#/definitions/Blob\"\n            },\n            {\n              \"$ref\": \"#/definitions/FormData\"\n            },\n            {\n              \"$ref\": \"#/definitions/URLSearchParams\"\n            },\n            {\n              \"type\": [\n                \"null\",\n                \"string\"\n              ]\n            }\n          ]\n        },\n        \"cache\": {\n          \"enum\": [\n            \"default\",\n            \"force-cache\",\n            \"no-cache\",\n            \"no-store\",\n            \"only-if-cached\",\n            \"reload\"\n          ],\n          \"type\": \"string\"\n        },\n        \"credentials\": {\n          \"enum\": [\n            \"include\",\n            \"omit\",\n            \"same-origin\"\n          ],\n          \"type\": \"string\"\n        },\n        \"headers\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Record<string,string>\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"array\",\n                \"items\": [\n                  {\n                    \"type\": \"string\"\n                  },\n                  {\n                    \"type\": \"string\"\n                  }\n                ],\n                \"minItems\": 2,\n                \"maxItems\": 2\n              }\n            },\n            {\n              \"$ref\": \"#/definitions/Headers\"\n            }\n          ]\n        },\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"keepalive\": {\n          \"type\": \"boolean\"\n        },\n        \"method\": {\n          \"type\": \"string\"\n        },\n        \"mode\": {\n          \"enum\": [\n            \"cors\",\n            \"navigate\",\n            \"no-cors\",\n            \"same-origin\"\n          ],\n          \"type\": \"string\"\n        },\n        \"redirect\": {\n          \"enum\": [\n            \"error\",\n            \"follow\",\n            \"manual\"\n          ],\n          \"type\": \"string\"\n        },\n        \"referrer\": {\n          \"type\": \"string\"\n        },\n        \"referrerPolicy\": {\n          \"enum\": [\n            \"\",\n            \"no-referrer\",\n            \"no-referrer-when-downgrade\",\n            \"origin\",\n            \"origin-when-cross-origin\",\n            \"same-origin\",\n            \"strict-origin\",\n            \"strict-origin-when-cross-origin\",\n            \"unsafe-url\"\n          ],\n          \"type\": \"string\"\n        },\n        \"signal\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/AbortSignal\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"window\": {\n          \"type\": \"null\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"ArrayBuffer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"__@toStringTag@34\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"__@toStringTag@34\",\n        \"byteLength\"\n      ]\n    },\n    \"ArrayBufferView\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"buffer\": {\n          \"$ref\": \"#/definitions/ArrayBufferLike\"\n        },\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"byteOffset\": {\n          \"type\": \"number\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"buffer\",\n        \"byteLength\",\n        \"byteOffset\"\n      ]\n    },\n    \"ArrayBufferLike\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/ArrayBuffer\"\n        },\n        {\n          \"$ref\": \"#/definitions/SharedArrayBuffer\"\n        }\n      ]\n    },\n    \"SharedArrayBuffer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"__@species@494\": {\n          \"$ref\": \"#/definitions/SharedArrayBuffer\"\n        },\n        \"__@toStringTag@34\": {\n          \"type\": \"string\",\n          \"const\": \"SharedArrayBuffer\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"__@species@494\",\n        \"__@toStringTag@34\",\n        \"byteLength\"\n      ]\n    },\n    \"ReadableStream<any>\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locked\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"locked\"\n      ]\n    },\n    \"Blob\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"size\": {\n          \"type\": \"number\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"size\",\n        \"type\"\n      ]\n    },\n    \"FormData\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"URLSearchParams\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"size\": {\n          \"description\": \"The total number of parameter entries.\",\n          \"type\": \"number\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"size\"\n      ]\n    },\n    \"Record<string,string>\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"Headers\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"AbortSignal\": {},\n    \"RouteMatchCallback\": {}\n  },\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/schema/GetManifestOptions.json",
    "content": "{\n  \"additionalProperties\": false,\n  \"type\": \"object\",\n  \"properties\": {\n    \"additionalManifestEntries\": {\n      \"description\": \"A list of entries to be precached, in addition to any entries that are\\ngenerated as part of the build configuration.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/ManifestEntry\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"dontCacheBustURLsMatching\": {\n      \"description\": \"Assets that match this will be assumed to be uniquely versioned via their\\nURL, and exempted from the normal HTTP cache-busting that's done when\\npopulating the precache. While not required, it's recommended that if your\\nexisting build process already inserts a `[hash]` value into each filename,\\nyou provide a RegExp that will detect that, as it will reduce the bandwidth\\nconsumed when precaching.\",\n      \"$ref\": \"#/definitions/RegExp\"\n    },\n    \"manifestTransforms\": {\n      \"description\": \"One or more functions which will be applied sequentially against the\\ngenerated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\\nalso specified, their corresponding transformations will be applied first.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"maximumFileSizeToCacheInBytes\": {\n      \"description\": \"This value can be used to determine the maximum size of files that will be\\nprecached. This prevents you from inadvertently precaching very large files\\nthat might have accidentally matched one of your patterns.\",\n      \"default\": 2097152,\n      \"type\": \"number\"\n    },\n    \"modifyURLPrefix\": {\n      \"description\": \"An object mapping string prefixes to replacement string values. This can be\\nused to, e.g., remove or add a path prefix from a manifest entry if your\\nweb hosting setup doesn't match your local filesystem setup. As an\\nalternative with more flexibility, you can use the `manifestTransforms`\\noption and provide a function that modifies the entries in the manifest\\nusing whatever logic you provide.\\n\\nExample usage:\\n\\n```\\n// Replace a '/dist/' prefix with '/', and also prepend\\n// '/static' to every URL.\\nmodifyURLPrefix: {\\n  '/dist/': '/',\\n  '': '/static',\\n}\\n```\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globFollow\": {\n      \"description\": \"Determines whether or not symlinks are followed when generating the\\nprecache manifest. For more information, see the definition of `follow` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"globIgnores\": {\n      \"description\": \"A set of patterns matching files to always exclude when generating the\\nprecache manifest. For more information, see the definition of `ignore` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": [\n        \"**/node_modules/**/*\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globPatterns\": {\n      \"description\": \"Files matching any of these patterns will be included in the precache\\nmanifest. For more information, see the\\n[`glob` primer](https://github.com/isaacs/node-glob#glob-primer).\",\n      \"default\": [\n        \"**/*.{js,wasm,css,html}\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"templatedURLs\": {\n      \"description\": \"If a URL is rendered based on some server-side logic, its contents may\\ndepend on multiple files or on some other unique string value. The keys in\\nthis object are server-rendered URLs. If the values are an array of\\nstrings, they will be interpreted as `glob` patterns, and the contents of\\nany files matching the patterns will be used to uniquely version the URL.\\nIf used with a single string, it will be interpreted as unique versioning\\ninformation that you've generated for a given URL.\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"anyOf\": [\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            }\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"globDirectory\": {\n      \"description\": \"The local directory you wish to match `globPatterns` against. The path is\\nrelative to the current directory.\",\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\n    \"globDirectory\"\n  ],\n  \"definitions\": {\n    \"ManifestEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": [\n            \"null\",\n            \"string\"\n          ]\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"revision\",\n        \"url\"\n      ]\n    },\n    \"RegExp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        },\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"dotAll\",\n        \"flags\",\n        \"global\",\n        \"hasIndices\",\n        \"ignoreCase\",\n        \"lastIndex\",\n        \"multiline\",\n        \"source\",\n        \"sticky\",\n        \"unicode\"\n      ]\n    }\n  },\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/schema/InjectManifestOptions.json",
    "content": "{\n  \"additionalProperties\": false,\n  \"type\": \"object\",\n  \"properties\": {\n    \"additionalManifestEntries\": {\n      \"description\": \"A list of entries to be precached, in addition to any entries that are\\ngenerated as part of the build configuration.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/ManifestEntry\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"dontCacheBustURLsMatching\": {\n      \"description\": \"Assets that match this will be assumed to be uniquely versioned via their\\nURL, and exempted from the normal HTTP cache-busting that's done when\\npopulating the precache. While not required, it's recommended that if your\\nexisting build process already inserts a `[hash]` value into each filename,\\nyou provide a RegExp that will detect that, as it will reduce the bandwidth\\nconsumed when precaching.\",\n      \"$ref\": \"#/definitions/RegExp\"\n    },\n    \"manifestTransforms\": {\n      \"description\": \"One or more functions which will be applied sequentially against the\\ngenerated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\\nalso specified, their corresponding transformations will be applied first.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"maximumFileSizeToCacheInBytes\": {\n      \"description\": \"This value can be used to determine the maximum size of files that will be\\nprecached. This prevents you from inadvertently precaching very large files\\nthat might have accidentally matched one of your patterns.\",\n      \"default\": 2097152,\n      \"type\": \"number\"\n    },\n    \"modifyURLPrefix\": {\n      \"description\": \"An object mapping string prefixes to replacement string values. This can be\\nused to, e.g., remove or add a path prefix from a manifest entry if your\\nweb hosting setup doesn't match your local filesystem setup. As an\\nalternative with more flexibility, you can use the `manifestTransforms`\\noption and provide a function that modifies the entries in the manifest\\nusing whatever logic you provide.\\n\\nExample usage:\\n\\n```\\n// Replace a '/dist/' prefix with '/', and also prepend\\n// '/static' to every URL.\\nmodifyURLPrefix: {\\n  '/dist/': '/',\\n  '': '/static',\\n}\\n```\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globFollow\": {\n      \"description\": \"Determines whether or not symlinks are followed when generating the\\nprecache manifest. For more information, see the definition of `follow` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"globIgnores\": {\n      \"description\": \"A set of patterns matching files to always exclude when generating the\\nprecache manifest. For more information, see the definition of `ignore` in\\nthe `glob` [documentation](https://github.com/isaacs/node-glob#options).\",\n      \"default\": [\n        \"**/node_modules/**/*\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"globPatterns\": {\n      \"description\": \"Files matching any of these patterns will be included in the precache\\nmanifest. For more information, see the\\n[`glob` primer](https://github.com/isaacs/node-glob#glob-primer).\",\n      \"default\": [\n        \"**/*.{js,wasm,css,html}\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"templatedURLs\": {\n      \"description\": \"If a URL is rendered based on some server-side logic, its contents may\\ndepend on multiple files or on some other unique string value. The keys in\\nthis object are server-rendered URLs. If the values are an array of\\nstrings, they will be interpreted as `glob` patterns, and the contents of\\nany files matching the patterns will be used to uniquely version the URL.\\nIf used with a single string, it will be interpreted as unique versioning\\ninformation that you've generated for a given URL.\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"anyOf\": [\n          {\n            \"type\": \"array\",\n            \"items\": {\n              \"type\": \"string\"\n            }\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"injectionPoint\": {\n      \"description\": \"The string to find inside of the `swSrc` file. Once found, it will be\\nreplaced by the generated precache manifest.\",\n      \"default\": \"self.__WB_MANIFEST\",\n      \"type\": \"string\"\n    },\n    \"swSrc\": {\n      \"description\": \"The path and filename of the service worker file that will be read during\\nthe build process, relative to the current working directory.\",\n      \"type\": \"string\"\n    },\n    \"swDest\": {\n      \"description\": \"The path and filename of the service worker file that will be created by\\nthe build process, relative to the current working directory. It must end\\nin '.js'.\",\n      \"type\": \"string\"\n    },\n    \"globDirectory\": {\n      \"description\": \"The local directory you wish to match `globPatterns` against. The path is\\nrelative to the current directory.\",\n      \"type\": \"string\"\n    }\n  },\n  \"required\": [\n    \"globDirectory\",\n    \"swDest\",\n    \"swSrc\"\n  ],\n  \"definitions\": {\n    \"ManifestEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": [\n            \"null\",\n            \"string\"\n          ]\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"revision\",\n        \"url\"\n      ]\n    },\n    \"RegExp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        },\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"dotAll\",\n        \"flags\",\n        \"global\",\n        \"hasIndices\",\n        \"ignoreCase\",\n        \"lastIndex\",\n        \"multiline\",\n        \"source\",\n        \"sticky\",\n        \"unicode\"\n      ]\n    }\n  },\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/schema/WebpackGenerateSWOptions.json",
    "content": "{\n  \"additionalProperties\": false,\n  \"type\": \"object\",\n  \"properties\": {\n    \"additionalManifestEntries\": {\n      \"description\": \"A list of entries to be precached, in addition to any entries that are\\ngenerated as part of the build configuration.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/ManifestEntry\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"dontCacheBustURLsMatching\": {\n      \"description\": \"Assets that match this will be assumed to be uniquely versioned via their\\nURL, and exempted from the normal HTTP cache-busting that's done when\\npopulating the precache. While not required, it's recommended that if your\\nexisting build process already inserts a `[hash]` value into each filename,\\nyou provide a RegExp that will detect that, as it will reduce the bandwidth\\nconsumed when precaching.\",\n      \"$ref\": \"#/definitions/RegExp\"\n    },\n    \"manifestTransforms\": {\n      \"description\": \"One or more functions which will be applied sequentially against the\\ngenerated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\\nalso specified, their corresponding transformations will be applied first.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"maximumFileSizeToCacheInBytes\": {\n      \"description\": \"This value can be used to determine the maximum size of files that will be\\nprecached. This prevents you from inadvertently precaching very large files\\nthat might have accidentally matched one of your patterns.\",\n      \"default\": 2097152,\n      \"type\": \"number\"\n    },\n    \"modifyURLPrefix\": {\n      \"description\": \"An object mapping string prefixes to replacement string values. This can be\\nused to, e.g., remove or add a path prefix from a manifest entry if your\\nweb hosting setup doesn't match your local filesystem setup. As an\\nalternative with more flexibility, you can use the `manifestTransforms`\\noption and provide a function that modifies the entries in the manifest\\nusing whatever logic you provide.\\n\\nExample usage:\\n\\n```\\n// Replace a '/dist/' prefix with '/', and also prepend\\n// '/static' to every URL.\\nmodifyURLPrefix: {\\n  '/dist/': '/',\\n  '': '/static',\\n}\\n```\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      }\n    },\n    \"chunks\": {\n      \"description\": \"One or more chunk names whose corresponding output files should be included\\nin the precache manifest.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"exclude\": {\n      \"description\": \"One or more specifiers used to exclude assets from the precache manifest.\\nThis is interpreted following\\n[the same rules](https://webpack.js.org/configuration/module/#condition)\\nas `webpack`'s standard `exclude` option.\\nIf not provided, the default value is `[/\\\\.map$/, /^manifest.*\\\\.js$]`.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"excludeChunks\": {\n      \"description\": \"One or more chunk names whose corresponding output files should be excluded\\nfrom the precache manifest.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"include\": {\n      \"description\": \"One or more specifiers used to include assets in the precache manifest.\\nThis is interpreted following\\n[the same rules](https://webpack.js.org/configuration/module/#condition)\\nas `webpack`'s standard `include` option.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"mode\": {\n      \"description\": \"If set to 'production', then an optimized service worker bundle that\\nexcludes debugging info will be produced. If not explicitly configured\\nhere, the `process.env.NODE_ENV` value will be used, and failing that, it\\nwill fall back to `'production'`.\",\n      \"default\": \"production\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"babelPresetEnvTargets\": {\n      \"description\": \"The [targets](https://babeljs.io/docs/en/babel-preset-env#targets) to pass\\nto `babel-preset-env` when transpiling the service worker bundle.\",\n      \"default\": [\n        \"chrome >= 56\"\n      ],\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"cacheId\": {\n      \"description\": \"An optional ID to be prepended to cache names. This is primarily useful for\\nlocal development where multiple sites may be served from the same\\n`http://localhost:port` origin.\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"cleanupOutdatedCaches\": {\n      \"description\": \"Whether or not Workbox should attempt to identify and delete any precaches\\ncreated by older, incompatible versions.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"clientsClaim\": {\n      \"description\": \"Whether or not the service worker should [start controlling](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim)\\nany existing clients as soon as it activates.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"directoryIndex\": {\n      \"description\": \"If a navigation request for a URL ending in `/` fails to match a precached\\nURL, this value will be appended to the URL and that will be checked for a\\nprecache match. This should be set to what your web server is using for its\\ndirectory index.\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"disableDevLogs\": {\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"ignoreURLParametersMatching\": {\n      \"description\": \"Any search parameter names that match against one of the RegExp in this\\narray will be removed before looking for a precache match. This is useful\\nif your users might request URLs that contain, for example, URL parameters\\nused to track the source of the traffic. If not provided, the default value\\nis `[/^utm_/, /^fbclid$/]`.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"importScripts\": {\n      \"description\": \"A list of JavaScript files that should be passed to\\n[`importScripts()`](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts)\\ninside the generated service worker file. This is  useful when you want to\\nlet Workbox create your top-level service worker file, but want to include\\nsome additional code, such as a push event listener.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"inlineWorkboxRuntime\": {\n      \"description\": \"Whether the runtime code for the Workbox library should be included in the\\ntop-level service worker, or split into a separate file that needs to be\\ndeployed alongside the service worker. Keeping the runtime separate means\\nthat users will not have to re-download the Workbox code each time your\\ntop-level service worker changes.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"navigateFallback\": {\n      \"description\": \"If specified, all\\n[navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests)\\nfor URLs that aren't precached will be fulfilled with the HTML at the URL\\nprovided. You must pass in the URL of an HTML document that is listed in\\nyour precache manifest. This is meant to be used in a Single Page App\\nscenario, in which you want all navigations to use common\\n[App Shell HTML](https://developers.google.com/web/fundamentals/architecture/app-shell).\",\n      \"default\": null,\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"navigateFallbackAllowlist\": {\n      \"description\": \"An optional array of regular expressions that restricts which URLs the\\nconfigured `navigateFallback` behavior applies to. This is useful if only a\\nsubset of your site's URLs should be treated as being part of a\\n[Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\\nIf both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\\nconfigured, the denylist takes precedent.\\n\\n*Note*: These RegExps may be evaluated against every destination URL during\\na navigation. Avoid using\\n[complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\\nor else your users may see delays when navigating your site.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"navigateFallbackDenylist\": {\n      \"description\": \"An optional array of regular expressions that restricts which URLs the\\nconfigured `navigateFallback` behavior applies to. This is useful if only a\\nsubset of your site's URLs should be treated as being part of a\\n[Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\\nIf both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\\nconfigured, the denylist takes precedence.\\n\\n*Note*: These RegExps may be evaluated against every destination URL during\\na navigation. Avoid using\\n[complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\\nor else your users may see delays when navigating your site.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RegExp\"\n      }\n    },\n    \"navigationPreload\": {\n      \"description\": \"Whether or not to enable\\n[navigation preload](https://developers.google.com/web/tools/workbox/modules/workbox-navigation-preload)\\nin the generated service worker. When set to true, you must also use\\n`runtimeCaching` to set up an appropriate response strategy that will match\\nnavigation requests, and make use of the preloaded response.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"offlineGoogleAnalytics\": {\n      \"description\": \"Controls whether or not to include support for\\n[offline Google Analytics](https://developers.google.com/web/tools/workbox/guides/enable-offline-analytics).\\nWhen `true`, the call to `workbox-google-analytics`'s `initialize()` will\\nbe added to your generated service worker. When set to an `Object`, that\\nobject will be passed in to the `initialize()` call, allowing you to\\ncustomize the behavior.\",\n      \"default\": false,\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/GoogleAnalyticsInitializeOptions\"\n        },\n        {\n          \"type\": \"boolean\"\n        }\n      ]\n    },\n    \"runtimeCaching\": {\n      \"description\": \"When using Workbox's build tools to generate your service worker, you can\\nspecify one or more runtime caching configurations. These are then\\ntranslated to {@link workbox-routing.registerRoute} calls using the match\\nand handler configuration you define.\\n\\nFor all of the options, see the {@link workbox-build.RuntimeCaching}\\ndocumentation. The example below shows a typical configuration, with two\\nruntime routes defined:\",\n      \"type\": \"array\",\n      \"items\": {\n        \"$ref\": \"#/definitions/RuntimeCaching\"\n      }\n    },\n    \"skipWaiting\": {\n      \"description\": \"Whether to add an unconditional call to [`skipWaiting()`](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#skip_the_waiting_phase)\\nto the generated service worker. If `false`, then a `message` listener will\\nbe added instead, allowing client pages to trigger `skipWaiting()` by\\ncalling `postMessage({type: 'SKIP_WAITING'})` on a waiting service worker.\",\n      \"default\": false,\n      \"type\": \"boolean\"\n    },\n    \"sourcemap\": {\n      \"description\": \"Whether to create a sourcemap for the generated service worker files.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"importScriptsViaChunks\": {\n      \"description\": \"One or more names of webpack chunks. The content of those chunks will be\\nincluded in the generated service worker, via a call to `importScripts()`.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"swDest\": {\n      \"description\": \"The asset name of the service worker file created by this plugin.\",\n      \"default\": \"service-worker.js\",\n      \"type\": \"string\"\n    }\n  },\n  \"definitions\": {\n    \"ManifestEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": [\n            \"null\",\n            \"string\"\n          ]\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"revision\",\n        \"url\"\n      ]\n    },\n    \"RegExp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        },\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"dotAll\",\n        \"flags\",\n        \"global\",\n        \"hasIndices\",\n        \"ignoreCase\",\n        \"lastIndex\",\n        \"multiline\",\n        \"source\",\n        \"sticky\",\n        \"unicode\"\n      ]\n    },\n    \"GoogleAnalyticsInitializeOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"cacheName\": {\n          \"type\": \"string\"\n        },\n        \"parameterOverrides\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        },\n        \"hitFilter\": {\n          \"type\": \"object\",\n          \"additionalProperties\": false\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"RuntimeCaching\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"handler\": {\n          \"description\": \"This determines how the runtime route will generate a response.\\nTo use one of the built-in {@link workbox-strategies}, provide its name,\\nlike `'NetworkFirst'`.\\nAlternatively, this can be a {@link workbox-core.RouteHandler} callback\\nfunction with custom response logic.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RouteHandlerCallback\"\n            },\n            {\n              \"$ref\": \"#/definitions/RouteHandlerObject\"\n            },\n            {\n              \"enum\": [\n                \"CacheFirst\",\n                \"CacheOnly\",\n                \"NetworkFirst\",\n                \"NetworkOnly\",\n                \"StaleWhileRevalidate\"\n              ],\n              \"type\": \"string\"\n            }\n          ]\n        },\n        \"method\": {\n          \"description\": \"The HTTP method to match against. The default value of `'GET'` is normally\\nsufficient, unless you explicitly need to match `'POST'`, `'PUT'`, or\\nanother type of request.\",\n          \"default\": \"GET\",\n          \"enum\": [\n            \"DELETE\",\n            \"GET\",\n            \"HEAD\",\n            \"PATCH\",\n            \"POST\",\n            \"PUT\"\n          ],\n          \"type\": \"string\"\n        },\n        \"options\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"backgroundSync\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-background-sync.BackgroundSyncPlugin} instance to the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"name\": {\n                  \"type\": \"string\"\n                },\n                \"options\": {\n                  \"$ref\": \"#/definitions/QueueOptions\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"name\"\n              ]\n            },\n            \"broadcastUpdate\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-broadcast-update.BroadcastUpdatePlugin} instance to the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"channelName\": {\n                  \"type\": \"string\"\n                },\n                \"options\": {\n                  \"$ref\": \"#/definitions/BroadcastCacheUpdateOptions\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"options\"\n              ]\n            },\n            \"cacheableResponse\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-cacheable-response.CacheableResponsePlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/CacheableResponseOptions\"\n            },\n            \"cacheName\": {\n              \"description\": \"If provided, this will set the `cacheName` property of the\\n{@link workbox-strategies} configured in `handler`.\",\n              \"type\": [\n                \"null\",\n                \"string\"\n              ]\n            },\n            \"expiration\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-expiration.ExpirationPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/ExpirationPluginOptions\"\n            },\n            \"networkTimeoutSeconds\": {\n              \"description\": \"If provided, this will set the `networkTimeoutSeconds` property of the\\n{@link workbox-strategies} configured in `handler`. Note that only\\n`'NetworkFirst'` and `'NetworkOnly'` support `networkTimeoutSeconds`.\",\n              \"type\": \"number\"\n            },\n            \"plugins\": {\n              \"description\": \"Configuring this allows the use of one or more Workbox plugins that\\ndon't have \\\"shortcut\\\" options (like `expiration` for\\n{@link workbox-expiration.ExpirationPlugin}). The plugins provided here\\nwill be added to the {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"array\",\n              \"items\": {\n                \"$ref\": \"#/definitions/WorkboxPlugin\"\n              }\n            },\n            \"precacheFallback\": {\n              \"description\": \"Configuring this will add a\\n{@link workbox-precaching.PrecacheFallbackPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"object\",\n              \"properties\": {\n                \"fallbackURL\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"additionalProperties\": false,\n              \"required\": [\n                \"fallbackURL\"\n              ]\n            },\n            \"rangeRequests\": {\n              \"description\": \"Enabling this will add a\\n{@link workbox-range-requests.RangeRequestsPlugin} instance to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"type\": \"boolean\"\n            },\n            \"fetchOptions\": {\n              \"description\": \"Configuring this will pass along the `fetchOptions` value to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/RequestInit\"\n            },\n            \"matchOptions\": {\n              \"description\": \"Configuring this will pass along the `matchOptions` value to\\nthe {@link workbox-strategies} configured in `handler`.\",\n              \"$ref\": \"#/definitions/CacheQueryOptions\"\n            }\n          },\n          \"additionalProperties\": false\n        },\n        \"urlPattern\": {\n          \"description\": \"This match criteria determines whether the configured handler will\\ngenerate a response for any requests that don't match one of the precached\\nURLs. If multiple `RuntimeCaching` routes are defined, then the first one\\nwhose `urlPattern` matches will be the one that responds.\\n\\nThis value directly maps to the first parameter passed to\\n{@link workbox-routing.registerRoute}. It's recommended to use a\\n{@link workbox-core.RouteMatchCallback} function for greatest flexibility.\",\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/RegExp\"\n            },\n            {\n              \"$ref\": \"#/definitions/RouteMatchCallback\"\n            },\n            {\n              \"type\": \"string\"\n            }\n          ]\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"handler\",\n        \"urlPattern\"\n      ]\n    },\n    \"RouteHandlerCallback\": {},\n    \"RouteHandlerObject\": {\n      \"description\": \"An object with a `handle` method of type `RouteHandlerCallback`.\\n\\nA `Route` object can be created with either an `RouteHandlerCallback`\\nfunction or this `RouteHandler` object. The benefit of the `RouteHandler`\\nis it can be extended (as is done by the `workbox-strategies` package).\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"handle\": {\n          \"$ref\": \"#/definitions/RouteHandlerCallback\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"handle\"\n      ]\n    },\n    \"QueueOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"forceSyncFallback\": {\n          \"type\": \"boolean\"\n        },\n        \"maxRetentionTime\": {\n          \"type\": \"number\"\n        },\n        \"onSync\": {\n          \"$ref\": \"#/definitions/OnSyncCallback\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"OnSyncCallback\": {},\n    \"BroadcastCacheUpdateOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"headersToCheck\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          }\n        },\n        \"generatePayload\": {\n          \"type\": \"object\",\n          \"additionalProperties\": false\n        },\n        \"notifyAllClients\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheableResponseOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"statuses\": {\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"number\"\n          }\n        },\n        \"headers\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\n            \"type\": \"string\"\n          }\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"ExpirationPluginOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"maxEntries\": {\n          \"type\": \"number\"\n        },\n        \"maxAgeSeconds\": {\n          \"type\": \"number\"\n        },\n        \"matchOptions\": {\n          \"$ref\": \"#/definitions/CacheQueryOptions\"\n        },\n        \"purgeOnQuotaError\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheQueryOptions\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"ignoreMethod\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreSearch\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreVary\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"WorkboxPlugin\": {\n      \"description\": \"An object with optional lifecycle callback properties for the fetch and\\ncache operations.\",\n      \"type\": \"object\",\n      \"properties\": {\n        \"cacheDidUpdate\": {},\n        \"cachedResponseWillBeUsed\": {},\n        \"cacheKeyWillBeUsed\": {},\n        \"cacheWillUpdate\": {},\n        \"fetchDidFail\": {},\n        \"fetchDidSucceed\": {},\n        \"handlerDidComplete\": {},\n        \"handlerDidError\": {},\n        \"handlerDidRespond\": {},\n        \"handlerWillRespond\": {},\n        \"handlerWillStart\": {},\n        \"requestWillFetch\": {}\n      },\n      \"additionalProperties\": false\n    },\n    \"CacheDidUpdateCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CachedResponseWillBeUsedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CacheKeyWillBeUsedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"CacheWillUpdateCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"FetchDidFailCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"FetchDidSucceedCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidCompleteCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidErrorCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerDidRespondCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerWillRespondCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"HandlerWillStartCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"RequestWillFetchCallback\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"RequestInit\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"body\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/ArrayBuffer\"\n            },\n            {\n              \"$ref\": \"#/definitions/ArrayBufferView\"\n            },\n            {\n              \"$ref\": \"#/definitions/ReadableStream<any>\"\n            },\n            {\n              \"$ref\": \"#/definitions/Blob\"\n            },\n            {\n              \"$ref\": \"#/definitions/FormData\"\n            },\n            {\n              \"$ref\": \"#/definitions/URLSearchParams\"\n            },\n            {\n              \"type\": [\n                \"null\",\n                \"string\"\n              ]\n            }\n          ]\n        },\n        \"cache\": {\n          \"enum\": [\n            \"default\",\n            \"force-cache\",\n            \"no-cache\",\n            \"no-store\",\n            \"only-if-cached\",\n            \"reload\"\n          ],\n          \"type\": \"string\"\n        },\n        \"credentials\": {\n          \"enum\": [\n            \"include\",\n            \"omit\",\n            \"same-origin\"\n          ],\n          \"type\": \"string\"\n        },\n        \"headers\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/Record<string,string>\"\n            },\n            {\n              \"type\": \"array\",\n              \"items\": {\n                \"type\": \"array\",\n                \"items\": [\n                  {\n                    \"type\": \"string\"\n                  },\n                  {\n                    \"type\": \"string\"\n                  }\n                ],\n                \"minItems\": 2,\n                \"maxItems\": 2\n              }\n            },\n            {\n              \"$ref\": \"#/definitions/Headers\"\n            }\n          ]\n        },\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"keepalive\": {\n          \"type\": \"boolean\"\n        },\n        \"method\": {\n          \"type\": \"string\"\n        },\n        \"mode\": {\n          \"enum\": [\n            \"cors\",\n            \"navigate\",\n            \"no-cors\",\n            \"same-origin\"\n          ],\n          \"type\": \"string\"\n        },\n        \"redirect\": {\n          \"enum\": [\n            \"error\",\n            \"follow\",\n            \"manual\"\n          ],\n          \"type\": \"string\"\n        },\n        \"referrer\": {\n          \"type\": \"string\"\n        },\n        \"referrerPolicy\": {\n          \"enum\": [\n            \"\",\n            \"no-referrer\",\n            \"no-referrer-when-downgrade\",\n            \"origin\",\n            \"origin-when-cross-origin\",\n            \"same-origin\",\n            \"strict-origin\",\n            \"strict-origin-when-cross-origin\",\n            \"unsafe-url\"\n          ],\n          \"type\": \"string\"\n        },\n        \"signal\": {\n          \"anyOf\": [\n            {\n              \"$ref\": \"#/definitions/AbortSignal\"\n            },\n            {\n              \"type\": \"null\"\n            }\n          ]\n        },\n        \"window\": {\n          \"type\": \"null\"\n        }\n      },\n      \"additionalProperties\": false\n    },\n    \"ArrayBuffer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"__@toStringTag@34\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"__@toStringTag@34\",\n        \"byteLength\"\n      ]\n    },\n    \"ArrayBufferView\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"buffer\": {\n          \"$ref\": \"#/definitions/ArrayBufferLike\"\n        },\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"byteOffset\": {\n          \"type\": \"number\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"buffer\",\n        \"byteLength\",\n        \"byteOffset\"\n      ]\n    },\n    \"ArrayBufferLike\": {\n      \"anyOf\": [\n        {\n          \"$ref\": \"#/definitions/ArrayBuffer\"\n        },\n        {\n          \"$ref\": \"#/definitions/SharedArrayBuffer\"\n        }\n      ]\n    },\n    \"SharedArrayBuffer\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"byteLength\": {\n          \"type\": \"number\"\n        },\n        \"__@species@494\": {\n          \"$ref\": \"#/definitions/SharedArrayBuffer\"\n        },\n        \"__@toStringTag@34\": {\n          \"type\": \"string\",\n          \"const\": \"SharedArrayBuffer\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"__@species@494\",\n        \"__@toStringTag@34\",\n        \"byteLength\"\n      ]\n    },\n    \"ReadableStream<any>\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"locked\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"locked\"\n      ]\n    },\n    \"Blob\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"size\": {\n          \"type\": \"number\"\n        },\n        \"type\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"size\",\n        \"type\"\n      ]\n    },\n    \"FormData\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"URLSearchParams\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"size\": {\n          \"description\": \"The total number of parameter entries.\",\n          \"type\": \"number\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"size\"\n      ]\n    },\n    \"Record<string,string>\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"Headers\": {\n      \"type\": \"object\",\n      \"additionalProperties\": false\n    },\n    \"AbortSignal\": {},\n    \"RouteMatchCallback\": {}\n  },\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/schema/WebpackInjectManifestOptions.json",
    "content": "{\n  \"additionalProperties\": false,\n  \"type\": \"object\",\n  \"properties\": {\n    \"additionalManifestEntries\": {\n      \"description\": \"A list of entries to be precached, in addition to any entries that are\\ngenerated as part of the build configuration.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"anyOf\": [\n          {\n            \"$ref\": \"#/definitions/ManifestEntry\"\n          },\n          {\n            \"type\": \"string\"\n          }\n        ]\n      }\n    },\n    \"dontCacheBustURLsMatching\": {\n      \"description\": \"Assets that match this will be assumed to be uniquely versioned via their\\nURL, and exempted from the normal HTTP cache-busting that's done when\\npopulating the precache. While not required, it's recommended that if your\\nexisting build process already inserts a `[hash]` value into each filename,\\nyou provide a RegExp that will detect that, as it will reduce the bandwidth\\nconsumed when precaching.\",\n      \"$ref\": \"#/definitions/RegExp\"\n    },\n    \"manifestTransforms\": {\n      \"description\": \"One or more functions which will be applied sequentially against the\\ngenerated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\\nalso specified, their corresponding transformations will be applied first.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"maximumFileSizeToCacheInBytes\": {\n      \"description\": \"This value can be used to determine the maximum size of files that will be\\nprecached. This prevents you from inadvertently precaching very large files\\nthat might have accidentally matched one of your patterns.\",\n      \"default\": 2097152,\n      \"type\": \"number\"\n    },\n    \"modifyURLPrefix\": {\n      \"description\": \"An object mapping string prefixes to replacement string values. This can be\\nused to, e.g., remove or add a path prefix from a manifest entry if your\\nweb hosting setup doesn't match your local filesystem setup. As an\\nalternative with more flexibility, you can use the `manifestTransforms`\\noption and provide a function that modifies the entries in the manifest\\nusing whatever logic you provide.\\n\\nExample usage:\\n\\n```\\n// Replace a '/dist/' prefix with '/', and also prepend\\n// '/static' to every URL.\\nmodifyURLPrefix: {\\n  '/dist/': '/',\\n  '': '/static',\\n}\\n```\",\n      \"type\": \"object\",\n      \"additionalProperties\": {\n        \"type\": \"string\"\n      }\n    },\n    \"chunks\": {\n      \"description\": \"One or more chunk names whose corresponding output files should be included\\nin the precache manifest.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"exclude\": {\n      \"description\": \"One or more specifiers used to exclude assets from the precache manifest.\\nThis is interpreted following\\n[the same rules](https://webpack.js.org/configuration/module/#condition)\\nas `webpack`'s standard `exclude` option.\\nIf not provided, the default value is `[/\\\\.map$/, /^manifest.*\\\\.js$]`.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"excludeChunks\": {\n      \"description\": \"One or more chunk names whose corresponding output files should be excluded\\nfrom the precache manifest.\",\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"string\"\n      }\n    },\n    \"include\": {\n      \"description\": \"One or more specifiers used to include assets in the precache manifest.\\nThis is interpreted following\\n[the same rules](https://webpack.js.org/configuration/module/#condition)\\nas `webpack`'s standard `include` option.\",\n      \"type\": \"array\",\n      \"items\": {}\n    },\n    \"mode\": {\n      \"description\": \"If set to 'production', then an optimized service worker bundle that\\nexcludes debugging info will be produced. If not explicitly configured\\nhere, the `mode` value configured in the current `webpack` compilation\\nwill be used.\",\n      \"type\": [\n        \"null\",\n        \"string\"\n      ]\n    },\n    \"injectionPoint\": {\n      \"description\": \"The string to find inside of the `swSrc` file. Once found, it will be\\nreplaced by the generated precache manifest.\",\n      \"default\": \"self.__WB_MANIFEST\",\n      \"type\": \"string\"\n    },\n    \"swSrc\": {\n      \"description\": \"The path and filename of the service worker file that will be read during\\nthe build process, relative to the current working directory.\",\n      \"type\": \"string\"\n    },\n    \"compileSrc\": {\n      \"description\": \"When `true` (the default), the `swSrc` file will be compiled by webpack.\\nWhen `false`, compilation will not occur (and `webpackCompilationPlugins`\\ncan't be used.) Set to `false` if you want to inject the manifest into,\\ne.g., a JSON file.\",\n      \"default\": true,\n      \"type\": \"boolean\"\n    },\n    \"swDest\": {\n      \"description\": \"The asset name of the service worker file that will be created by this\\nplugin. If omitted, the name will be based on the `swSrc` name.\",\n      \"type\": \"string\"\n    },\n    \"webpackCompilationPlugins\": {\n      \"description\": \"Optional `webpack` plugins that will be used when compiling the `swSrc`\\ninput file. Only valid if `compileSrc` is `true`.\",\n      \"type\": \"array\",\n      \"items\": {}\n    }\n  },\n  \"required\": [\n    \"swSrc\"\n  ],\n  \"definitions\": {\n    \"ManifestEntry\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"integrity\": {\n          \"type\": \"string\"\n        },\n        \"revision\": {\n          \"type\": [\n            \"null\",\n            \"string\"\n          ]\n        },\n        \"url\": {\n          \"type\": \"string\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"revision\",\n        \"url\"\n      ]\n    },\n    \"RegExp\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"source\": {\n          \"type\": \"string\"\n        },\n        \"global\": {\n          \"type\": \"boolean\"\n        },\n        \"ignoreCase\": {\n          \"type\": \"boolean\"\n        },\n        \"multiline\": {\n          \"type\": \"boolean\"\n        },\n        \"lastIndex\": {\n          \"type\": \"number\"\n        },\n        \"flags\": {\n          \"type\": \"string\"\n        },\n        \"sticky\": {\n          \"type\": \"boolean\"\n        },\n        \"unicode\": {\n          \"type\": \"boolean\"\n        },\n        \"dotAll\": {\n          \"type\": \"boolean\"\n        },\n        \"hasIndices\": {\n          \"type\": \"boolean\"\n        }\n      },\n      \"additionalProperties\": false,\n      \"required\": [\n        \"dotAll\",\n        \"flags\",\n        \"global\",\n        \"hasIndices\",\n        \"ignoreCase\",\n        \"lastIndex\",\n        \"multiline\",\n        \"source\",\n        \"sticky\",\n        \"unicode\"\n      ]\n    }\n  },\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\"\n}\n"
  },
  {
    "path": "packages/workbox-build/src/strip-comments.d.ts",
    "content": "declare module 'strip-comments';\n"
  },
  {
    "path": "packages/workbox-build/src/templates/sw-template.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nexport const swTemplate = `/**\n * Welcome to your Workbox-powered service worker!\n *\n * You'll need to register this file in your web app.\n * See https://goo.gl/nhQhGp\n *\n * The rest of the code is auto-generated. Please don't update this file\n * directly; instead, make changes to your Workbox build configuration\n * and re-run your build process.\n * See https://goo.gl/2aRDsh\n */\n\n<% if (importScripts) { %>\nimportScripts(\n  <%= importScripts.map(JSON.stringify).join(',\\\\n  ') %>\n);\n<% } %>\n\n<% if (navigationPreload) { %><%= use('workbox-navigation-preload', 'enable') %>();<% } %>\n\n<% if (cacheId) { %><%= use('workbox-core', 'setCacheNameDetails') %>({prefix: <%= JSON.stringify(cacheId) %>});<% } %>\n\n<% if (skipWaiting) { %>\nself.skipWaiting();\n<% } else { %>\nself.addEventListener('message', (event) => {\n  if (event.data && event.data.type === 'SKIP_WAITING') {\n    self.skipWaiting();\n  }\n});\n<% } %>\n<% if (clientsClaim) { %><%= use('workbox-core', 'clientsClaim') %>();<% } %>\n\n<% if (Array.isArray(manifestEntries) && manifestEntries.length > 0) {%>\n/**\n * The precacheAndRoute() method efficiently caches and responds to\n * requests for URLs in the manifest.\n * See https://goo.gl/S9QRab\n */\n<%= use('workbox-precaching', 'precacheAndRoute') %>(<%= JSON.stringify(manifestEntries, null, 2) %>, <%= precacheOptionsString %>);\n<% if (cleanupOutdatedCaches) { %><%= use('workbox-precaching', 'cleanupOutdatedCaches') %>();<% } %>\n<% if (navigateFallback) { %><%= use('workbox-routing', 'registerRoute') %>(new <%= use('workbox-routing', 'NavigationRoute') %>(<%= use('workbox-precaching', 'createHandlerBoundToURL') %>(<%= JSON.stringify(navigateFallback) %>)<% if (navigateFallbackAllowlist || navigateFallbackDenylist) { %>, {\n  <% if (navigateFallbackAllowlist) { %>allowlist: [<%= navigateFallbackAllowlist %>],<% } %>\n  <% if (navigateFallbackDenylist) { %>denylist: [<%= navigateFallbackDenylist %>],<% } %>\n}<% } %>));<% } %>\n<% } %>\n\n<% if (runtimeCaching) { runtimeCaching.forEach(runtimeCachingString => {%><%= runtimeCachingString %><% });} %>\n\n<% if (offlineAnalyticsConfigString) { %><%= use('workbox-google-analytics', 'initialize') %>(<%= offlineAnalyticsConfigString %>);<% } %>\n\n<% if (disableDevLogs) { %>self.__WB_DISABLE_DEV_LOGS = true;<% } %>`;\n"
  },
  {
    "path": "packages/workbox-build/src/types.ts",
    "content": "import {PackageJson} from 'type-fest';\n\nimport {BroadcastCacheUpdateOptions} from 'workbox-broadcast-update/BroadcastCacheUpdate';\nimport {GoogleAnalyticsInitializeOptions} from 'workbox-google-analytics/initialize';\nimport {HTTPMethod} from 'workbox-routing/utils/constants';\nimport {QueueOptions} from 'workbox-background-sync/Queue';\nimport {RouteHandler, RouteMatchCallback} from 'workbox-core/types';\nimport {CacheableResponseOptions} from 'workbox-cacheable-response/CacheableResponse';\nimport {ExpirationPluginOptions} from 'workbox-expiration/ExpirationPlugin';\nimport {WorkboxPlugin} from 'workbox-core/types';\n\nexport interface ManifestEntry {\n  integrity?: string;\n  revision: string | null;\n  url: string;\n}\n\nexport type StrategyName =\n  | 'CacheFirst'\n  | 'CacheOnly'\n  | 'NetworkFirst'\n  | 'NetworkOnly'\n  | 'StaleWhileRevalidate';\n\nexport interface RuntimeCaching {\n  /**\n   * This determines how the runtime route will generate a response.\n   * To use one of the built-in {@link workbox-strategies}, provide its name,\n   * like `'NetworkFirst'`.\n   * Alternatively, this can be a {@link workbox-core.RouteHandler} callback\n   * function with custom response logic.\n   */\n  handler: RouteHandler | StrategyName;\n  /**\n   * The HTTP method to match against. The default value of `'GET'` is normally\n   * sufficient, unless you explicitly need to match `'POST'`, `'PUT'`, or\n   * another type of request.\n   * @default \"GET\"\n   */\n  method?: HTTPMethod;\n  options?: {\n    /**\n     * Configuring this will add a\n     * {@link workbox-background-sync.BackgroundSyncPlugin} instance to the\n     * {@link workbox-strategies} configured in `handler`.\n     */\n    backgroundSync?: {\n      name: string;\n      options?: QueueOptions;\n    };\n    /**\n     * Configuring this will add a\n     * {@link workbox-broadcast-update.BroadcastUpdatePlugin} instance to the\n     * {@link workbox-strategies} configured in `handler`.\n     */\n    broadcastUpdate?: {\n      // TODO: This option is ignored since we switched to using postMessage().\n      // Remove it in the next major release.\n      channelName?: string;\n      options: BroadcastCacheUpdateOptions;\n    };\n    /**\n     * Configuring this will add a\n     * {@link workbox-cacheable-response.CacheableResponsePlugin} instance to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    cacheableResponse?: CacheableResponseOptions;\n    /**\n     * If provided, this will set the `cacheName` property of the\n     * {@link workbox-strategies} configured in `handler`.\n     */\n    cacheName?: string | null;\n    /**\n     * Configuring this will add a\n     * {@link workbox-expiration.ExpirationPlugin} instance to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    expiration?: ExpirationPluginOptions;\n    /**\n     * If provided, this will set the `networkTimeoutSeconds` property of the\n     * {@link workbox-strategies} configured in `handler`. Note that only\n     * `'NetworkFirst'` and `'NetworkOnly'` support `networkTimeoutSeconds`.\n     */\n    networkTimeoutSeconds?: number;\n    /**\n     * Configuring this allows the use of one or more Workbox plugins that\n     * don't have \"shortcut\" options (like `expiration` for\n     * {@link workbox-expiration.ExpirationPlugin}). The plugins provided here\n     * will be added to the {@link workbox-strategies} configured in `handler`.\n     */\n    plugins?: Array<WorkboxPlugin>;\n    /**\n     * Configuring this will add a\n     * {@link workbox-precaching.PrecacheFallbackPlugin} instance to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    precacheFallback?: {\n      fallbackURL: string;\n    };\n    /**\n     * Enabling this will add a\n     * {@link workbox-range-requests.RangeRequestsPlugin} instance to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    rangeRequests?: boolean;\n    /**\n     * Configuring this will pass along the `fetchOptions` value to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    fetchOptions?: RequestInit;\n    /**\n     * Configuring this will pass along the `matchOptions` value to\n     * the {@link workbox-strategies} configured in `handler`.\n     */\n    matchOptions?: CacheQueryOptions;\n  };\n  /**\n   * This match criteria determines whether the configured handler will\n   * generate a response for any requests that don't match one of the precached\n   * URLs. If multiple `RuntimeCaching` routes are defined, then the first one\n   * whose `urlPattern` matches will be the one that responds.\n   *\n   * This value directly maps to the first parameter passed to\n   * {@link workbox-routing.registerRoute}. It's recommended to use a\n   * {@link workbox-core.RouteMatchCallback} function for greatest flexibility.\n   */\n  urlPattern: RegExp | string | RouteMatchCallback;\n}\n\nexport interface ManifestTransformResult {\n  manifest: Array<ManifestEntry & {size: number}>;\n  warnings?: Array<string>;\n}\n\nexport type ManifestTransform = (\n  manifestEntries: Array<ManifestEntry & {size: number}>,\n  compilation?: unknown,\n) => Promise<ManifestTransformResult> | ManifestTransformResult;\n\nexport interface BasePartial {\n  /**\n   * A list of entries to be precached, in addition to any entries that are\n   * generated as part of the build configuration.\n   */\n  additionalManifestEntries?: Array<string | ManifestEntry>;\n  /**\n   * Assets that match this will be assumed to be uniquely versioned via their\n   * URL, and exempted from the normal HTTP cache-busting that's done when\n   * populating the precache. While not required, it's recommended that if your\n   * existing build process already inserts a `[hash]` value into each filename,\n   * you provide a RegExp that will detect that, as it will reduce the bandwidth\n   * consumed when precaching.\n   */\n  dontCacheBustURLsMatching?: RegExp;\n  /**\n   * One or more functions which will be applied sequentially against the\n   * generated manifest. If `modifyURLPrefix` or `dontCacheBustURLsMatching` are\n   * also specified, their corresponding transformations will be applied first.\n   */\n  manifestTransforms?: Array<ManifestTransform>;\n  /**\n   * This value can be used to determine the maximum size of files that will be\n   * precached. This prevents you from inadvertently precaching very large files\n   * that might have accidentally matched one of your patterns.\n   * @default 2097152\n   */\n  maximumFileSizeToCacheInBytes?: number;\n  /**\n   * An object mapping string prefixes to replacement string values. This can be\n   * used to, e.g., remove or add a path prefix from a manifest entry if your\n   * web hosting setup doesn't match your local filesystem setup. As an\n   * alternative with more flexibility, you can use the `manifestTransforms`\n   * option and provide a function that modifies the entries in the manifest\n   * using whatever logic you provide.\n   *\n   * Example usage:\n   *\n   * ```\n   * // Replace a '/dist/' prefix with '/', and also prepend\n   * // '/static' to every URL.\n   * modifyURLPrefix: {\n   *   '/dist/': '/',\n   *   '': '/static',\n   * }\n   * ```\n   */\n  modifyURLPrefix?: {\n    [key: string]: string;\n  };\n}\n\nexport interface GeneratePartial {\n  /**\n   * The [targets](https://babeljs.io/docs/en/babel-preset-env#targets) to pass\n   * to `babel-preset-env` when transpiling the service worker bundle.\n   * @default [\"chrome >= 56\"]\n   */\n  babelPresetEnvTargets?: Array<string>;\n  /**\n   * An optional ID to be prepended to cache names. This is primarily useful for\n   * local development where multiple sites may be served from the same\n   * `http://localhost:port` origin.\n   */\n  cacheId?: string | null;\n  /**\n   * Whether or not Workbox should attempt to identify and delete any precaches\n   * created by older, incompatible versions.\n   * @default false\n   */\n  cleanupOutdatedCaches?: boolean;\n  /**\n   * Whether or not the service worker should [start controlling](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#clientsclaim)\n   * any existing clients as soon as it activates.\n   * @default false\n   */\n  clientsClaim?: boolean;\n  /**\n   * If a navigation request for a URL ending in `/` fails to match a precached\n   * URL, this value will be appended to the URL and that will be checked for a\n   * precache match. This should be set to what your web server is using for its\n   * directory index.\n   */\n  directoryIndex?: string | null;\n  /**\n   * @default false\n   */\n  disableDevLogs?: boolean;\n  // We can't use the @default annotation here to assign the value via AJV, as\n  // an Array<RegExp> can't be serialized into JSON.\n  /**\n   * Any search parameter names that match against one of the RegExp in this\n   * array will be removed before looking for a precache match. This is useful\n   * if your users might request URLs that contain, for example, URL parameters\n   * used to track the source of the traffic. If not provided, the default value\n   * is `[/^utm_/, /^fbclid$/]`.\n   *\n   */\n  ignoreURLParametersMatching?: Array<RegExp>;\n  /**\n   * A list of JavaScript files that should be passed to\n   * [`importScripts()`](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/importScripts)\n   * inside the generated service worker file. This is  useful when you want to\n   * let Workbox create your top-level service worker file, but want to include\n   * some additional code, such as a push event listener.\n   */\n  importScripts?: Array<string>;\n  /**\n   * Whether the runtime code for the Workbox library should be included in the\n   * top-level service worker, or split into a separate file that needs to be\n   * deployed alongside the service worker. Keeping the runtime separate means\n   * that users will not have to re-download the Workbox code each time your\n   * top-level service worker changes.\n   * @default false\n   */\n  inlineWorkboxRuntime?: boolean;\n  /**\n   * If set to 'production', then an optimized service worker bundle that\n   * excludes debugging info will be produced. If not explicitly configured\n   * here, the `process.env.NODE_ENV` value will be used, and failing that, it\n   * will fall back to `'production'`.\n   * @default \"production\"\n   */\n  mode?: string | null;\n  /**\n   * If specified, all\n   * [navigation requests](https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests)\n   * for URLs that aren't precached will be fulfilled with the HTML at the URL\n   * provided. You must pass in the URL of an HTML document that is listed in\n   * your precache manifest. This is meant to be used in a Single Page App\n   * scenario, in which you want all navigations to use common\n   * [App Shell HTML](https://developers.google.com/web/fundamentals/architecture/app-shell).\n   * @default null\n   */\n  navigateFallback?: string | null;\n  /**\n   * An optional array of regular expressions that restricts which URLs the\n   * configured `navigateFallback` behavior applies to. This is useful if only a\n   * subset of your site's URLs should be treated as being part of a\n   * [Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\n   * If both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\n   * configured, the denylist takes precedent.\n   *\n   * *Note*: These RegExps may be evaluated against every destination URL during\n   * a navigation. Avoid using\n   * [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\n   * or else your users may see delays when navigating your site.\n   */\n  navigateFallbackAllowlist?: Array<RegExp>;\n  /**\n   * An optional array of regular expressions that restricts which URLs the\n   * configured `navigateFallback` behavior applies to. This is useful if only a\n   * subset of your site's URLs should be treated as being part of a\n   * [Single Page App](https://en.wikipedia.org/wiki/Single-page_application).\n   * If both `navigateFallbackDenylist` and `navigateFallbackAllowlist` are\n   * configured, the denylist takes precedence.\n   *\n   * *Note*: These RegExps may be evaluated against every destination URL during\n   * a navigation. Avoid using\n   * [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\n   * or else your users may see delays when navigating your site.\n   */\n  navigateFallbackDenylist?: Array<RegExp>;\n  /**\n   * Whether or not to enable\n   * [navigation preload](https://developers.google.com/web/tools/workbox/modules/workbox-navigation-preload)\n   * in the generated service worker. When set to true, you must also use\n   * `runtimeCaching` to set up an appropriate response strategy that will match\n   * navigation requests, and make use of the preloaded response.\n   * @default false\n   */\n  navigationPreload?: boolean;\n  /**\n   * Controls whether or not to include support for\n   * [offline Google Analytics](https://developers.google.com/web/tools/workbox/guides/enable-offline-analytics).\n   * When `true`, the call to `workbox-google-analytics`'s `initialize()` will\n   * be added to your generated service worker. When set to an `Object`, that\n   * object will be passed in to the `initialize()` call, allowing you to\n   * customize the behavior.\n   * @default false\n   */\n  offlineGoogleAnalytics?: boolean | GoogleAnalyticsInitializeOptions;\n  /**\n   * When using Workbox's build tools to generate your service worker, you can\n   * specify one or more runtime caching configurations. These are then\n   * translated to {@link workbox-routing.registerRoute} calls using the match\n   * and handler configuration you define.\n   *\n   * For all of the options, see the {@link workbox-build.RuntimeCaching}\n   * documentation. The example below shows a typical configuration, with two\n   * runtime routes defined:\n   *\n   * @example\n   * runtimeCaching: [{\n   *   urlPattern: ({url}) => url.origin === 'https://api.example.com',\n   *   handler: 'NetworkFirst',\n   *   options: {\n   *     cacheName: 'api-cache',\n   *   },\n   * }, {\n   *   urlPattern: ({request}) => request.destination === 'image',\n   *   handler: 'StaleWhileRevalidate',\n   *   options: {\n   *     cacheName: 'images-cache',\n   *     expiration: {\n   *       maxEntries: 10,\n   *     },\n   *   },\n   * }]\n   */\n  runtimeCaching?: Array<RuntimeCaching>;\n  /**\n   * Whether to add an unconditional call to [`skipWaiting()`](https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#skip_the_waiting_phase)\n   * to the generated service worker. If `false`, then a `message` listener will\n   * be added instead, allowing client pages to trigger `skipWaiting()` by\n   * calling `postMessage({type: 'SKIP_WAITING'})` on a waiting service worker.\n   * @default false\n   */\n  skipWaiting?: boolean;\n  /**\n   * Whether to create a sourcemap for the generated service worker files.\n   * @default true\n   */\n  sourcemap?: boolean;\n}\n\n// This needs to be set when using GetManifest or InjectManifest, but is\n// optional when using GenerateSW if runtimeCaching is also used. This is\n// enforced via runtime validation, and needs to be documented.\nexport interface RequiredGlobDirectoryPartial {\n  /**\n   * The local directory you wish to match `globPatterns` against. The path is\n   * relative to the current directory.\n   */\n  globDirectory: string;\n}\n\nexport interface OptionalGlobDirectoryPartial {\n  /**\n   * The local directory you wish to match `globPatterns` against. The path is\n   * relative to the current directory.\n   */\n  globDirectory?: string;\n}\n\nexport interface GlobPartial {\n  /**\n   * Determines whether or not symlinks are followed when generating the\n   * precache manifest. For more information, see the definition of `follow` in\n   * the `glob` [documentation](https://github.com/isaacs/node-glob#options).\n   * @default true\n   */\n  globFollow?: boolean;\n  /**\n   * A set of patterns matching files to always exclude when generating the\n   * precache manifest. For more information, see the definition of `ignore` in\n   * the `glob` [documentation](https://github.com/isaacs/node-glob#options).\n   * @default [\"**\\/node_modules\\/**\\/*\"]\n   */\n  globIgnores?: Array<string>;\n  /**\n   * Files matching any of these patterns will be included in the precache\n   * manifest. For more information, see the\n   * [`glob` primer](https://github.com/isaacs/node-glob#glob-primer).\n   * @default [\"**\\/*.{js,wasm,css,html}\"]\n   */\n  globPatterns?: Array<string>;\n  /**\n   * If a URL is rendered based on some server-side logic, its contents may\n   * depend on multiple files or on some other unique string value. The keys in\n   * this object are server-rendered URLs. If the values are an array of\n   * strings, they will be interpreted as `glob` patterns, and the contents of\n   * any files matching the patterns will be used to uniquely version the URL.\n   * If used with a single string, it will be interpreted as unique versioning\n   * information that you've generated for a given URL.\n   */\n  templatedURLs?: {\n    [key: string]: string | Array<string>;\n  };\n}\n\nexport interface InjectPartial {\n  /**\n   * The string to find inside of the `swSrc` file. Once found, it will be\n   * replaced by the generated precache manifest.\n   * @default \"self.__WB_MANIFEST\"\n   */\n  injectionPoint?: string;\n  /**\n   * The path and filename of the service worker file that will be read during\n   * the build process, relative to the current working directory.\n   */\n  swSrc: string;\n}\n\nexport interface WebpackPartial {\n  /**\n   * One or more chunk names whose corresponding output files should be included\n   * in the precache manifest.\n   */\n  chunks?: Array<string>;\n  // We can't use the @default annotation here to assign the value via AJV, as\n  // an Array<RegExp> can't be serialized into JSON.\n  // The default value of [/\\.map$/, /^manifest.*\\.js$/] will be assigned by\n  // the validation function, and we need to reflect that in the docs.\n  /**\n   * One or more specifiers used to exclude assets from the precache manifest.\n   * This is interpreted following\n   * [the same rules](https://webpack.js.org/configuration/module/#condition)\n   * as `webpack`'s standard `exclude` option.\n   * If not provided, the default value is `[/\\.map$/, /^manifest.*\\.js$]`.\n   */\n  //eslint-disable-next-line @typescript-eslint/ban-types\n  exclude?: Array<string | RegExp | ((arg0: any) => boolean)>;\n  /**\n   * One or more chunk names whose corresponding output files should be excluded\n   * from the precache manifest.\n   */\n  excludeChunks?: Array<string>;\n  /**\n   * One or more specifiers used to include assets in the precache manifest.\n   * This is interpreted following\n   * [the same rules](https://webpack.js.org/configuration/module/#condition)\n   * as `webpack`'s standard `include` option.\n   */\n  //eslint-disable-next-line @typescript-eslint/ban-types\n  include?: Array<string | RegExp | ((arg0: any) => boolean)>;\n  /**\n   * If set to 'production', then an optimized service worker bundle that\n   * excludes debugging info will be produced. If not explicitly configured\n   * here, the `mode` value configured in the current `webpack` compilation\n   * will be used.\n   */\n  mode?: string | null;\n}\n\nexport interface RequiredSWDestPartial {\n  /**\n   * The path and filename of the service worker file that will be created by\n   * the build process, relative to the current working directory. It must end\n   * in '.js'.\n   */\n  swDest: string;\n}\n\nexport interface WebpackGenerateSWPartial {\n  /**\n   * One or more names of webpack chunks. The content of those chunks will be\n   * included in the generated service worker, via a call to `importScripts()`.\n   */\n  importScriptsViaChunks?: Array<string>;\n  /**\n   * The asset name of the service worker file created by this plugin.\n   * @default \"service-worker.js\"\n   */\n  swDest?: string;\n}\n\nexport interface WebpackInjectManifestPartial {\n  /**\n   * When `true` (the default), the `swSrc` file will be compiled by webpack.\n   * When `false`, compilation will not occur (and `webpackCompilationPlugins`\n   * can't be used.) Set to `false` if you want to inject the manifest into,\n   * e.g., a JSON file.\n   * @default true\n   */\n  compileSrc?: boolean;\n  // This doesn't have a hardcoded default value; instead, the default will be\n  // set at runtime to the swSrc basename, with the hardcoded extension .js.\n  /**\n   * The asset name of the service worker file that will be created by this\n   * plugin. If omitted, the name will be based on the `swSrc` name.\n   */\n  swDest?: string;\n  // This can only be set if compileSrc is true, but that restriction can't be\n  // represented in TypeScript. It's enforced via custom runtime validation\n  // logic and needs to be documented.\n  /**\n   * Optional `webpack` plugins that will be used when compiling the `swSrc`\n   * input file. Only valid if `compileSrc` is `true`.\n   */\n  webpackCompilationPlugins?: Array<any>;\n}\n\nexport type GenerateSWOptions = BasePartial &\n  GlobPartial &\n  GeneratePartial &\n  RequiredSWDestPartial &\n  OptionalGlobDirectoryPartial;\n\nexport type GetManifestOptions = BasePartial &\n  GlobPartial &\n  RequiredGlobDirectoryPartial;\n\nexport type InjectManifestOptions = BasePartial &\n  GlobPartial &\n  InjectPartial &\n  RequiredSWDestPartial &\n  RequiredGlobDirectoryPartial;\n\nexport type WebpackGenerateSWOptions = BasePartial &\n  WebpackPartial &\n  GeneratePartial &\n  WebpackGenerateSWPartial;\n\nexport type WebpackInjectManifestOptions = BasePartial &\n  WebpackPartial &\n  InjectPartial &\n  WebpackInjectManifestPartial;\n\nexport interface GetManifestResult {\n  count: number;\n  manifestEntries: Array<ManifestEntry>;\n  size: number;\n  warnings: Array<string>;\n}\n\nexport type BuildResult = Omit<GetManifestResult, 'manifestEntries'> & {\n  filePaths: Array<string>;\n};\n\n/**\n * @private\n */\nexport interface FileDetails {\n  file: string;\n  hash: string;\n  size: number;\n}\n\n/**\n * @private\n */\nexport type BuildType = 'dev' | 'prod';\n\n/**\n * @private\n */\nexport interface WorkboxPackageJSON extends PackageJson {\n  workbox?: {\n    browserNamespace?: string;\n    packageType?: string;\n    prodOnly?: boolean;\n  };\n}\n"
  },
  {
    "path": "packages/workbox-build/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"CommonJS\",\n    \"outDir\": \"./build\",\n    \"resolveJsonModule\": true,\n    \"rootDir\": \"./src\",\n    \"target\": \"ES2018\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"files\": [\"src/cdn-details.json\"],\n  \"include\": [\"src/**/*.ts\", \"src/schema/*.json\"],\n  \"references\": [\n    {\"path\": \"../workbox-background-sync/\"},\n    {\"path\": \"../workbox-broadcast-update/\"},\n    {\"path\": \"../workbox-cacheable-response/\"},\n    {\"path\": \"../workbox-core/\"},\n    {\"path\": \"../workbox-expiration/\"},\n    {\"path\": \"../workbox-google-analytics/\"}\n  ]\n}\n"
  },
  {
    "path": "packages/workbox-cacheable-response/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-cacheable-response\n"
  },
  {
    "path": "packages/workbox-cacheable-response/package.json",
    "content": "{\n  \"name\": \"workbox-cacheable-response\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This library takes a Response object and determines whether it's cacheable based on a specific configuration.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"workbox-plugin\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.cacheableResponse\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-cacheable-response/src/CacheableResponse.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport './_version.js';\n\nexport interface CacheableResponseOptions {\n  statuses?: number[];\n  headers?: {[headerName: string]: string};\n}\n\n/**\n * This class allows you to set up rules determining what\n * status codes and/or headers need to be present in order for a\n * [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\n * to be considered cacheable.\n *\n * @memberof workbox-cacheable-response\n */\nclass CacheableResponse {\n  private readonly _statuses?: CacheableResponseOptions['statuses'];\n  private readonly _headers?: CacheableResponseOptions['headers'];\n\n  /**\n   * To construct a new CacheableResponse instance you must provide at least\n   * one of the `config` properties.\n   *\n   * If both `statuses` and `headers` are specified, then both conditions must\n   * be met for the `Response` to be considered cacheable.\n   *\n   * @param {Object} config\n   * @param {Array<number>} [config.statuses] One or more status codes that a\n   * `Response` can have and be considered cacheable.\n   * @param {Object<string,string>} [config.headers] A mapping of header names\n   * and expected values that a `Response` can have and be considered cacheable.\n   * If multiple headers are provided, only one needs to be present.\n   */\n  constructor(config: CacheableResponseOptions = {}) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (!(config.statuses || config.headers)) {\n        throw new WorkboxError('statuses-or-headers-required', {\n          moduleName: 'workbox-cacheable-response',\n          className: 'CacheableResponse',\n          funcName: 'constructor',\n        });\n      }\n\n      if (config.statuses) {\n        assert!.isArray(config.statuses, {\n          moduleName: 'workbox-cacheable-response',\n          className: 'CacheableResponse',\n          funcName: 'constructor',\n          paramName: 'config.statuses',\n        });\n      }\n\n      if (config.headers) {\n        assert!.isType(config.headers, 'object', {\n          moduleName: 'workbox-cacheable-response',\n          className: 'CacheableResponse',\n          funcName: 'constructor',\n          paramName: 'config.headers',\n        });\n      }\n    }\n\n    this._statuses = config.statuses;\n    this._headers = config.headers;\n  }\n\n  /**\n   * Checks a response to see whether it's cacheable or not, based on this\n   * object's configuration.\n   *\n   * @param {Response} response The response whose cacheability is being\n   * checked.\n   * @return {boolean} `true` if the `Response` is cacheable, and `false`\n   * otherwise.\n   */\n  isResponseCacheable(response: Response): boolean {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(response, Response, {\n        moduleName: 'workbox-cacheable-response',\n        className: 'CacheableResponse',\n        funcName: 'isResponseCacheable',\n        paramName: 'response',\n      });\n    }\n\n    let cacheable = true;\n\n    if (this._statuses) {\n      cacheable = this._statuses.includes(response.status);\n    }\n\n    if (this._headers && cacheable) {\n      cacheable = Object.keys(this._headers).some((headerName) => {\n        return response.headers.get(headerName) === this._headers![headerName];\n      });\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (!cacheable) {\n        logger.groupCollapsed(\n          `The request for ` +\n            `'${getFriendlyURL(response.url)}' returned a response that does ` +\n            `not meet the criteria for being cached.`,\n        );\n\n        logger.groupCollapsed(`View cacheability criteria here.`);\n        logger.log(`Cacheable statuses: ` + JSON.stringify(this._statuses));\n        logger.log(\n          `Cacheable headers: ` + JSON.stringify(this._headers, null, 2),\n        );\n        logger.groupEnd();\n\n        const logFriendlyHeaders: {[key: string]: string} = {};\n        response.headers.forEach((value, key) => {\n          logFriendlyHeaders[key] = value;\n        });\n\n        logger.groupCollapsed(`View response status and headers here.`);\n        logger.log(`Response status: ${response.status}`);\n        logger.log(\n          `Response headers: ` + JSON.stringify(logFriendlyHeaders, null, 2),\n        );\n        logger.groupEnd();\n\n        logger.groupCollapsed(`View full response details here.`);\n        logger.log(response.headers);\n        logger.log(response);\n        logger.groupEnd();\n\n        logger.groupEnd();\n      }\n    }\n\n    return cacheable;\n  }\n}\n\nexport {CacheableResponse};\n"
  },
  {
    "path": "packages/workbox-cacheable-response/src/CacheableResponsePlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport {\n  CacheableResponse,\n  CacheableResponseOptions,\n} from './CacheableResponse.js';\nimport './_version.js';\n\n/**\n * A class implementing the `cacheWillUpdate` lifecycle callback. This makes it\n * easier to add in cacheability checks to requests made via Workbox's built-in\n * strategies.\n *\n * @memberof workbox-cacheable-response\n */\nclass CacheableResponsePlugin implements WorkboxPlugin {\n  private readonly _cacheableResponse: CacheableResponse;\n\n  /**\n   * To construct a new CacheableResponsePlugin instance you must provide at\n   * least one of the `config` properties.\n   *\n   * If both `statuses` and `headers` are specified, then both conditions must\n   * be met for the `Response` to be considered cacheable.\n   *\n   * @param {Object} config\n   * @param {Array<number>} [config.statuses] One or more status codes that a\n   * `Response` can have and be considered cacheable.\n   * @param {Object<string,string>} [config.headers] A mapping of header names\n   * and expected values that a `Response` can have and be considered cacheable.\n   * If multiple headers are provided, only one needs to be present.\n   */\n  constructor(config: CacheableResponseOptions) {\n    this._cacheableResponse = new CacheableResponse(config);\n  }\n\n  /**\n   * @param {Object} options\n   * @param {Response} options.response\n   * @return {Response|null}\n   * @private\n   */\n  cacheWillUpdate: WorkboxPlugin['cacheWillUpdate'] = async ({response}) => {\n    if (this._cacheableResponse.isResponseCacheable(response)) {\n      return response;\n    }\n    return null;\n  };\n}\n\nexport {CacheableResponsePlugin};\n"
  },
  {
    "path": "packages/workbox-cacheable-response/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:cacheable-response:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-cacheable-response/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {\n  CacheableResponse,\n  CacheableResponseOptions,\n} from './CacheableResponse.js';\nimport {CacheableResponsePlugin} from './CacheableResponsePlugin.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-cacheable-response\n */\n\nexport {CacheableResponse, CacheableResponseOptions, CacheableResponsePlugin};\n"
  },
  {
    "path": "packages/workbox-cacheable-response/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-cli/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-cli\n"
  },
  {
    "path": "packages/workbox-cli/package.json",
    "content": "{\n  \"name\": \"workbox-cli\",\n  \"version\": \"7.4.0\",\n  \"description\": \"workbox-cli is the command line interface for Workbox.\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"caching\",\n    \"fetch requests\",\n    \"offline\",\n    \"cli\"\n  ],\n  \"bin\": {\n    \"workbox\": \"build/bin.js\"\n  },\n  \"files\": [\n    \"build\"\n  ],\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox/tree/master/packages/workbox-cli\",\n  \"dependencies\": {\n    \"chalk\": \"^4.1.0\",\n    \"chokidar\": \"^3.6.0\",\n    \"common-tags\": \"^1.8.0\",\n    \"fs-extra\": \"^9.0.1\",\n    \"glob\": \"^11.0.1\",\n    \"inquirer\": \"^7.3.3\",\n    \"meow\": \"^7.1.0\",\n    \"ora\": \"^5.0.0\",\n    \"pretty-bytes\": \"^5.3.0\",\n    \"stringify-object\": \"^3.3.0\",\n    \"upath\": \"^1.2.0\",\n    \"update-notifier\": \"^7.3.1\",\n    \"workbox-build\": \"7.4.0\"\n  },\n  \"workbox\": {\n    \"packageType\": \"node_ts\"\n  },\n  \"devDependencies\": {\n    \"@types/common-tags\": \"^1.8.0\",\n    \"@types/fs-extra\": \"^9.0.1\",\n    \"@types/inquirer\": \"^9.0.3\",\n    \"@types/node\": \"^20.14.8\",\n    \"@types/stringify-object\": \"^3.3.0\",\n    \"@types/update-notifier\": \"^4.1.1\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/app.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {oneLine as ol} from 'common-tags';\nimport * as workboxBuild from 'workbox-build';\nimport assert from 'assert';\nimport {default as chokidar, WatchOptions} from 'chokidar';\nimport meow from 'meow';\nimport prettyBytes from 'pretty-bytes';\nimport upath from 'upath';\n\nimport {constants} from './lib/constants.js';\nimport {errors} from './lib/errors.js';\nimport {logger} from './lib/logger.js';\nimport {readConfig} from './lib/read-config.js';\nimport {runWizard} from './lib/run-wizard.js';\nimport {SupportedFlags} from './bin.js';\n\ninterface BuildCommand {\n  command: 'generateSW' | 'injectManifest';\n  config: workboxBuild.GenerateSWOptions | workboxBuild.InjectManifestOptions;\n  watch: boolean;\n}\n\n/**\n * Runs the specified build command with the provided configuration.\n *\n * @param {Object} options\n */\nasync function runBuildCommand({command, config, watch}: BuildCommand) {\n  const {count, filePaths, size, warnings} = await workboxBuild[command](\n    config as any,\n  );\n\n  for (const warning of warnings) {\n    logger.warn(warning);\n  }\n\n  if (filePaths.length === 1) {\n    logger.log(`The service worker file was written to ${config.swDest}`);\n  } else {\n    const message = filePaths\n      .sort()\n      .map((filePath) => `  • ${filePath}`)\n      .join(`\\n`);\n    logger.log(`The service worker files were written to:\\n${message}`);\n  }\n\n  logger.log(\n    `The service worker will precache ${count} URLs, ` +\n      `totaling ${prettyBytes(size)}.`,\n  );\n\n  if (watch) {\n    logger.log(`\\nWatching for changes...`);\n  }\n}\n\nexport const app = async (\n  params: meow.Result<SupportedFlags>,\n): Promise<void> => {\n  // This should not be a user-visible error, unless meow() messes something up.\n  assert(params && Array.isArray(params.input), errors['missing-input']);\n\n  // Default to showing the help message if there's no command provided.\n  const [command = 'help', option] = params.input;\n\n  switch (command) {\n    case 'wizard': {\n      await runWizard(params.flags);\n      break;\n    }\n\n    case 'copyLibraries': {\n      assert(option, errors['missing-dest-dir-param']);\n      const parentDirectory = upath.resolve(process.cwd(), option);\n\n      const dirName = await workboxBuild.copyWorkboxLibraries(parentDirectory);\n      const fullPath = upath.join(parentDirectory, dirName);\n\n      logger.log(`The Workbox libraries were copied to ${fullPath}`);\n      logger.log(ol`Add a call to workbox.setConfig({modulePathPrefix: '...'})\n        to your service worker to use these local libraries.`);\n      logger.log(`See https://goo.gl/Fo9gPX for further documentation.`);\n      break;\n    }\n\n    case 'generateSW':\n    case 'injectManifest': {\n      const configPath = upath.resolve(\n        process.cwd(),\n        option || constants.defaultConfigFile,\n      );\n\n      let configFromDisk:\n        | workboxBuild.GenerateSWOptions\n        | workboxBuild.InjectManifestOptions;\n      try {\n        configFromDisk = readConfig(configPath);\n      } catch (error) {\n        if (error instanceof Error) {\n          logger.error(errors['invalid-common-js-module']);\n          throw error;\n        }\n      }\n\n      logger.log(`Using configuration from ${configPath}.`);\n\n      const config = configFromDisk!;\n      // Determine whether we're in --watch mode, or one-off mode.\n      if (params?.flags?.watch) {\n        const options: WatchOptions = {\n          ignoreInitial: true,\n        };\n        if (config.globIgnores) {\n          options.ignored = config.globIgnores;\n        }\n        if (config.globDirectory) {\n          options.cwd = config.globDirectory;\n        }\n\n        if (config.globPatterns) {\n          chokidar\n            .watch(config.globPatterns, options)\n            .on('all', async () => {\n              await runBuildCommand({command, config, watch: true});\n            })\n            .on('ready', async () => {\n              await runBuildCommand({command, config, watch: true});\n            })\n            .on('error', (err) => {\n              logger.error(err.toString());\n            });\n        }\n      } else {\n        await runBuildCommand({command, config, watch: false});\n      }\n      break;\n    }\n\n    case 'help': {\n      params.showHelp();\n      break;\n    }\n\n    default: {\n      throw new Error(errors['unknown-command'] + ` ` + command);\n    }\n  }\n};\n"
  },
  {
    "path": "packages/workbox-cli/src/bin.ts",
    "content": "#! /usr/bin/env node\n\n/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport meow from 'meow';\nimport updateNotifier from 'update-notifier';\n\nimport {app} from './app';\nimport {cleanupStackTrace} from './lib/cleanup-stack-trace.js';\nimport {helpText} from './lib/help-text';\nimport {logger} from './lib/logger';\n\nexport interface SupportedFlags extends meow.AnyFlags {\n  debug: meow.BooleanFlag;\n  injectManifest: meow.BooleanFlag;\n  watch: meow.BooleanFlag;\n}\n\nvoid (async () => {\n  const params: meow.Result<any> = meow(helpText);\n  updateNotifier({pkg: params.pkg as updateNotifier.Package}).notify();\n\n  try {\n    await app(params);\n  } catch (error) {\n    if (error instanceof Error) {\n      // Show the full error and stack trace if we're run with --debug.\n      if (params.flags.debug) {\n        if (error.stack) {\n          logger.error(`\\n${error.stack}`);\n        }\n      } else {\n        logger.error(`\\n${error.message}`);\n        logger.debug(`${cleanupStackTrace(error, 'app.js')}\\n`);\n      }\n    }\n\n    process.exit(1);\n  }\n})();\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/cleanup-stack-trace.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Helper to parse out less relevant info from an Error's stack trace.\n// Removes the initial portion, since that's obtained from error.message.\n// Removes every stack frame earlier than the last instance of moduleName,\n// since that's just frames related to the Node runtime/loader.\nexport function cleanupStackTrace(error: Error, moduleName: string): string {\n  if (!error.stack) {\n    return '';\n  }\n  const frames = error.stack.split(`\\n`);\n  let startFrame: number | undefined;\n  let lastFrame = 0;\n  frames.forEach((frame, index) => {\n    if (startFrame === undefined && frame.includes(`    at `)) {\n      startFrame = index;\n    }\n\n    if (frame.includes(`${moduleName}:`)) {\n      lastFrame = index;\n    }\n  });\n  return frames.slice(startFrame, lastFrame + 1).join(`\\n`);\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/constants.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nexport const constants = {\n  defaultConfigFile: 'workbox-config.js',\n  ignoredDirectories: ['node_modules'],\n  ignoredFileExtensions: ['map'],\n  ignoreURLParametersMatching: [/^utm_/, /^fbclid$/],\n};\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/errors.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {oneLine as ol} from 'common-tags';\n\nexport const errors = {\n  'missing-input': `params.input value was not set properly.`,\n  'missing-dest-dir-param': ol`Please provide the path to a directory in which\n    the libraries will be copied.`,\n  'invalid-common-js-module': ol`Please pass in a valid CommonJS module that\n    exports your configuration.`,\n  'config-validation-failed': `Your configuration is invalid:`,\n  'workbox-build-runtime-error': `Service worker generation failed:`,\n  'unknown-command': `Unknown command:`,\n  'no-file-extensions-found': ol`No files could be found that are suitable for\n    caching.`,\n  'no-file-extensions-selected': `Please select at least one file extension.`,\n  'invalid-sw-dest': ol`Please enter a valid path to use for the service worker\n    file that's created.`,\n  'glob-directory-invalid': ol`The path you entered isn't a valid directory.`,\n  'invalid-config-location': ol`Please enter a valid path to use for the saved\n    configuration file.`,\n  'sw-src-missing-injection-point': ol`That is not a valid source service worker\n    file. Please try again with a file containing\n    'self.__WB_MANIFEST'.`,\n  'no-search-parameters-supplied': ol`Please provide the url search param(s)\n    you would like to ignore.`,\n  'invalid-search-parameters-supplied': ol`Please provide the valid URL search parameter(s)\n    without the leading '/' or '?' (i.e. source,version,language).`,\n};\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/help-text.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nexport const helpText = `Usage:\n$ workbox <command> [options]\n\nCommands:\n  wizard [--injectManifest]\n    Runs the configuration wizard, which will generate a\n    config file based on answers to questions.\n    By default the configuration will be tailored to the\n    generateSW use case.\n    If --injectManifest is provided, the wizard will ask\n    questions needed for the injectManifest use case.\n\n  generateSW [<path/to/config.js>] [--watch]\n    Creates a new service worker file based on the options\n    in the config file (defaults to workbox-config.js).\n    If --watch is provided, the CLI will stay running, and will\n    rebuild the service worker each time a file in the precache\n    manifest changes.\n    See https://bit.ly/wb-generateSW\n\n  injectManifest [<path/to/config.js>] [--watch]\n    Takes an existing service worker file and creates a\n    copy of it with a precache manifest \"injected\" into\n    it. The precache manifest is generated based on the\n    options in the config file (defaults to workbox-config.js).\n    If --watch is provided, the CLI will stay running, and will\n    rebuild the service worker each time a file in the precache\n    manifest changes.\n    See https://bit.ly/wb-injectManifest\n\n  copyLibraries <path/to/parent/dir>\n    Makes a local copy of all of the Workbox libraries inside\n    a version directory at the location specified. This is intended\n    for developers using injectManifest who prefer using local,\n    rather than CDN hosted, libraries.\n\nConfig file:\n  In 'generateSW' or 'injectManifest' mode, the config file should be a\n  JavaScript file, in CommonJS module format.\n  By default, a config file named workbox-config.js in the current\n  directory is assumed, but this can be overridden.\n\nExamples:\n  $ workbox wizard\n  $ workbox wizard --injectManifest\n  $ workbox generateSW --watch\n  $ workbox injectManifest configs/workbox-dev-config.js\n  $ workbox copyLibraries build/\n`;\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/logger.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport chalk from 'chalk';\n\nexport const logger = {\n  debug: (...args: string[]): void => console.log(chalk.gray(...args)),\n  log: (...args: string[]): void => console.log(...args),\n  warn: (...args: string[]): void => console.warn(chalk.yellow(...args)),\n  error: (...args: string[]): void => console.error(chalk.red.bold(...args)),\n};\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-config-location.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\nimport {Answers, Question} from 'inquirer';\nimport inquirer from 'inquirer';\nimport {oneLine as ol} from 'common-tags';\n\nimport {constants} from '../constants';\nimport {errors} from '../errors';\n\n// The key used for the question/answer.\nconst name = 'configLocation';\n\nconst configLocationQuestion: Question<Answers> = {\n  name,\n  message: ol`Where would you like to save these configuration options?`,\n  type: 'input',\n  default: constants.defaultConfigFile,\n};\n/**\n * @return {Promise<Answers>} The answers from inquirer.\n */\nfunction askQuestion(): Promise<Answers> {\n  return inquirer.prompt([configLocationQuestion]);\n}\n\nexport async function askConfigLocation(): Promise<string> {\n  const answers = await askQuestion();\n  // The value of the answer when the question type is 'input' is String\n  // and it has a default value, the casting is safe.\n  const configLocation: string = (answers[name] as string).trim();\n\n  assert(configLocation, errors['invalid-config-location']);\n\n  return configLocation;\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-extensions-to-cache.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\nimport {Answers} from 'inquirer';\nimport inquirer from 'inquirer';\nimport {glob} from 'glob';\nimport ora from 'ora';\nimport upath from 'upath';\n\nimport {errors} from '../errors';\nimport {constants} from '../constants';\n\n// The key used for the question/answer.\nconst name = 'globPatterns';\n\n/**\n * @param {string} globDirectory The directory used for the root of globbing.\n * @return {Promise<Array<string>>} The unique file extensions corresponding\n * to all of the files under globDirectory.\n */\nasync function getAllFileExtensions(globDirectory: string) {\n  const files = await glob('**/*.*', {\n    cwd: globDirectory,\n    nodir: true,\n    ignore: [\n      ...constants.ignoredDirectories.map((directory) => `**/${directory}/**`),\n      ...constants.ignoredFileExtensions.map(\n        (extension) => `**/*.${extension}`,\n      ),\n    ],\n  });\n\n  const extensions: Set<string> = new Set();\n  for (const file of files) {\n    const extension = upath.extname(file);\n    if (extension) {\n      // Get rid of the leading . character.\n      extensions.add(extension.replace(/^\\./, ''));\n    }\n  }\n\n  return [...extensions];\n}\n\n/**\n * @param {string} globDirectory The directory used for the root of globbing.\n * @return {Promise<Answers>} The answers from inquirer.\n */\nasync function askQuestion(globDirectory: string): Promise<Answers> {\n  // We need to get a list of extensions corresponding to files in the directory\n  // to use when asking the next question. That could potentially take some\n  // time, so we show a spinner and explanatory text.\n  const spinner = ora({\n    text: `Examining files in ${globDirectory}...`,\n    stream: process.stdout,\n  }).start();\n  const fileExtensions = await getAllFileExtensions(globDirectory);\n  spinner.stop();\n\n  assert(fileExtensions.length > 0, errors['no-file-extensions-found']);\n\n  return inquirer.prompt([\n    {\n      name,\n      message: 'Which file types would you like to precache?',\n      type: 'checkbox',\n      choices: fileExtensions,\n      default: fileExtensions,\n    },\n  ]);\n}\n\nexport async function askExtensionsToCache(\n  globDirectory: string,\n): Promise<string[]> {\n  const answers = await askQuestion(globDirectory);\n  // The return value is an array of strings with the selected values\n  // and there is a default, the casting is safe.\n  const extensions: string[] = answers[name] as string[];\n  assert(extensions.length > 0, errors['no-file-extensions-selected']);\n\n  // glob isn't happy with a single option inside of a {} group, so use a\n  // pattern without a {} group when there's only one extension.\n  const extensionsPattern: string =\n    extensions.length === 1 ? extensions[0] : `{${extensions.join(',')}}`;\n  return [`**/*.${extensionsPattern}`];\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-questions.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {askConfigLocation} from './ask-config-location';\nimport {askExtensionsToCache} from './ask-extensions-to-cache';\nimport {askRootOfWebApp} from './ask-root-of-web-app';\nimport {askSWDest} from './ask-sw-dest';\nimport {askSWSrc} from './ask-sw-src';\nimport {askQueryParametersInStartUrl} from './ask-start_url-query-params';\n\ninterface ConfigWithConfigLocation {\n  config: {\n    [key: string]: any;\n  };\n  configLocation: string;\n}\n\nexport async function askQuestions(\n  options = {},\n): Promise<ConfigWithConfigLocation> {\n  const isInjectManifest = 'injectManifest' in options;\n\n  const globDirectory = await askRootOfWebApp();\n  const globPatterns = await askExtensionsToCache(globDirectory);\n  const swSrc = isInjectManifest ? await askSWSrc() : undefined;\n  const swDest = await askSWDest(globDirectory);\n  const configLocation = await askConfigLocation();\n  // See https://github.com/GoogleChrome/workbox/issues/2985\n  const ignoreURLParametersMatching = isInjectManifest\n    ? undefined\n    : await askQueryParametersInStartUrl();\n\n  const config: {[key: string]: any} = {\n    globDirectory,\n    globPatterns,\n    swDest,\n  };\n\n  if (swSrc) {\n    config.swSrc = swSrc;\n  }\n\n  if (ignoreURLParametersMatching) {\n    config.ignoreURLParametersMatching = ignoreURLParametersMatching;\n  }\n\n  return {\n    config,\n    configLocation,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-root-of-web-app.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\nimport fse from 'fs-extra';\nimport {glob} from 'glob';\nimport inquirer from 'inquirer';\nimport {oneLine as ol} from 'common-tags';\n\nimport {errors} from '../errors';\nimport {constants} from '../constants';\n\nconst ROOT_PROMPT = 'Please enter the path to the root of your web app:';\n\n// The keys used for the questions/answers.\nconst questionRootDirectory = 'globDirectory';\nconst questionManualInput = 'manualDirectoryInput';\n\n/**\n * @return {Promise<Array<string>>} The subdirectories of the current\n * working directory, with hidden and ignored ones filtered out.\n */\nasync function getSubdirectories(): Promise<Array<string>> {\n  return await glob('*/', {\n    ignore: constants.ignoredDirectories.map((directory) => `${directory}/`),\n  });\n}\n\n/**\n * @return {Promise<Object>} The answers from inquirer.\n */\nasync function askQuestion(): Promise<{\n  globDirectory: string;\n  manualDirectoryInput?: string;\n}> {\n  const subdirectories: (string | InstanceType<typeof inquirer.Separator>)[] =\n    await getSubdirectories();\n\n  if (subdirectories.length > 0) {\n    const manualEntryChoice = 'Manually enter path';\n    return inquirer.prompt([\n      {\n        name: questionRootDirectory,\n        type: 'list',\n        message: ol`What is the root of your web app (i.e. which directory do\n        you deploy)?`,\n        choices: subdirectories.concat([\n          new inquirer.Separator(),\n          manualEntryChoice,\n        ]),\n      },\n      {\n        name: questionManualInput,\n        when: (answers: {globDirectory: string}) =>\n          answers.globDirectory === manualEntryChoice,\n        message: ROOT_PROMPT,\n      },\n    ]);\n  }\n\n  return inquirer.prompt([\n    {\n      name: questionRootDirectory,\n      message: ROOT_PROMPT,\n      default: '.',\n    },\n  ]);\n}\n\nexport async function askRootOfWebApp(): Promise<string> {\n  const {manualDirectoryInput, globDirectory} = await askQuestion();\n\n  try {\n    const stat = await fse.stat(manualDirectoryInput || globDirectory);\n    assert(stat.isDirectory());\n  } catch (error) {\n    throw new Error(errors['glob-directory-invalid']);\n  }\n\n  return manualDirectoryInput || globDirectory;\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-start_url-query-params.ts",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\nimport inquirer from 'inquirer';\nimport {oneLine as ol} from 'common-tags';\n\nimport {errors} from '../errors';\nimport {constants} from '../constants';\n\nconst START_URL_QUERY_PARAMS_PROMPT =\n  'Please enter the search parameter(s) that you would like to ignore (separated by comma):';\n\n// The keys used for the questions/answers.\nconst question_ignoreURLParametersMatching = 'ignoreURLParametersMatching';\nconst question_shouldAskForIgnoreURLParametersMatching =\n  'shouldAskForIgnoreURLParametersMatching';\n\n/**\n * @return {Promise<Object>} The answers from inquirer.\n */\nasync function askQuestion(): Promise<{\n  shouldAskForIgnoreURLParametersMatching: boolean;\n  ignoreURLParametersMatching?: string;\n}> {\n  return inquirer.prompt([\n    {\n      name: question_shouldAskForIgnoreURLParametersMatching,\n      message: ol`Does your web app manifest include search parameter(s)\n      in the 'start_url', other than 'utm_' or 'fbclid'\n      (like '?source=pwa')?`,\n      type: 'confirm',\n      default: false,\n    },\n    {\n      name: question_ignoreURLParametersMatching,\n      when: (answer: {shouldAskForIgnoreURLParametersMatching: boolean}) =>\n        answer.shouldAskForIgnoreURLParametersMatching,\n      message: START_URL_QUERY_PARAMS_PROMPT,\n      type: 'input',\n    },\n  ]);\n}\n\nexport async function askQueryParametersInStartUrl(\n  defaultIgnoredSearchParameters: RegExp[] = constants.ignoreURLParametersMatching,\n): Promise<RegExp[]> {\n  const {\n    shouldAskForIgnoreURLParametersMatching,\n    ignoreURLParametersMatching = '',\n  } = await askQuestion();\n\n  if (!shouldAskForIgnoreURLParametersMatching) {\n    return defaultIgnoredSearchParameters;\n  }\n\n  assert(\n    ignoreURLParametersMatching.length > 0,\n    errors['no-search-parameters-supplied'],\n  );\n\n  const ignoreSearchParameters = ignoreURLParametersMatching\n    .trim()\n    .split(',')\n    .filter(Boolean);\n\n  assert(\n    ignoreSearchParameters.length > 0,\n    errors['no-search-parameters-supplied'],\n  );\n  assert(\n    ignoreSearchParameters.every((param) => !param.match(/^[^\\w|-]/g)),\n    errors['invalid-search-parameters-supplied'],\n  );\n\n  return defaultIgnoredSearchParameters.concat(\n    ignoreSearchParameters.map((searchParam) => new RegExp(`^${searchParam}`)),\n  );\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-sw-dest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport assert from 'assert';\nimport {Answers} from 'inquirer';\nimport inquirer from 'inquirer';\nimport upath from 'upath';\n\nimport {errors} from '../errors';\n\n// The key used for the question/answer.\nconst name = 'swDest';\n\n/**\n * @param {string} defaultDir\n * @return {Promise<Answers>} The answers from inquirer.\n */\nfunction askQuestion(defaultDir: string): Promise<Answers> {\n  return inquirer.prompt([\n    {\n      name,\n      message: `Where would you like your service worker file to be saved?`,\n      type: 'input',\n      default: upath.join(defaultDir, 'sw.js'),\n    },\n  ]);\n}\n\nexport async function askSWDest(defaultDir = '.'): Promise<string> {\n  const answers = await askQuestion(defaultDir);\n  // When prompt type is input the return type is string\n  // casting is safe\n  const swDest: string = (answers[name] as string).trim();\n\n  assert(swDest, errors['invalid-sw-dest']);\n\n  return swDest;\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/questions/ask-sw-src.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Answers} from 'inquirer';\nimport inquirer from 'inquirer';\nimport {oneLine as ol} from 'common-tags';\n\n// The key used for the question/answer.\nconst name = 'swSrc';\n\n/**\n * @return {Promise<Answers>} The answers from inquirer.\n */\nfunction askQuestion(): Promise<Answers> {\n  return inquirer.prompt([\n    {\n      name,\n      message: ol`Where's your existing service worker file? To be used with\n      injectManifest, it should include a call to\n      'self.__WB_MANIFEST'`,\n      type: 'input',\n    },\n  ]);\n}\n\nexport async function askSWSrc(): Promise<string | null> {\n  const answers = await askQuestion();\n  // When prompt type is input the return is string or null\n  return answers[name] ? (answers[name] as string).trim() : null;\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/read-config.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {GenerateSWOptions, InjectManifestOptions} from 'workbox-build';\n\n// A really light wrapper on top of Node's require() to make it easier to stub\n// out reading the configuration during tests.\nexport function readConfig(\n  configFile: string,\n): GenerateSWOptions | InjectManifestOptions {\n  return require(configFile) as GenerateSWOptions | InjectManifestOptions;\n}\n"
  },
  {
    "path": "packages/workbox-cli/src/lib/run-wizard.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport fse from 'fs-extra';\nimport {oneLine as ol} from 'common-tags';\nimport stringifyObject from 'stringify-object';\n\nimport {askQuestions} from './questions/ask-questions';\nimport {logger} from './logger';\n\nexport async function runWizard(options = {}): Promise<void> {\n  const {configLocation, config} = await askQuestions(options);\n\n  // See https://github.com/GoogleChrome/workbox/issues/2796\n  const contents = `module.exports = ${stringifyObject(config)};`;\n  await fse.writeFile(configLocation, contents);\n\n  const command = 'injectManifest' in options ? 'injectManifest' : 'generateSW';\n  logger.log(`To build your service worker, run\n\n  workbox ${command} ${configLocation}\n\nas part of a build process. See https://goo.gl/fdTQBf for details.`);\n\n  const configDocsURL =\n    'injectManifest' in options\n      ? 'https://goo.gl/8bs14N'\n      : 'https://goo.gl/gVo87N';\n\n  logger.log(ol`You can further customize your service worker by making changes\n    to ${configLocation}. See ${configDocsURL} for details.`);\n}\n"
  },
  {
    "path": "packages/workbox-cli/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"module\": \"CommonJS\",\n    \"outDir\": \"build\",\n    \"rootDir\": \"src\",\n    \"target\": \"ES2018\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-build/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-core/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-core\n"
  },
  {
    "path": "packages/workbox-core/package.json",
    "content": "{\n  \"name\": \"workbox-core\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This module is used by a number of the other Workbox modules to share common code.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.core\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\"\n}\n"
  },
  {
    "path": "packages/workbox-core/src/_private/Deferred.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * The Deferred class composes Promises in a way that allows for them to be\n * resolved or rejected from outside the constructor. In most cases promises\n * should be used directly, but Deferreds can be necessary when the logic to\n * resolve a promise must be separate.\n *\n * @private\n */\nclass Deferred<T> {\n  promise: Promise<T>;\n  resolve!: (value: T) => void;\n  reject!: (reason?: any) => void;\n\n  /**\n   * Creates a promise and exposes its resolve and reject functions as methods.\n   */\n  constructor() {\n    this.promise = new Promise((resolve, reject) => {\n      this.resolve = resolve;\n      this.reject = reject;\n    });\n  }\n}\n\nexport {Deferred};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/WorkboxError.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {messageGenerator} from '../models/messages/messageGenerator.js';\nimport {MapLikeObject} from '../types.js';\nimport '../_version.js';\n\n/**\n * Workbox errors should be thrown with this class.\n * This allows use to ensure the type easily in tests,\n * helps developers identify errors from workbox\n * easily and allows use to optimise error\n * messages correctly.\n *\n * @private\n */\nclass WorkboxError extends Error {\n  details?: MapLikeObject;\n\n  /**\n   *\n   * @param {string} errorCode The error code that\n   * identifies this particular error.\n   * @param {Object=} details Any relevant arguments\n   * that will help developers identify issues should\n   * be added as a key on the context object.\n   */\n  constructor(errorCode: string, details?: MapLikeObject) {\n    const message = messageGenerator(errorCode, details);\n\n    super(message);\n\n    this.name = errorCode;\n    this.details = details;\n  }\n}\n\nexport {WorkboxError};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/assert.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from '../_private/WorkboxError.js';\nimport {MapLikeObject} from '../types.js';\nimport '../_version.js';\n\n/*\n * This method throws if the supplied value is not an array.\n * The destructed values are required to produce a meaningful error for users.\n * The destructed and restructured object is so it's clear what is\n * needed.\n */\nconst isArray = (value: any[], details: MapLikeObject) => {\n  if (!Array.isArray(value)) {\n    throw new WorkboxError('not-an-array', details);\n  }\n};\n\nconst hasMethod = (\n  object: MapLikeObject,\n  expectedMethod: string,\n  details: MapLikeObject,\n) => {\n  const type = typeof object[expectedMethod];\n  if (type !== 'function') {\n    details['expectedMethod'] = expectedMethod;\n    throw new WorkboxError('missing-a-method', details);\n  }\n};\n\nconst isType = (\n  object: unknown,\n  expectedType: string,\n  details: MapLikeObject,\n) => {\n  if (typeof object !== expectedType) {\n    details['expectedType'] = expectedType;\n    throw new WorkboxError('incorrect-type', details);\n  }\n};\n\nconst isInstance = (\n  object: unknown,\n  // Need the general type to do the check later.\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  expectedClass: Function,\n  details: MapLikeObject,\n) => {\n  if (!(object instanceof expectedClass)) {\n    details['expectedClassName'] = expectedClass.name;\n    throw new WorkboxError('incorrect-class', details);\n  }\n};\n\nconst isOneOf = (value: any, validValues: any[], details: MapLikeObject) => {\n  if (!validValues.includes(value)) {\n    details['validValueDescription'] = `Valid values are ${JSON.stringify(\n      validValues,\n    )}.`;\n    throw new WorkboxError('invalid-value', details);\n  }\n};\n\nconst isArrayOfClass = (\n  value: any,\n  // Need general type to do check later.\n  expectedClass: Function, // eslint-disable-line\n  details: MapLikeObject,\n) => {\n  const error = new WorkboxError('not-array-of-class', details);\n  if (!Array.isArray(value)) {\n    throw error;\n  }\n\n  for (const item of value) {\n    if (!(item instanceof expectedClass)) {\n      throw error;\n    }\n  }\n};\n\nconst finalAssertExports =\n  process.env.NODE_ENV === 'production'\n    ? null\n    : {\n        hasMethod,\n        isArray,\n        isInstance,\n        isOneOf,\n        isType,\n        isArrayOfClass,\n      };\n\nexport {finalAssertExports as assert};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/cacheMatchIgnoreParams.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nfunction stripParams(fullURL: string, ignoreParams: string[]) {\n  const strippedURL = new URL(fullURL);\n  for (const param of ignoreParams) {\n    strippedURL.searchParams.delete(param);\n  }\n  return strippedURL.href;\n}\n\n/**\n * Matches an item in the cache, ignoring specific URL params. This is similar\n * to the `ignoreSearch` option, but it allows you to ignore just specific\n * params (while continuing to match on the others).\n *\n * @private\n * @param {Cache} cache\n * @param {Request} request\n * @param {Object} matchOptions\n * @param {Array<string>} ignoreParams\n * @return {Promise<Response|undefined>}\n */\nasync function cacheMatchIgnoreParams(\n  cache: Cache,\n  request: Request,\n  ignoreParams: string[],\n  matchOptions?: CacheQueryOptions,\n): Promise<Response | undefined> {\n  const strippedRequestURL = stripParams(request.url, ignoreParams);\n\n  // If the request doesn't include any ignored params, match as normal.\n  if (request.url === strippedRequestURL) {\n    return cache.match(request, matchOptions);\n  }\n\n  // Otherwise, match by comparing keys\n  const keysOptions = {...matchOptions, ignoreSearch: true};\n  const cacheKeys = await cache.keys(request, keysOptions);\n\n  for (const cacheKey of cacheKeys) {\n    const strippedCacheKeyURL = stripParams(cacheKey.url, ignoreParams);\n    if (strippedRequestURL === strippedCacheKeyURL) {\n      return cache.match(cacheKey, matchOptions);\n    }\n  }\n  return;\n}\n\nexport {cacheMatchIgnoreParams};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/cacheNames.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\ndeclare let registration: ServiceWorkerRegistration | undefined;\n\nexport interface CacheNameDetails {\n  googleAnalytics: string;\n  precache: string;\n  prefix: string;\n  runtime: string;\n  suffix: string;\n}\n\nexport interface PartialCacheNameDetails {\n  [propName: string]: string;\n}\n\nexport type CacheNameDetailsProp =\n  | 'googleAnalytics'\n  | 'precache'\n  | 'prefix'\n  | 'runtime'\n  | 'suffix';\n\nconst _cacheNameDetails: CacheNameDetails = {\n  googleAnalytics: 'googleAnalytics',\n  precache: 'precache-v2',\n  prefix: 'workbox',\n  runtime: 'runtime',\n  suffix: typeof registration !== 'undefined' ? registration.scope : '',\n};\n\nconst _createCacheName = (cacheName: string): string => {\n  return [_cacheNameDetails.prefix, cacheName, _cacheNameDetails.suffix]\n    .filter((value) => value && value.length > 0)\n    .join('-');\n};\n\nconst eachCacheNameDetail = (fn: (key: CacheNameDetailsProp) => void): void => {\n  for (const key of Object.keys(_cacheNameDetails)) {\n    fn(key as CacheNameDetailsProp);\n  }\n};\n\nexport const cacheNames = {\n  updateDetails: (details: PartialCacheNameDetails): void => {\n    eachCacheNameDetail((key: CacheNameDetailsProp): void => {\n      if (typeof details[key] === 'string') {\n        _cacheNameDetails[key] = details[key];\n      }\n    });\n  },\n  getGoogleAnalyticsName: (userCacheName?: string): string => {\n    return userCacheName || _createCacheName(_cacheNameDetails.googleAnalytics);\n  },\n  getPrecacheName: (userCacheName?: string): string => {\n    return userCacheName || _createCacheName(_cacheNameDetails.precache);\n  },\n  getPrefix: (): string => {\n    return _cacheNameDetails.prefix;\n  },\n  getRuntimeName: (userCacheName?: string): string => {\n    return userCacheName || _createCacheName(_cacheNameDetails.runtime);\n  },\n  getSuffix: (): string => {\n    return _cacheNameDetails.suffix;\n  },\n};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/canConstructReadableStream.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nlet supportStatus: boolean | undefined;\n\n/**\n * A utility function that determines whether the current browser supports\n * constructing a [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n * object.\n *\n * @return {boolean} `true`, if the current browser can successfully\n *     construct a `ReadableStream`, `false` otherwise.\n *\n * @private\n */\nfunction canConstructReadableStream(): boolean {\n  if (supportStatus === undefined) {\n    // See https://github.com/GoogleChrome/workbox/issues/1473\n    try {\n      new ReadableStream({start() {}});\n      supportStatus = true;\n    } catch (error) {\n      supportStatus = false;\n    }\n  }\n\n  return supportStatus;\n}\n\nexport {canConstructReadableStream};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/canConstructResponseFromBodyStream.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nlet supportStatus: boolean | undefined;\n\n/**\n * A utility function that determines whether the current browser supports\n * constructing a new `Response` from a `response.body` stream.\n *\n * @return {boolean} `true`, if the current browser can successfully\n *     construct a `Response` from a `response.body` stream, `false` otherwise.\n *\n * @private\n */\nfunction canConstructResponseFromBodyStream(): boolean {\n  if (supportStatus === undefined) {\n    const testResponse = new Response('');\n\n    if ('body' in testResponse) {\n      try {\n        new Response(testResponse.body);\n        supportStatus = true;\n      } catch (error) {\n        supportStatus = false;\n      }\n    }\n    supportStatus = false;\n  }\n\n  return supportStatus;\n}\n\nexport {canConstructResponseFromBodyStream};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/dontWaitFor.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * A helper function that prevents a promise from being flagged as unused.\n *\n * @private\n **/\nexport function dontWaitFor(promise: Promise<any>): void {\n  // Effective no-op.\n  void promise.then(() => {});\n}\n"
  },
  {
    "path": "packages/workbox-core/src/_private/executeQuotaErrorCallbacks.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from '../_private/logger.js';\nimport {quotaErrorCallbacks} from '../models/quotaErrorCallbacks.js';\nimport '../_version.js';\n\n/**\n * Runs all of the callback functions, one at a time sequentially, in the order\n * in which they were registered.\n *\n * @memberof workbox-core\n * @private\n */\nasync function executeQuotaErrorCallbacks(): Promise<void> {\n  if (process.env.NODE_ENV !== 'production') {\n    logger.log(\n      `About to run ${quotaErrorCallbacks.size} ` +\n        `callbacks to clean up caches.`,\n    );\n  }\n\n  for (const callback of quotaErrorCallbacks) {\n    await callback();\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(callback, 'is complete.');\n    }\n  }\n\n  if (process.env.NODE_ENV !== 'production') {\n    logger.log('Finished running callbacks.');\n  }\n}\n\nexport {executeQuotaErrorCallbacks};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/getFriendlyURL.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nconst getFriendlyURL = (url: URL | string): string => {\n  const urlObj = new URL(String(url), location.href);\n  // See https://github.com/GoogleChrome/workbox/issues/2323\n  // We want to include everything, except for the origin if it's same-origin.\n  return urlObj.href.replace(new RegExp(`^${location.origin}`), '');\n};\n\nexport {getFriendlyURL};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/logger.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n// logger is used inside of both service workers and the window global scope.\ndeclare global {\n  interface WorkerGlobalScope {\n    __WB_DISABLE_DEV_LOGS: boolean;\n  }\n\n  interface Window {\n    __WB_DISABLE_DEV_LOGS: boolean;\n  }\n}\n\ntype LoggerMethods =\n  | 'debug'\n  | 'log'\n  | 'warn'\n  | 'error'\n  | 'groupCollapsed'\n  | 'groupEnd';\n\nconst logger = (\n  process.env.NODE_ENV === 'production'\n    ? null\n    : (() => {\n        // Don't overwrite this value if it's already set.\n        // See https://github.com/GoogleChrome/workbox/pull/2284#issuecomment-560470923\n        if (!('__WB_DISABLE_DEV_LOGS' in globalThis)) {\n          self.__WB_DISABLE_DEV_LOGS = false;\n        }\n\n        let inGroup = false;\n\n        const methodToColorMap: {[methodName: string]: string | null} = {\n          debug: `#7f8c8d`, // Gray\n          log: `#2ecc71`, // Green\n          warn: `#f39c12`, // Yellow\n          error: `#c0392b`, // Red\n          groupCollapsed: `#3498db`, // Blue\n          groupEnd: null, // No colored prefix on groupEnd\n        };\n\n        const print = function (method: LoggerMethods, args: any[]) {\n          if (self.__WB_DISABLE_DEV_LOGS) {\n            return;\n          }\n\n          if (method === 'groupCollapsed') {\n            // Safari doesn't print all console.groupCollapsed() arguments:\n            // https://bugs.webkit.org/show_bug.cgi?id=182754\n            if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {\n              console[method](...args);\n              return;\n            }\n          }\n\n          const styles = [\n            `background: ${methodToColorMap[method]!}`,\n            `border-radius: 0.5em`,\n            `color: white`,\n            `font-weight: bold`,\n            `padding: 2px 0.5em`,\n          ];\n\n          // When in a group, the workbox prefix is not displayed.\n          const logPrefix = inGroup ? [] : ['%cworkbox', styles.join(';')];\n\n          console[method](...logPrefix, ...args);\n\n          if (method === 'groupCollapsed') {\n            inGroup = true;\n          }\n          if (method === 'groupEnd') {\n            inGroup = false;\n          }\n        };\n        // eslint-disable-next-line @typescript-eslint/ban-types\n        const api: {[methodName: string]: Function} = {};\n        const loggerMethods = Object.keys(methodToColorMap);\n\n        for (const key of loggerMethods) {\n          const method = key as LoggerMethods;\n\n          api[method] = (...args: any[]) => {\n            print(method, args);\n          };\n        }\n\n        return api as unknown;\n      })()\n) as Console;\n\nexport {logger};\n"
  },
  {
    "path": "packages/workbox-core/src/_private/resultingClientExists.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {timeout} from './timeout.js';\nimport '../_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\nconst MAX_RETRY_TIME = 2000;\n\n/**\n * Returns a promise that resolves to a window client matching the passed\n * `resultingClientId`. For browsers that don't support `resultingClientId`\n * or if waiting for the resulting client to apper takes too long, resolve to\n * `undefined`.\n *\n * @param {string} [resultingClientId]\n * @return {Promise<Client|undefined>}\n * @private\n */\nexport async function resultingClientExists(\n  resultingClientId?: string,\n): Promise<Client | undefined> {\n  if (!resultingClientId) {\n    return;\n  }\n\n  let existingWindows = await self.clients.matchAll({type: 'window'});\n  const existingWindowIds = new Set(existingWindows.map((w) => w.id));\n\n  let resultingWindow;\n  const startTime = performance.now();\n\n  // Only wait up to `MAX_RETRY_TIME` to find a matching client.\n  while (performance.now() - startTime < MAX_RETRY_TIME) {\n    existingWindows = await self.clients.matchAll({type: 'window'});\n\n    resultingWindow = existingWindows.find((w) => {\n      if (resultingClientId) {\n        // If we have a `resultingClientId`, we can match on that.\n        return w.id === resultingClientId;\n      } else {\n        // Otherwise match on finding a window not in `existingWindowIds`.\n        return !existingWindowIds.has(w.id);\n      }\n    });\n\n    if (resultingWindow) {\n      break;\n    }\n\n    // Sleep for 100ms and retry.\n    await timeout(100);\n  }\n\n  return resultingWindow;\n}\n"
  },
  {
    "path": "packages/workbox-core/src/_private/timeout.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * Returns a promise that resolves and the passed number of milliseconds.\n * This utility is an async/await-friendly version of `setTimeout`.\n *\n * @param {number} ms\n * @return {Promise}\n * @private\n */\n\nexport function timeout(ms: number): Promise<unknown> {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n}\n"
  },
  {
    "path": "packages/workbox-core/src/_private/waitUntil.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * A utility method that makes it easier to use `event.waitUntil` with\n * async functions and return the result.\n *\n * @param {ExtendableEvent} event\n * @param {Function} asyncFn\n * @return {Function}\n * @private\n */\nfunction waitUntil(\n  event: ExtendableEvent,\n  asyncFn: () => Promise<any>,\n): Promise<any> {\n  const returnPromise = asyncFn();\n  event.waitUntil(returnPromise);\n  return returnPromise;\n}\n\nexport {waitUntil};\n"
  },
  {
    "path": "packages/workbox-core/src/_private.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// We either expose defaults or we expose every named export.\nimport {assert} from './_private/assert.js';\nimport {cacheNames} from './_private/cacheNames.js';\nimport {cacheMatchIgnoreParams} from './_private/cacheMatchIgnoreParams.js';\nimport {canConstructReadableStream} from './_private/canConstructReadableStream.js';\nimport {canConstructResponseFromBodyStream} from './_private/canConstructResponseFromBodyStream.js';\nimport {dontWaitFor} from './_private/dontWaitFor.js';\nimport {Deferred} from './_private/Deferred.js';\nimport {executeQuotaErrorCallbacks} from './_private/executeQuotaErrorCallbacks.js';\nimport {getFriendlyURL} from './_private/getFriendlyURL.js';\nimport {logger} from './_private/logger.js';\nimport {resultingClientExists} from './_private/resultingClientExists.js';\nimport {timeout} from './_private/timeout.js';\nimport {waitUntil} from './_private/waitUntil.js';\nimport {WorkboxError} from './_private/WorkboxError.js';\n\nimport './_version.js';\n\nexport {\n  assert,\n  cacheMatchIgnoreParams,\n  cacheNames,\n  canConstructReadableStream,\n  canConstructResponseFromBodyStream,\n  dontWaitFor,\n  Deferred,\n  executeQuotaErrorCallbacks,\n  getFriendlyURL,\n  logger,\n  resultingClientExists,\n  timeout,\n  waitUntil,\n  WorkboxError,\n};\n"
  },
  {
    "path": "packages/workbox-core/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:core:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-core/src/cacheNames.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames as _cacheNames} from './_private/cacheNames.js';\nimport './_version.js';\n\n/**\n * Get the current cache names and prefix/suffix used by Workbox.\n *\n * `cacheNames.precache` is used for precached assets,\n * `cacheNames.googleAnalytics` is used by `workbox-google-analytics` to\n * store `analytics.js`, and `cacheNames.runtime` is used for everything else.\n *\n * `cacheNames.prefix` can be used to retrieve just the current prefix value.\n * `cacheNames.suffix` can be used to retrieve just the current suffix value.\n *\n * @return {Object} An object with `precache`, `runtime`, `prefix`, and\n *     `googleAnalytics` properties.\n *\n * @memberof workbox-core\n */\nconst cacheNames = {\n  get googleAnalytics(): string {\n    return _cacheNames.getGoogleAnalyticsName();\n  },\n  get precache(): string {\n    return _cacheNames.getPrecacheName();\n  },\n  get prefix(): string {\n    return _cacheNames.getPrefix();\n  },\n  get runtime(): string {\n    return _cacheNames.getRuntimeName();\n  },\n  get suffix(): string {\n    return _cacheNames.getSuffix();\n  },\n};\n\nexport {cacheNames};\n"
  },
  {
    "path": "packages/workbox-core/src/clientsClaim.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * Claim any currently available clients once the service worker\n * becomes active. This is normally used in conjunction with `skipWaiting()`.\n *\n * @memberof workbox-core\n */\nfunction clientsClaim(): void {\n  self.addEventListener('activate', () => self.clients.claim());\n}\n\nexport {clientsClaim};\n"
  },
  {
    "path": "packages/workbox-core/src/copyResponse.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {canConstructResponseFromBodyStream} from './_private/canConstructResponseFromBodyStream.js';\nimport {WorkboxError} from './_private/WorkboxError.js';\n\nimport './_version.js';\n\n/**\n * Allows developers to copy a response and modify its `headers`, `status`,\n * or `statusText` values (the values settable via a\n * [`ResponseInit`]{@link https://developer.mozilla.org/en-US/docs/Web/API/Response/Response#Syntax}\n * object in the constructor).\n * To modify these values, pass a function as the second argument. That\n * function will be invoked with a single object with the response properties\n * `{headers, status, statusText}`. The return value of this function will\n * be used as the `ResponseInit` for the new `Response`. To change the values\n * either modify the passed parameter(s) and return it, or return a totally\n * new object.\n *\n * This method is intentionally limited to same-origin responses, regardless of\n * whether CORS was used or not.\n *\n * @param {Response} response\n * @param {Function} modifier\n * @memberof workbox-core\n */\nasync function copyResponse(\n  response: Response,\n  modifier?: (responseInit: ResponseInit) => ResponseInit,\n): Promise<Response> {\n  let origin = null;\n  // If response.url isn't set, assume it's cross-origin and keep origin null.\n  if (response.url) {\n    const responseURL = new URL(response.url);\n    origin = responseURL.origin;\n  }\n\n  if (origin !== self.location.origin) {\n    throw new WorkboxError('cross-origin-copy-response', {origin});\n  }\n\n  const clonedResponse = response.clone();\n\n  // Create a fresh `ResponseInit` object by cloning the headers.\n  const responseInit: ResponseInit = {\n    headers: new Headers(clonedResponse.headers),\n    status: clonedResponse.status,\n    statusText: clonedResponse.statusText,\n  };\n\n  // Apply any user modifications.\n  const modifiedResponseInit = modifier ? modifier(responseInit) : responseInit;\n\n  // Create the new response from the body stream and `ResponseInit`\n  // modifications. Note: not all browsers support the Response.body stream,\n  // so fall back to reading the entire body into memory as a blob.\n  const body = canConstructResponseFromBodyStream()\n    ? clonedResponse.body\n    : await clonedResponse.blob();\n\n  return new Response(body, modifiedResponseInit);\n}\n\nexport {copyResponse};\n"
  },
  {
    "path": "packages/workbox-core/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {registerQuotaErrorCallback} from './registerQuotaErrorCallback.js';\nimport * as _private from './_private.js';\nimport {cacheNames} from './cacheNames.js';\nimport {copyResponse} from './copyResponse.js';\nimport {clientsClaim} from './clientsClaim.js';\nimport {setCacheNameDetails} from './setCacheNameDetails.js';\nimport {skipWaiting} from './skipWaiting.js';\nimport './_version.js';\n\n/**\n * All of the Workbox service worker libraries use workbox-core for shared\n * code as well as setting default values that need to be shared (like cache\n * names).\n *\n * @module workbox-core\n */\nexport {\n  _private,\n  cacheNames,\n  clientsClaim,\n  copyResponse,\n  registerQuotaErrorCallback,\n  setCacheNameDetails,\n  skipWaiting,\n};\n\nexport * from './types.js';\n"
  },
  {
    "path": "packages/workbox-core/src/models/messages/messageGenerator.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {messages} from './messages.js';\nimport '../../_version.js';\n\nconst fallback = (code: string, ...args: any[]) => {\n  let msg = code;\n  if (args.length > 0) {\n    msg += ` :: ${JSON.stringify(args)}`;\n  }\n  return msg;\n};\n\nconst generatorFunction = (code: string, details = {}) => {\n  const message = messages[code];\n  if (!message) {\n    throw new Error(`Unable to find message for code '${code}'.`);\n  }\n\n  return message(details);\n};\n\nexport const messageGenerator =\n  process.env.NODE_ENV === 'production' ? fallback : generatorFunction;\n"
  },
  {
    "path": "packages/workbox-core/src/models/messages/messages.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../../_version.js';\n\ninterface LoggableObject {\n  [key: string]: string | number;\n}\ninterface MessageMap {\n  [messageID: string]: (param: LoggableObject) => string;\n}\n\nexport const messages: MessageMap = {\n  'invalid-value': ({paramName, validValueDescription, value}) => {\n    if (!paramName || !validValueDescription) {\n      throw new Error(`Unexpected input to 'invalid-value' error.`);\n    }\n    return (\n      `The '${paramName}' parameter was given a value with an ` +\n      `unexpected value. ${validValueDescription} Received a value of ` +\n      `${JSON.stringify(value)}.`\n    );\n  },\n\n  'not-an-array': ({moduleName, className, funcName, paramName}) => {\n    if (!moduleName || !className || !funcName || !paramName) {\n      throw new Error(`Unexpected input to 'not-an-array' error.`);\n    }\n    return (\n      `The parameter '${paramName}' passed into ` +\n      `'${moduleName}.${className}.${funcName}()' must be an array.`\n    );\n  },\n\n  'incorrect-type': ({\n    expectedType,\n    paramName,\n    moduleName,\n    className,\n    funcName,\n  }) => {\n    if (!expectedType || !paramName || !moduleName || !funcName) {\n      throw new Error(`Unexpected input to 'incorrect-type' error.`);\n    }\n    const classNameStr = className ? `${className}.` : '';\n    return (\n      `The parameter '${paramName}' passed into ` +\n      `'${moduleName}.${classNameStr}` +\n      `${funcName}()' must be of type ${expectedType}.`\n    );\n  },\n\n  'incorrect-class': ({\n    expectedClassName,\n    paramName,\n    moduleName,\n    className,\n    funcName,\n    isReturnValueProblem,\n  }) => {\n    if (!expectedClassName || !moduleName || !funcName) {\n      throw new Error(`Unexpected input to 'incorrect-class' error.`);\n    }\n    const classNameStr = className ? `${className}.` : '';\n    if (isReturnValueProblem) {\n      return (\n        `The return value from ` +\n        `'${moduleName}.${classNameStr}${funcName}()' ` +\n        `must be an instance of class ${expectedClassName}.`\n      );\n    }\n\n    return (\n      `The parameter '${paramName}' passed into ` +\n      `'${moduleName}.${classNameStr}${funcName}()' ` +\n      `must be an instance of class ${expectedClassName}.`\n    );\n  },\n\n  'missing-a-method': ({\n    expectedMethod,\n    paramName,\n    moduleName,\n    className,\n    funcName,\n  }) => {\n    if (\n      !expectedMethod ||\n      !paramName ||\n      !moduleName ||\n      !className ||\n      !funcName\n    ) {\n      throw new Error(`Unexpected input to 'missing-a-method' error.`);\n    }\n    return (\n      `${moduleName}.${className}.${funcName}() expected the ` +\n      `'${paramName}' parameter to expose a '${expectedMethod}' method.`\n    );\n  },\n\n  'add-to-cache-list-unexpected-type': ({entry}) => {\n    return (\n      `An unexpected entry was passed to ` +\n      `'workbox-precaching.PrecacheController.addToCacheList()' The entry ` +\n      `'${JSON.stringify(\n        entry,\n      )}' isn't supported. You must supply an array of ` +\n      `strings with one or more characters, objects with a url property or ` +\n      `Request objects.`\n    );\n  },\n\n  'add-to-cache-list-conflicting-entries': ({firstEntry, secondEntry}) => {\n    if (!firstEntry || !secondEntry) {\n      throw new Error(\n        `Unexpected input to ` + `'add-to-cache-list-duplicate-entries' error.`,\n      );\n    }\n\n    return (\n      `Two of the entries passed to ` +\n      `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` +\n      `${firstEntry} but different revision details. Workbox is ` +\n      `unable to cache and version the asset correctly. Please remove one ` +\n      `of the entries.`\n    );\n  },\n\n  'plugin-error-request-will-fetch': ({thrownErrorMessage}) => {\n    if (!thrownErrorMessage) {\n      throw new Error(\n        `Unexpected input to ` + `'plugin-error-request-will-fetch', error.`,\n      );\n    }\n\n    return (\n      `An error was thrown by a plugins 'requestWillFetch()' method. ` +\n      `The thrown error message was: '${thrownErrorMessage}'.`\n    );\n  },\n\n  'invalid-cache-name': ({cacheNameId, value}) => {\n    if (!cacheNameId) {\n      throw new Error(\n        `Expected a 'cacheNameId' for error 'invalid-cache-name'`,\n      );\n    }\n\n    return (\n      `You must provide a name containing at least one character for ` +\n      `setCacheDetails({${cacheNameId}: '...'}). Received a value of ` +\n      `'${JSON.stringify(value)}'`\n    );\n  },\n\n  'unregister-route-but-not-found-with-method': ({method}) => {\n    if (!method) {\n      throw new Error(\n        `Unexpected input to ` +\n          `'unregister-route-but-not-found-with-method' error.`,\n      );\n    }\n\n    return (\n      `The route you're trying to unregister was not  previously ` +\n      `registered for the method type '${method}'.`\n    );\n  },\n\n  'unregister-route-route-not-registered': () => {\n    return (\n      `The route you're trying to unregister was not previously ` +\n      `registered.`\n    );\n  },\n\n  'queue-replay-failed': ({name}) => {\n    return `Replaying the background sync queue '${name}' failed.`;\n  },\n\n  'duplicate-queue-name': ({name}) => {\n    return (\n      `The Queue name '${name}' is already being used. ` +\n      `All instances of backgroundSync.Queue must be given unique names.`\n    );\n  },\n\n  'expired-test-without-max-age': ({methodName, paramName}) => {\n    return (\n      `The '${methodName}()' method can only be used when the ` +\n      `'${paramName}' is used in the constructor.`\n    );\n  },\n\n  'unsupported-route-type': ({moduleName, className, funcName, paramName}) => {\n    return (\n      `The supplied '${paramName}' parameter was an unsupported type. ` +\n      `Please check the docs for ${moduleName}.${className}.${funcName} for ` +\n      `valid input types.`\n    );\n  },\n\n  'not-array-of-class': ({\n    value,\n    expectedClass,\n    moduleName,\n    className,\n    funcName,\n    paramName,\n  }) => {\n    return (\n      `The supplied '${paramName}' parameter must be an array of ` +\n      `'${expectedClass}' objects. Received '${JSON.stringify(value)},'. ` +\n      `Please check the call to ${moduleName}.${className}.${funcName}() ` +\n      `to fix the issue.`\n    );\n  },\n\n  'max-entries-or-age-required': ({moduleName, className, funcName}) => {\n    return (\n      `You must define either config.maxEntries or config.maxAgeSeconds` +\n      `in ${moduleName}.${className}.${funcName}`\n    );\n  },\n\n  'statuses-or-headers-required': ({moduleName, className, funcName}) => {\n    return (\n      `You must define either config.statuses or config.headers` +\n      `in ${moduleName}.${className}.${funcName}`\n    );\n  },\n\n  'invalid-string': ({moduleName, funcName, paramName}) => {\n    if (!paramName || !moduleName || !funcName) {\n      throw new Error(`Unexpected input to 'invalid-string' error.`);\n    }\n    return (\n      `When using strings, the '${paramName}' parameter must start with ` +\n      `'http' (for cross-origin matches) or '/' (for same-origin matches). ` +\n      `Please see the docs for ${moduleName}.${funcName}() for ` +\n      `more info.`\n    );\n  },\n\n  'channel-name-required': () => {\n    return (\n      `You must provide a channelName to construct a ` +\n      `BroadcastCacheUpdate instance.`\n    );\n  },\n\n  'invalid-responses-are-same-args': () => {\n    return (\n      `The arguments passed into responsesAreSame() appear to be ` +\n      `invalid. Please ensure valid Responses are used.`\n    );\n  },\n\n  'expire-custom-caches-only': () => {\n    return (\n      `You must provide a 'cacheName' property when using the ` +\n      `expiration plugin with a runtime caching strategy.`\n    );\n  },\n\n  'unit-must-be-bytes': ({normalizedRangeHeader}) => {\n    if (!normalizedRangeHeader) {\n      throw new Error(`Unexpected input to 'unit-must-be-bytes' error.`);\n    }\n    return (\n      `The 'unit' portion of the Range header must be set to 'bytes'. ` +\n      `The Range header provided was \"${normalizedRangeHeader}\"`\n    );\n  },\n\n  'single-range-only': ({normalizedRangeHeader}) => {\n    if (!normalizedRangeHeader) {\n      throw new Error(`Unexpected input to 'single-range-only' error.`);\n    }\n    return (\n      `Multiple ranges are not supported. Please use a  single start ` +\n      `value, and optional end value. The Range header provided was ` +\n      `\"${normalizedRangeHeader}\"`\n    );\n  },\n\n  'invalid-range-values': ({normalizedRangeHeader}) => {\n    if (!normalizedRangeHeader) {\n      throw new Error(`Unexpected input to 'invalid-range-values' error.`);\n    }\n    return (\n      `The Range header is missing both start and end values. At least ` +\n      `one of those values is needed. The Range header provided was ` +\n      `\"${normalizedRangeHeader}\"`\n    );\n  },\n\n  'no-range-header': () => {\n    return `No Range header was found in the Request provided.`;\n  },\n\n  'range-not-satisfiable': ({size, start, end}) => {\n    return (\n      `The start (${start}) and end (${end}) values in the Range are ` +\n      `not satisfiable by the cached response, which is ${size} bytes.`\n    );\n  },\n\n  'attempt-to-cache-non-get-request': ({url, method}) => {\n    return (\n      `Unable to cache '${url}' because it is a '${method}' request and ` +\n      `only 'GET' requests can be cached.`\n    );\n  },\n\n  'cache-put-with-no-response': ({url}) => {\n    return (\n      `There was an attempt to cache '${url}' but the response was not ` +\n      `defined.`\n    );\n  },\n\n  'no-response': ({url, error}) => {\n    let message = `The strategy could not generate a response for '${url}'.`;\n    if (error) {\n      message += ` The underlying error is ${error}.`;\n    }\n    return message;\n  },\n\n  'bad-precaching-response': ({url, status}) => {\n    return (\n      `The precaching request for '${url}' failed` +\n      (status ? ` with an HTTP status of ${status}.` : `.`)\n    );\n  },\n\n  'non-precached-url': ({url}) => {\n    return (\n      `createHandlerBoundToURL('${url}') was called, but that URL is ` +\n      `not precached. Please pass in a URL that is precached instead.`\n    );\n  },\n\n  'add-to-cache-list-conflicting-integrities': ({url}) => {\n    return (\n      `Two of the entries passed to ` +\n      `'workbox-precaching.PrecacheController.addToCacheList()' had the URL ` +\n      `${url} with different integrity values. Please remove one of them.`\n    );\n  },\n\n  'missing-precache-entry': ({cacheName, url}) => {\n    return `Unable to find a precached response in ${cacheName} for ${url}.`;\n  },\n\n  'cross-origin-copy-response': ({origin}) => {\n    return (\n      `workbox-core.copyResponse() can only be used with same-origin ` +\n      `responses. It was passed a response with origin ${origin}.`\n    );\n  },\n\n  'opaque-streams-source': ({type}) => {\n    const message =\n      `One of the workbox-streams sources resulted in an ` +\n      `'${type}' response.`;\n    if (type === 'opaqueredirect') {\n      return (\n        `${message} Please do not use a navigation request that results ` +\n        `in a redirect as a source.`\n      );\n    }\n    return `${message} Please ensure your sources are CORS-enabled.`;\n  },\n};\n"
  },
  {
    "path": "packages/workbox-core/src/models/pluginEvents.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nexport const enum pluginEvents {\n  CACHE_DID_UPDATE = 'cacheDidUpdate',\n  CACHE_KEY_WILL_BE_USED = 'cacheKeyWillBeUsed',\n  CACHE_WILL_UPDATE = 'cacheWillUpdate',\n  CACHED_RESPONSE_WILL_BE_USED = 'cachedResponseWillBeUsed',\n  FETCH_DID_FAIL = 'fetchDidFail',\n  FETCH_DID_SUCCEED = 'fetchDidSucceed',\n  REQUEST_WILL_FETCH = 'requestWillFetch',\n}\n"
  },
  {
    "path": "packages/workbox-core/src/models/quotaErrorCallbacks.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n// Callbacks to be executed whenever there's a quota error.\n// Can't change Function type right now.\n// eslint-disable-next-line @typescript-eslint/ban-types\nconst quotaErrorCallbacks: Set<Function> = new Set();\n\nexport {quotaErrorCallbacks};\n"
  },
  {
    "path": "packages/workbox-core/src/registerQuotaErrorCallback.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from './_private/logger.js';\nimport {assert} from './_private/assert.js';\nimport {quotaErrorCallbacks} from './models/quotaErrorCallbacks.js';\nimport './_version.js';\n\n/**\n * Adds a function to the set of quotaErrorCallbacks that will be executed if\n * there's a quota error.\n *\n * @param {Function} callback\n * @memberof workbox-core\n */\n// Can't change Function type\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction registerQuotaErrorCallback(callback: Function): void {\n  if (process.env.NODE_ENV !== 'production') {\n    assert!.isType(callback, 'function', {\n      moduleName: 'workbox-core',\n      funcName: 'register',\n      paramName: 'callback',\n    });\n  }\n\n  quotaErrorCallbacks.add(callback);\n\n  if (process.env.NODE_ENV !== 'production') {\n    logger.log('Registered a callback to respond to quota errors.', callback);\n  }\n}\n\nexport {registerQuotaErrorCallback};\n"
  },
  {
    "path": "packages/workbox-core/src/setCacheNameDetails.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from './_private/assert.js';\nimport {cacheNames, PartialCacheNameDetails} from './_private/cacheNames.js';\nimport {WorkboxError} from './_private/WorkboxError.js';\nimport './_version.js';\n\n/**\n * Modifies the default cache names used by the Workbox packages.\n * Cache names are generated as `<prefix>-<Cache Name>-<suffix>`.\n *\n * @param {Object} details\n * @param {Object} [details.prefix] The string to add to the beginning of\n *     the precache and runtime cache names.\n * @param {Object} [details.suffix] The string to add to the end of\n *     the precache and runtime cache names.\n * @param {Object} [details.precache] The cache name to use for precache\n *     caching.\n * @param {Object} [details.runtime] The cache name to use for runtime caching.\n * @param {Object} [details.googleAnalytics] The cache name to use for\n *     `workbox-google-analytics` caching.\n *\n * @memberof workbox-core\n */\nfunction setCacheNameDetails(details: PartialCacheNameDetails): void {\n  if (process.env.NODE_ENV !== 'production') {\n    Object.keys(details).forEach((key) => {\n      assert!.isType(details[key], 'string', {\n        moduleName: 'workbox-core',\n        funcName: 'setCacheNameDetails',\n        paramName: `details.${key}`,\n      });\n    });\n\n    if ('precache' in details && details['precache']!.length === 0) {\n      throw new WorkboxError('invalid-cache-name', {\n        cacheNameId: 'precache',\n        value: details['precache'],\n      });\n    }\n\n    if ('runtime' in details && details['runtime']!.length === 0) {\n      throw new WorkboxError('invalid-cache-name', {\n        cacheNameId: 'runtime',\n        value: details['runtime'],\n      });\n    }\n\n    if (\n      'googleAnalytics' in details &&\n      details['googleAnalytics'].length === 0\n    ) {\n      throw new WorkboxError('invalid-cache-name', {\n        cacheNameId: 'googleAnalytics',\n        value: details['googleAnalytics'],\n      });\n    }\n  }\n\n  cacheNames.updateDetails(details);\n}\n\nexport {setCacheNameDetails};\n"
  },
  {
    "path": "packages/workbox-core/src/skipWaiting.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from './_private/logger.js';\n\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * This method is deprecated, and will be removed in Workbox v7.\n *\n * Calling self.skipWaiting() is equivalent, and should be used instead.\n *\n * @memberof workbox-core\n */\nfunction skipWaiting(): void {\n  // Just call self.skipWaiting() directly.\n  // See https://github.com/GoogleChrome/workbox/issues/2525\n  if (process.env.NODE_ENV !== 'production') {\n    logger.warn(\n      `skipWaiting() from workbox-core is no longer recommended ` +\n        `and will be removed in Workbox v7. Using self.skipWaiting() instead ` +\n        `is equivalent.`,\n    );\n  }\n\n  void self.skipWaiting();\n}\n\nexport {skipWaiting};\n"
  },
  {
    "path": "packages/workbox-core/src/types.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\nexport interface MapLikeObject {\n  [key: string]: any;\n}\n\n/**\n * Using a plain `MapLikeObject` for now, but could extend/restrict this\n * in the future.\n */\nexport type PluginState = MapLikeObject;\n\n/**\n * Options passed to a `RouteMatchCallback` function.\n */\nexport interface RouteMatchCallbackOptions {\n  event: ExtendableEvent;\n  request: Request;\n  sameOrigin: boolean;\n  url: URL;\n}\n\n/**\n * The \"match\" callback is used to determine if a `Route` should apply for a\n * particular URL and request. When matching occurs in response to a fetch\n * event from the client, the `event` object is also supplied. However, since\n * the match callback can be invoked outside of a fetch event, matching logic\n * should not assume the `event` object will always be available.\n * If the match callback returns a truthy value, the matching route's\n * `RouteHandlerCallback` will be invoked immediately. If the value returned\n * is a non-empty array or object, that value will be set on the handler's\n * `options.params` argument.\n */\nexport interface RouteMatchCallback {\n  (options: RouteMatchCallbackOptions): any;\n}\n\n/**\n * Options passed to a `RouteHandlerCallback` function.\n */\nexport declare interface RouteHandlerCallbackOptions {\n  event: ExtendableEvent;\n  request: Request;\n  url: URL;\n  params?: string[] | MapLikeObject;\n}\n\n/**\n * Options passed to a `ManualHandlerCallback` function.\n */\nexport interface ManualHandlerCallbackOptions {\n  event: ExtendableEvent;\n  request: Request | string;\n}\n\nexport type HandlerCallbackOptions =\n  | RouteHandlerCallbackOptions\n  | ManualHandlerCallbackOptions;\n\n/**\n * The \"handler\" callback is invoked whenever a `Router` matches a URL/Request\n * to a `Route` via its `RouteMatchCallback`. This handler callback should\n * return a `Promise` that resolves with a `Response`.\n *\n * If a non-empty array or object is returned by the `RouteMatchCallback` it\n * will be passed in as this handler's `options.params` argument.\n */\nexport interface RouteHandlerCallback {\n  (options: RouteHandlerCallbackOptions): Promise<Response>;\n}\n\n/**\n * The \"handler\" callback is invoked whenever a `Router` matches a URL/Request\n * to a `Route` via its `RouteMatchCallback`. This handler callback should\n * return a `Promise` that resolves with a `Response`.\n *\n * If a non-empty array or object is returned by the `RouteMatchCallback` it\n * will be passed in as this handler's `options.params` argument.\n */\nexport interface ManualHandlerCallback {\n  (options: ManualHandlerCallbackOptions): Promise<Response>;\n}\n\n/**\n * An object with a `handle` method of type `RouteHandlerCallback`.\n *\n * A `Route` object can be created with either an `RouteHandlerCallback`\n * function or this `RouteHandler` object. The benefit of the `RouteHandler`\n * is it can be extended (as is done by the `workbox-strategies` package).\n */\nexport interface RouteHandlerObject {\n  handle: RouteHandlerCallback;\n}\n\n/**\n * Either a `RouteHandlerCallback` or a `RouteHandlerObject`.\n * Most APIs in `workbox-routing` that accept route handlers take either.\n */\nexport type RouteHandler = RouteHandlerCallback | RouteHandlerObject;\n\nexport interface HandlerWillStartCallbackParam {\n  request: Request;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface HandlerWillStartCallback {\n  (param: HandlerWillStartCallbackParam): Promise<void | null | undefined>;\n}\n\nexport interface CacheDidUpdateCallbackParam {\n  cacheName: string;\n  newResponse: Response;\n  request: Request;\n  event: ExtendableEvent;\n  oldResponse?: Response | null;\n  state?: PluginState;\n}\n\nexport interface CacheDidUpdateCallback {\n  (param: CacheDidUpdateCallbackParam): Promise<void | null | undefined>;\n}\n\nexport interface CacheKeyWillBeUsedCallbackParam {\n  mode: string;\n  request: Request;\n  event: ExtendableEvent;\n  params?: any;\n  state?: PluginState;\n}\n\nexport interface CacheKeyWillBeUsedCallback {\n  (param: CacheKeyWillBeUsedCallbackParam): Promise<Request | string>;\n}\n\nexport interface CacheWillUpdateCallbackParam {\n  request: Request;\n  response: Response;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface CacheWillUpdateCallback {\n  (param: CacheWillUpdateCallbackParam): Promise<\n    Response | void | null | undefined\n  >;\n}\n\nexport interface CachedResponseWillBeUsedCallbackParam {\n  cacheName: string;\n  request: Request;\n  cachedResponse?: Response;\n  event: ExtendableEvent;\n  matchOptions?: CacheQueryOptions;\n  state?: PluginState;\n}\n\nexport interface CachedResponseWillBeUsedCallback {\n  (param: CachedResponseWillBeUsedCallbackParam): Promise<\n    Response | void | null | undefined\n  >;\n}\n\nexport interface FetchDidFailCallbackParam {\n  error: Error;\n  originalRequest: Request;\n  request: Request;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface FetchDidFailCallback {\n  (param: FetchDidFailCallbackParam): Promise<void | null | undefined>;\n}\n\nexport interface FetchDidSucceedCallbackParam {\n  request: Request;\n  response: Response;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface FetchDidSucceedCallback {\n  (param: FetchDidSucceedCallbackParam): Promise<Response>;\n}\n\nexport interface RequestWillFetchCallbackParam {\n  request: Request;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface RequestWillFetchCallback {\n  (param: RequestWillFetchCallbackParam): Promise<Request>;\n}\n\nexport interface HandlerWillRespondCallbackParam {\n  request: Request;\n  response: Response;\n  event: ExtendableEvent;\n  state?: PluginState;\n}\n\nexport interface HandlerWillRespondCallback {\n  (param: HandlerWillRespondCallbackParam): Promise<Response>;\n}\n\nexport interface HandlerDidErrorCallbackParam {\n  request: Request;\n  event: ExtendableEvent;\n  error: Error;\n  state?: PluginState;\n}\n\nexport interface HandlerDidErrorCallback {\n  (param: HandlerDidErrorCallbackParam): Promise<Response | undefined>;\n}\n\nexport interface HandlerDidRespondCallbackParam {\n  request: Request;\n  event: ExtendableEvent;\n  response?: Response;\n  state?: PluginState;\n}\n\nexport interface HandlerDidRespondCallback {\n  (param: HandlerDidRespondCallbackParam): Promise<void | null | undefined>;\n}\n\nexport interface HandlerDidCompleteCallbackParam {\n  request: Request;\n  error?: Error;\n  event: ExtendableEvent;\n  response?: Response;\n  state?: PluginState;\n}\n\nexport interface HandlerDidCompleteCallback {\n  (param: HandlerDidCompleteCallbackParam): Promise<void | null | undefined>;\n}\n\n/**\n * An object with optional lifecycle callback properties for the fetch and\n * cache operations.\n */\nexport declare interface WorkboxPlugin {\n  cacheDidUpdate?: CacheDidUpdateCallback;\n  cachedResponseWillBeUsed?: CachedResponseWillBeUsedCallback;\n  cacheKeyWillBeUsed?: CacheKeyWillBeUsedCallback;\n  cacheWillUpdate?: CacheWillUpdateCallback;\n  fetchDidFail?: FetchDidFailCallback;\n  fetchDidSucceed?: FetchDidSucceedCallback;\n  handlerDidComplete?: HandlerDidCompleteCallback;\n  handlerDidError?: HandlerDidErrorCallback;\n  handlerDidRespond?: HandlerDidRespondCallback;\n  handlerWillRespond?: HandlerWillRespondCallback;\n  handlerWillStart?: HandlerWillStartCallback;\n  requestWillFetch?: RequestWillFetchCallback;\n}\n\nexport interface WorkboxPluginCallbackParam {\n  cacheDidUpdate: CacheDidUpdateCallbackParam;\n  cachedResponseWillBeUsed: CachedResponseWillBeUsedCallbackParam;\n  cacheKeyWillBeUsed: CacheKeyWillBeUsedCallbackParam;\n  cacheWillUpdate: CacheWillUpdateCallbackParam;\n  fetchDidFail: FetchDidFailCallbackParam;\n  fetchDidSucceed: FetchDidSucceedCallbackParam;\n  handlerDidComplete: HandlerDidCompleteCallbackParam;\n  handlerDidError: HandlerDidErrorCallbackParam;\n  handlerDidRespond: HandlerDidRespondCallbackParam;\n  handlerWillRespond: HandlerWillRespondCallbackParam;\n  handlerWillStart: HandlerWillStartCallbackParam;\n  requestWillFetch: RequestWillFetchCallbackParam;\n}\n"
  },
  {
    "path": "packages/workbox-core/src/utils/pluginUtils.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from '../types.js';\nimport '../_version.js';\n\nexport const pluginUtils = {\n  filter: (plugins: WorkboxPlugin[], callbackName: string): WorkboxPlugin[] => {\n    return plugins.filter((plugin) => callbackName in plugin);\n  },\n};\n"
  },
  {
    "path": "packages/workbox-core/src/utils/welcome.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from '../_private/logger.js';\nimport '../_version.js';\n\n// A WorkboxCore instance must be exported before we can use the logger.\n// This is so it can get the current log level.\nif (process.env.NODE_ENV !== 'production') {\n  const padding = '   ';\n  logger.groupCollapsed('Welcome to Workbox!');\n  logger.log(\n    `You are currently using a development build. ` +\n      `By default this will switch to prod builds when not on localhost. ` +\n      `You can force this with workbox.setConfig({debug: true|false}).`,\n  );\n  logger.log(\n    `📖 Read the guides and documentation\\n` +\n      `${padding}https://developers.google.com/web/tools/workbox/`,\n  );\n  logger.log(\n    `❓ Use the [workbox] tag on Stack Overflow to ask questions\\n` +\n      `${padding}https://stackoverflow.com/questions/ask?tags=workbox`,\n  );\n  logger.log(\n    `🐛 Found a bug? Report it on GitHub\\n` +\n      `${padding}https://github.com/GoogleChrome/workbox/issues/new`,\n  );\n  logger.groupEnd();\n}\n"
  },
  {
    "path": "packages/workbox-core/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "packages/workbox-expiration/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-expiration\n"
  },
  {
    "path": "packages/workbox-expiration/package.json",
    "content": "{\n  \"name\": \"workbox-expiration\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A service worker helper library that expires cached responses based on age or maximum number of entries.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"workbox-plugin\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.expiration\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"idb\": \"^7.0.1\",\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-expiration/src/CacheExpiration.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {dontWaitFor} from 'workbox-core/_private/dontWaitFor.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {CacheTimestampsModel} from './models/CacheTimestampsModel.js';\n\nimport './_version.js';\n\ninterface CacheExpirationConfig {\n  maxEntries?: number;\n  maxAgeSeconds?: number;\n  matchOptions?: CacheQueryOptions;\n}\n\n/**\n * The `CacheExpiration` class allows you define an expiration and / or\n * limit on the number of responses stored in a\n * [`Cache`](https://developer.mozilla.org/en-US/docs/Web/API/Cache).\n *\n * @memberof workbox-expiration\n */\nclass CacheExpiration {\n  private _isRunning = false;\n  private _rerunRequested = false;\n  private readonly _maxEntries?: number;\n  private readonly _maxAgeSeconds?: number;\n  private readonly _matchOptions?: CacheQueryOptions;\n  private readonly _cacheName: string;\n  private readonly _timestampModel: CacheTimestampsModel;\n\n  /**\n   * To construct a new CacheExpiration instance you must provide at least\n   * one of the `config` properties.\n   *\n   * @param {string} cacheName Name of the cache to apply restrictions to.\n   * @param {Object} config\n   * @param {number} [config.maxEntries] The maximum number of entries to cache.\n   * Entries used the least will be removed as the maximum is reached.\n   * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n   * it's treated as stale and removed.\n   * @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)\n   * that will be used when calling `delete()` on the cache.\n   */\n  constructor(cacheName: string, config: CacheExpirationConfig = {}) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(cacheName, 'string', {\n        moduleName: 'workbox-expiration',\n        className: 'CacheExpiration',\n        funcName: 'constructor',\n        paramName: 'cacheName',\n      });\n\n      if (!(config.maxEntries || config.maxAgeSeconds)) {\n        throw new WorkboxError('max-entries-or-age-required', {\n          moduleName: 'workbox-expiration',\n          className: 'CacheExpiration',\n          funcName: 'constructor',\n        });\n      }\n\n      if (config.maxEntries) {\n        assert!.isType(config.maxEntries, 'number', {\n          moduleName: 'workbox-expiration',\n          className: 'CacheExpiration',\n          funcName: 'constructor',\n          paramName: 'config.maxEntries',\n        });\n      }\n\n      if (config.maxAgeSeconds) {\n        assert!.isType(config.maxAgeSeconds, 'number', {\n          moduleName: 'workbox-expiration',\n          className: 'CacheExpiration',\n          funcName: 'constructor',\n          paramName: 'config.maxAgeSeconds',\n        });\n      }\n    }\n\n    this._maxEntries = config.maxEntries;\n    this._maxAgeSeconds = config.maxAgeSeconds;\n    this._matchOptions = config.matchOptions;\n    this._cacheName = cacheName;\n    this._timestampModel = new CacheTimestampsModel(cacheName);\n  }\n\n  /**\n   * Expires entries for the given cache and given criteria.\n   */\n  async expireEntries(): Promise<void> {\n    if (this._isRunning) {\n      this._rerunRequested = true;\n      return;\n    }\n    this._isRunning = true;\n\n    const minTimestamp = this._maxAgeSeconds\n      ? Date.now() - this._maxAgeSeconds * 1000\n      : 0;\n\n    const urlsExpired = await this._timestampModel.expireEntries(\n      minTimestamp,\n      this._maxEntries,\n    );\n\n    // Delete URLs from the cache\n    const cache = await self.caches.open(this._cacheName);\n    for (const url of urlsExpired) {\n      await cache.delete(url, this._matchOptions);\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (urlsExpired.length > 0) {\n        logger.groupCollapsed(\n          `Expired ${urlsExpired.length} ` +\n            `${urlsExpired.length === 1 ? 'entry' : 'entries'} and removed ` +\n            `${urlsExpired.length === 1 ? 'it' : 'them'} from the ` +\n            `'${this._cacheName}' cache.`,\n        );\n        logger.log(\n          `Expired the following ${urlsExpired.length === 1 ? 'URL' : 'URLs'}:`,\n        );\n        urlsExpired.forEach((url) => logger.log(`    ${url}`));\n        logger.groupEnd();\n      } else {\n        logger.debug(`Cache expiration ran and found no entries to remove.`);\n      }\n    }\n\n    this._isRunning = false;\n    if (this._rerunRequested) {\n      this._rerunRequested = false;\n      dontWaitFor(this.expireEntries());\n    }\n  }\n\n  /**\n   * Update the timestamp for the given URL. This ensures the when\n   * removing entries based on maximum entries, most recently used\n   * is accurate or when expiring, the timestamp is up-to-date.\n   *\n   * @param {string} url\n   */\n  async updateTimestamp(url: string): Promise<void> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(url, 'string', {\n        moduleName: 'workbox-expiration',\n        className: 'CacheExpiration',\n        funcName: 'updateTimestamp',\n        paramName: 'url',\n      });\n    }\n\n    await this._timestampModel.setTimestamp(url, Date.now());\n  }\n\n  /**\n   * Can be used to check if a URL has expired or not before it's used.\n   *\n   * This requires a look up from IndexedDB, so can be slow.\n   *\n   * Note: This method will not remove the cached entry, call\n   * `expireEntries()` to remove indexedDB and Cache entries.\n   *\n   * @param {string} url\n   * @return {boolean}\n   */\n  async isURLExpired(url: string): Promise<boolean> {\n    if (!this._maxAgeSeconds) {\n      if (process.env.NODE_ENV !== 'production') {\n        throw new WorkboxError(`expired-test-without-max-age`, {\n          methodName: 'isURLExpired',\n          paramName: 'maxAgeSeconds',\n        });\n      }\n      return false;\n    } else {\n      const timestamp = await this._timestampModel.getTimestamp(url);\n      const expireOlderThan = Date.now() - this._maxAgeSeconds * 1000;\n      return timestamp !== undefined ? timestamp < expireOlderThan : true;\n    }\n  }\n\n  /**\n   * Removes the IndexedDB object store used to keep track of cache expiration\n   * metadata.\n   */\n  async delete(): Promise<void> {\n    // Make sure we don't attempt another rerun if we're called in the middle of\n    // a cache expiration.\n    this._rerunRequested = false;\n    await this._timestampModel.expireEntries(Infinity); // Expires all.\n  }\n}\n\nexport {CacheExpiration};\n"
  },
  {
    "path": "packages/workbox-expiration/src/ExpirationPlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {dontWaitFor} from 'workbox-core/_private/dontWaitFor.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {WorkboxPlugin} from 'workbox-core/types.js';\n\nimport {CacheExpiration} from './CacheExpiration.js';\n\nimport './_version.js';\n\nexport interface ExpirationPluginOptions {\n  maxEntries?: number;\n  maxAgeSeconds?: number;\n  matchOptions?: CacheQueryOptions;\n  purgeOnQuotaError?: boolean;\n}\n\n/**\n * This plugin can be used in a `workbox-strategy` to regularly enforce a\n * limit on the age and / or the number of cached requests.\n *\n * It can only be used with `workbox-strategy` instances that have a\n * [custom `cacheName` property set](/web/tools/workbox/guides/configure-workbox#custom_cache_names_in_strategies).\n * In other words, it can't be used to expire entries in strategy that uses the\n * default runtime cache name.\n *\n * Whenever a cached response is used or updated, this plugin will look\n * at the associated cache and remove any old or extra responses.\n *\n * When using `maxAgeSeconds`, responses may be used *once* after expiring\n * because the expiration clean up will not have occurred until *after* the\n * cached response has been used. If the response has a \"Date\" header, then\n * a light weight expiration check is performed and the response will not be\n * used immediately.\n *\n * When using `maxEntries`, the entry least-recently requested will be removed\n * from the cache first.\n *\n * @memberof workbox-expiration\n */\nclass ExpirationPlugin implements WorkboxPlugin {\n  private readonly _config: ExpirationPluginOptions;\n  private readonly _maxAgeSeconds?: number;\n  private _cacheExpirations: Map<string, CacheExpiration>;\n\n  /**\n   * @param {ExpirationPluginOptions} config\n   * @param {number} [config.maxEntries] The maximum number of entries to cache.\n   * Entries used the least will be removed as the maximum is reached.\n   * @param {number} [config.maxAgeSeconds] The maximum age of an entry before\n   * it's treated as stale and removed.\n   * @param {Object} [config.matchOptions] The [`CacheQueryOptions`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/delete#Parameters)\n   * that will be used when calling `delete()` on the cache.\n   * @param {boolean} [config.purgeOnQuotaError] Whether to opt this cache in to\n   * automatic deletion if the available storage quota has been exceeded.\n   */\n  constructor(config: ExpirationPluginOptions = {}) {\n    if (process.env.NODE_ENV !== 'production') {\n      if (!(config.maxEntries || config.maxAgeSeconds)) {\n        throw new WorkboxError('max-entries-or-age-required', {\n          moduleName: 'workbox-expiration',\n          className: 'Plugin',\n          funcName: 'constructor',\n        });\n      }\n\n      if (config.maxEntries) {\n        assert!.isType(config.maxEntries, 'number', {\n          moduleName: 'workbox-expiration',\n          className: 'Plugin',\n          funcName: 'constructor',\n          paramName: 'config.maxEntries',\n        });\n      }\n\n      if (config.maxAgeSeconds) {\n        assert!.isType(config.maxAgeSeconds, 'number', {\n          moduleName: 'workbox-expiration',\n          className: 'Plugin',\n          funcName: 'constructor',\n          paramName: 'config.maxAgeSeconds',\n        });\n      }\n    }\n\n    this._config = config;\n    this._maxAgeSeconds = config.maxAgeSeconds;\n    this._cacheExpirations = new Map();\n\n    if (config.purgeOnQuotaError) {\n      registerQuotaErrorCallback(() => this.deleteCacheAndMetadata());\n    }\n  }\n\n  /**\n   * A simple helper method to return a CacheExpiration instance for a given\n   * cache name.\n   *\n   * @param {string} cacheName\n   * @return {CacheExpiration}\n   *\n   * @private\n   */\n  private _getCacheExpiration(cacheName: string): CacheExpiration {\n    if (cacheName === cacheNames.getRuntimeName()) {\n      throw new WorkboxError('expire-custom-caches-only');\n    }\n\n    let cacheExpiration = this._cacheExpirations.get(cacheName);\n    if (!cacheExpiration) {\n      cacheExpiration = new CacheExpiration(cacheName, this._config);\n      this._cacheExpirations.set(cacheName, cacheExpiration);\n    }\n    return cacheExpiration;\n  }\n\n  /**\n   * A \"lifecycle\" callback that will be triggered automatically by the\n   * `workbox-strategies` handlers when a `Response` is about to be returned\n   * from a [Cache](https://developer.mozilla.org/en-US/docs/Web/API/Cache) to\n   * the handler. It allows the `Response` to be inspected for freshness and\n   * prevents it from being used if the `Response`'s `Date` header value is\n   * older than the configured `maxAgeSeconds`.\n   *\n   * @param {Object} options\n   * @param {string} options.cacheName Name of the cache the response is in.\n   * @param {Response} options.cachedResponse The `Response` object that's been\n   *     read from a cache and whose freshness should be checked.\n   * @return {Response} Either the `cachedResponse`, if it's\n   *     fresh, or `null` if the `Response` is older than `maxAgeSeconds`.\n   *\n   * @private\n   */\n  cachedResponseWillBeUsed: WorkboxPlugin['cachedResponseWillBeUsed'] = async ({\n    event,\n    request,\n    cacheName,\n    cachedResponse,\n  }) => {\n    if (!cachedResponse) {\n      return null;\n    }\n\n    const isFresh = this._isResponseDateFresh(cachedResponse);\n\n    // Expire entries to ensure that even if the expiration date has\n    // expired, it'll only be used once.\n    const cacheExpiration = this._getCacheExpiration(cacheName);\n    dontWaitFor(cacheExpiration.expireEntries());\n\n    // Update the metadata for the request URL to the current timestamp,\n    // but don't `await` it as we don't want to block the response.\n    const updateTimestampDone = cacheExpiration.updateTimestamp(request.url);\n    if (event) {\n      try {\n        event.waitUntil(updateTimestampDone);\n      } catch (error) {\n        if (process.env.NODE_ENV !== 'production') {\n          // The event may not be a fetch event; only log the URL if it is.\n          if ('request' in event) {\n            logger.warn(\n              `Unable to ensure service worker stays alive when ` +\n                `updating cache entry for ` +\n                `'${getFriendlyURL((event as FetchEvent).request.url)}'.`,\n            );\n          }\n        }\n      }\n    }\n\n    return isFresh ? cachedResponse : null;\n  };\n\n  /**\n   * @param {Response} cachedResponse\n   * @return {boolean}\n   *\n   * @private\n   */\n  private _isResponseDateFresh(cachedResponse: Response): boolean {\n    if (!this._maxAgeSeconds) {\n      // We aren't expiring by age, so return true, it's fresh\n      return true;\n    }\n\n    // Check if the 'date' header will suffice a quick expiration check.\n    // See https://github.com/GoogleChromeLabs/sw-toolbox/issues/164 for\n    // discussion.\n    const dateHeaderTimestamp = this._getDateHeaderTimestamp(cachedResponse);\n    if (dateHeaderTimestamp === null) {\n      // Unable to parse date, so assume it's fresh.\n      return true;\n    }\n\n    // If we have a valid headerTime, then our response is fresh iff the\n    // headerTime plus maxAgeSeconds is greater than the current time.\n    const now = Date.now();\n    return dateHeaderTimestamp >= now - this._maxAgeSeconds * 1000;\n  }\n\n  /**\n   * This method will extract the data header and parse it into a useful\n   * value.\n   *\n   * @param {Response} cachedResponse\n   * @return {number|null}\n   *\n   * @private\n   */\n  private _getDateHeaderTimestamp(cachedResponse: Response): number | null {\n    if (!cachedResponse.headers.has('date')) {\n      return null;\n    }\n\n    const dateHeader = cachedResponse.headers.get('date');\n    const parsedDate = new Date(dateHeader!);\n    const headerTime = parsedDate.getTime();\n\n    // If the Date header was invalid for some reason, parsedDate.getTime()\n    // will return NaN.\n    if (isNaN(headerTime)) {\n      return null;\n    }\n\n    return headerTime;\n  }\n\n  /**\n   * A \"lifecycle\" callback that will be triggered automatically by the\n   * `workbox-strategies` handlers when an entry is added to a cache.\n   *\n   * @param {Object} options\n   * @param {string} options.cacheName Name of the cache that was updated.\n   * @param {string} options.request The Request for the cached entry.\n   *\n   * @private\n   */\n  cacheDidUpdate: WorkboxPlugin['cacheDidUpdate'] = async ({\n    cacheName,\n    request,\n  }) => {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(cacheName, 'string', {\n        moduleName: 'workbox-expiration',\n        className: 'Plugin',\n        funcName: 'cacheDidUpdate',\n        paramName: 'cacheName',\n      });\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-expiration',\n        className: 'Plugin',\n        funcName: 'cacheDidUpdate',\n        paramName: 'request',\n      });\n    }\n\n    const cacheExpiration = this._getCacheExpiration(cacheName);\n    await cacheExpiration.updateTimestamp(request.url);\n    await cacheExpiration.expireEntries();\n  };\n\n  /**\n   * This is a helper method that performs two operations:\n   *\n   * - Deletes *all* the underlying Cache instances associated with this plugin\n   * instance, by calling caches.delete() on your behalf.\n   * - Deletes the metadata from IndexedDB used to keep track of expiration\n   * details for each Cache instance.\n   *\n   * When using cache expiration, calling this method is preferable to calling\n   * `caches.delete()` directly, since this will ensure that the IndexedDB\n   * metadata is also cleanly removed and open IndexedDB instances are deleted.\n   *\n   * Note that if you're *not* using cache expiration for a given cache, calling\n   * `caches.delete()` and passing in the cache's name should be sufficient.\n   * There is no Workbox-specific method needed for cleanup in that case.\n   */\n  async deleteCacheAndMetadata(): Promise<void> {\n    // Do this one at a time instead of all at once via `Promise.all()` to\n    // reduce the chance of inconsistency if a promise rejects.\n    for (const [cacheName, cacheExpiration] of this._cacheExpirations) {\n      await self.caches.delete(cacheName);\n      await cacheExpiration.delete();\n    }\n\n    // Reset this._cacheExpirations to its initial state.\n    this._cacheExpirations = new Map();\n  }\n}\n\nexport {ExpirationPlugin};\n"
  },
  {
    "path": "packages/workbox-expiration/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:expiration:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-expiration/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheExpiration} from './CacheExpiration.js';\nimport {ExpirationPlugin, ExpirationPluginOptions} from './ExpirationPlugin.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-expiration\n */\n\nexport {CacheExpiration, ExpirationPlugin, ExpirationPluginOptions};\n"
  },
  {
    "path": "packages/workbox-expiration/src/models/CacheTimestampsModel.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {openDB, DBSchema, IDBPDatabase, deleteDB} from 'idb';\nimport '../_version.js';\n\nconst DB_NAME = 'workbox-expiration';\nconst CACHE_OBJECT_STORE = 'cache-entries';\n\nconst normalizeURL = (unNormalizedUrl: string) => {\n  const url = new URL(unNormalizedUrl, location.href);\n  url.hash = '';\n\n  return url.href;\n};\n\ninterface CacheTimestampsModelEntry {\n  id: string;\n  cacheName: string;\n  url: string;\n  timestamp: number;\n}\n\ninterface CacheDbSchema extends DBSchema {\n  'cache-entries': {\n    key: string;\n    value: CacheTimestampsModelEntry;\n    indexes: {cacheName: string; timestamp: number};\n  };\n}\n\n/**\n * Returns the timestamp model.\n *\n * @private\n */\nclass CacheTimestampsModel {\n  private readonly _cacheName: string;\n  private _db: IDBPDatabase<CacheDbSchema> | null = null;\n\n  /**\n   *\n   * @param {string} cacheName\n   *\n   * @private\n   */\n  constructor(cacheName: string) {\n    this._cacheName = cacheName;\n  }\n\n  /**\n   * Performs an upgrade of indexedDB.\n   *\n   * @param {IDBPDatabase<CacheDbSchema>} db\n   *\n   * @private\n   */\n  private _upgradeDb(db: IDBPDatabase<CacheDbSchema>) {\n    // TODO(philipwalton): EdgeHTML doesn't support arrays as a keyPath, so we\n    // have to use the `id` keyPath here and create our own values (a\n    // concatenation of `url + cacheName`) instead of simply using\n    // `keyPath: ['url', 'cacheName']`, which is supported in other browsers.\n    const objStore = db.createObjectStore(CACHE_OBJECT_STORE, {keyPath: 'id'});\n\n    // TODO(philipwalton): once we don't have to support EdgeHTML, we can\n    // create a single index with the keyPath `['cacheName', 'timestamp']`\n    // instead of doing both these indexes.\n    objStore.createIndex('cacheName', 'cacheName', {unique: false});\n    objStore.createIndex('timestamp', 'timestamp', {unique: false});\n  }\n\n  /**\n   * Performs an upgrade of indexedDB and deletes deprecated DBs.\n   *\n   * @param {IDBPDatabase<CacheDbSchema>} db\n   *\n   * @private\n   */\n  private _upgradeDbAndDeleteOldDbs(db: IDBPDatabase<CacheDbSchema>) {\n    this._upgradeDb(db);\n    if (this._cacheName) {\n      void deleteDB(this._cacheName);\n    }\n  }\n\n  /**\n   * @param {string} url\n   * @param {number} timestamp\n   *\n   * @private\n   */\n  async setTimestamp(url: string, timestamp: number): Promise<void> {\n    url = normalizeURL(url);\n\n    const entry: CacheTimestampsModelEntry = {\n      url,\n      timestamp,\n      cacheName: this._cacheName,\n      // Creating an ID from the URL and cache name won't be necessary once\n      // Edge switches to Chromium and all browsers we support work with\n      // array keyPaths.\n      id: this._getId(url),\n    };\n    const db = await this.getDb();\n    const tx = db.transaction(CACHE_OBJECT_STORE, 'readwrite', {\n      durability: 'relaxed',\n    });\n    await tx.store.put(entry);\n    await tx.done;\n  }\n\n  /**\n   * Returns the timestamp stored for a given URL.\n   *\n   * @param {string} url\n   * @return {number | undefined}\n   *\n   * @private\n   */\n  async getTimestamp(url: string): Promise<number | undefined> {\n    const db = await this.getDb();\n    const entry = await db.get(CACHE_OBJECT_STORE, this._getId(url));\n    return entry?.timestamp;\n  }\n\n  /**\n   * Iterates through all the entries in the object store (from newest to\n   * oldest) and removes entries once either `maxCount` is reached or the\n   * entry's timestamp is less than `minTimestamp`.\n   *\n   * @param {number} minTimestamp\n   * @param {number} maxCount\n   * @return {Array<string>}\n   *\n   * @private\n   */\n  async expireEntries(\n    minTimestamp: number,\n    maxCount?: number,\n  ): Promise<string[]> {\n    const db = await this.getDb();\n    let cursor = await db\n      .transaction(CACHE_OBJECT_STORE)\n      .store.index('timestamp')\n      .openCursor(null, 'prev');\n    const entriesToDelete: CacheTimestampsModelEntry[] = [];\n    let entriesNotDeletedCount = 0;\n    while (cursor) {\n      const result = cursor.value;\n      // TODO(philipwalton): once we can use a multi-key index, we\n      // won't have to check `cacheName` here.\n      if (result.cacheName === this._cacheName) {\n        // Delete an entry if it's older than the max age or\n        // if we already have the max number allowed.\n        if (\n          (minTimestamp && result.timestamp < minTimestamp) ||\n          (maxCount && entriesNotDeletedCount >= maxCount)\n        ) {\n          // TODO(philipwalton): we should be able to delete the\n          // entry right here, but doing so causes an iteration\n          // bug in Safari stable (fixed in TP). Instead we can\n          // store the keys of the entries to delete, and then\n          // delete the separate transactions.\n          // https://github.com/GoogleChrome/workbox/issues/1978\n          // cursor.delete();\n\n          // We only need to return the URL, not the whole entry.\n          entriesToDelete.push(cursor.value);\n        } else {\n          entriesNotDeletedCount++;\n        }\n      }\n      cursor = await cursor.continue();\n    }\n\n    // TODO(philipwalton): once the Safari bug in the following issue is fixed,\n    // we should be able to remove this loop and do the entry deletion in the\n    // cursor loop above:\n    // https://github.com/GoogleChrome/workbox/issues/1978\n    const urlsDeleted: string[] = [];\n    for (const entry of entriesToDelete) {\n      await db.delete(CACHE_OBJECT_STORE, entry.id);\n      urlsDeleted.push(entry.url);\n    }\n\n    return urlsDeleted;\n  }\n\n  /**\n   * Takes a URL and returns an ID that will be unique in the object store.\n   *\n   * @param {string} url\n   * @return {string}\n   *\n   * @private\n   */\n  private _getId(url: string): string {\n    // Creating an ID from the URL and cache name won't be necessary once\n    // Edge switches to Chromium and all browsers we support work with\n    // array keyPaths.\n    return this._cacheName + '|' + normalizeURL(url);\n  }\n\n  /**\n   * Returns an open connection to the database.\n   *\n   * @private\n   */\n  private async getDb() {\n    if (!this._db) {\n      this._db = await openDB(DB_NAME, 1, {\n        upgrade: this._upgradeDbAndDeleteOldDbs.bind(this),\n      });\n    }\n    return this._db;\n  }\n}\n\nexport {CacheTimestampsModel};\n"
  },
  {
    "path": "packages/workbox-expiration/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-google-analytics/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-google-analytics\n"
  },
  {
    "path": "packages/workbox-google-analytics/package.json",
    "content": "{\n  \"name\": \"workbox-google-analytics\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"Queues failed requests and uses the Background Sync API to replay them when the network is available\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"offline\",\n    \"google\",\n    \"analytics\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.googleAnalytics\",\n    \"outputFilename\": \"workbox-offline-ga\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-background-sync\": \"7.4.0\",\n    \"workbox-core\": \"7.4.0\",\n    \"workbox-routing\": \"7.4.0\",\n    \"workbox-strategies\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-google-analytics/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:google-analytics:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-google-analytics/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {initialize, GoogleAnalyticsInitializeOptions} from './initialize.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-google-analytics\n */\n\nexport {initialize, GoogleAnalyticsInitializeOptions};\n"
  },
  {
    "path": "packages/workbox-google-analytics/src/initialize.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {BackgroundSyncPlugin} from 'workbox-background-sync/BackgroundSyncPlugin.js';\nimport {Queue} from 'workbox-background-sync/Queue.js';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {RouteMatchCallbackOptions} from 'workbox-core/types.js';\nimport {Route} from 'workbox-routing/Route.js';\nimport {Router} from 'workbox-routing/Router.js';\nimport {NetworkFirst} from 'workbox-strategies/NetworkFirst.js';\nimport {NetworkOnly} from 'workbox-strategies/NetworkOnly.js';\nimport {\n  QUEUE_NAME,\n  MAX_RETENTION_TIME,\n  GOOGLE_ANALYTICS_HOST,\n  GTM_HOST,\n  ANALYTICS_JS_PATH,\n  GTAG_JS_PATH,\n  GTM_JS_PATH,\n  COLLECT_PATHS_REGEX,\n} from './utils/constants.js';\nimport './_version.js';\n\nexport interface GoogleAnalyticsInitializeOptions {\n  cacheName?: string;\n  parameterOverrides?: {[paramName: string]: string};\n  hitFilter?: (params: URLSearchParams) => void;\n}\n\n/**\n * Creates the requestWillDequeue callback to be used with the background\n * sync plugin. The callback takes the failed request and adds the\n * `qt` param based on the current time, as well as applies any other\n * user-defined hit modifications.\n *\n * @param {Object} config See {@link workbox-google-analytics.initialize}.\n * @return {Function} The requestWillDequeue callback function.\n *\n * @private\n */\nconst createOnSyncCallback = (config: GoogleAnalyticsInitializeOptions) => {\n  return async ({queue}: {queue: Queue}) => {\n    let entry;\n    while ((entry = await queue.shiftRequest())) {\n      const {request, timestamp} = entry;\n      const url = new URL(request.url);\n\n      try {\n        // Measurement protocol requests can set their payload parameters in\n        // either the URL query string (for GET requests) or the POST body.\n        const params =\n          request.method === 'POST'\n            ? new URLSearchParams(await request.clone().text())\n            : url.searchParams;\n\n        // Calculate the qt param, accounting for the fact that an existing\n        // qt param may be present and should be updated rather than replaced.\n        const originalHitTime = timestamp! - (Number(params.get('qt')) || 0);\n        const queueTime = Date.now() - originalHitTime;\n\n        // Set the qt param prior to applying hitFilter or parameterOverrides.\n        params.set('qt', String(queueTime));\n\n        // Apply `parameterOverrides`, if set.\n        if (config.parameterOverrides) {\n          for (const param of Object.keys(config.parameterOverrides)) {\n            const value = config.parameterOverrides[param];\n            params.set(param, value);\n          }\n        }\n\n        // Apply `hitFilter`, if set.\n        if (typeof config.hitFilter === 'function') {\n          config.hitFilter.call(null, params);\n        }\n\n        // Retry the fetch. Ignore URL search params from the URL as they're\n        // now in the post body.\n        await fetch(\n          new Request(url.origin + url.pathname, {\n            body: params.toString(),\n            method: 'POST',\n            mode: 'cors',\n            credentials: 'omit',\n            headers: {'Content-Type': 'text/plain'},\n          }),\n        );\n\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `Request for '${getFriendlyURL(url.href)}' ` + `has been replayed`,\n          );\n        }\n      } catch (err) {\n        await queue.unshiftRequest(entry);\n\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `Request for '${getFriendlyURL(url.href)}' ` +\n              `failed to replay, putting it back in the queue.`,\n          );\n        }\n        throw err;\n      }\n    }\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        `All Google Analytics request successfully replayed; ` +\n          `the queue is now empty!`,\n      );\n    }\n  };\n};\n\n/**\n * Creates GET and POST routes to catch failed Measurement Protocol hits.\n *\n * @param {BackgroundSyncPlugin} bgSyncPlugin\n * @return {Array<Route>} The created routes.\n *\n * @private\n */\nconst createCollectRoutes = (bgSyncPlugin: BackgroundSyncPlugin) => {\n  const match = ({url}: RouteMatchCallbackOptions) =>\n    url.hostname === GOOGLE_ANALYTICS_HOST &&\n    COLLECT_PATHS_REGEX.test(url.pathname);\n\n  const handler = new NetworkOnly({\n    plugins: [bgSyncPlugin],\n  });\n\n  return [new Route(match, handler, 'GET'), new Route(match, handler, 'POST')];\n};\n\n/**\n * Creates a route with a network first strategy for the analytics.js script.\n *\n * @param {string} cacheName\n * @return {Route} The created route.\n *\n * @private\n */\nconst createAnalyticsJsRoute = (cacheName: string) => {\n  const match = ({url}: RouteMatchCallbackOptions) =>\n    url.hostname === GOOGLE_ANALYTICS_HOST &&\n    url.pathname === ANALYTICS_JS_PATH;\n\n  const handler = new NetworkFirst({cacheName});\n\n  return new Route(match, handler, 'GET');\n};\n\n/**\n * Creates a route with a network first strategy for the gtag.js script.\n *\n * @param {string} cacheName\n * @return {Route} The created route.\n *\n * @private\n */\nconst createGtagJsRoute = (cacheName: string) => {\n  const match = ({url}: RouteMatchCallbackOptions) =>\n    url.hostname === GTM_HOST && url.pathname === GTAG_JS_PATH;\n\n  const handler = new NetworkFirst({cacheName});\n\n  return new Route(match, handler, 'GET');\n};\n\n/**\n * Creates a route with a network first strategy for the gtm.js script.\n *\n * @param {string} cacheName\n * @return {Route} The created route.\n *\n * @private\n */\nconst createGtmJsRoute = (cacheName: string) => {\n  const match = ({url}: RouteMatchCallbackOptions) =>\n    url.hostname === GTM_HOST && url.pathname === GTM_JS_PATH;\n\n  const handler = new NetworkFirst({cacheName});\n\n  return new Route(match, handler, 'GET');\n};\n\n/**\n * @param {Object=} [options]\n * @param {Object} [options.cacheName] The cache name to store and retrieve\n *     analytics.js. Defaults to the cache names provided by `workbox-core`.\n * @param {Object} [options.parameterOverrides]\n *     [Measurement Protocol parameters](https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters),\n *     expressed as key/value pairs, to be added to replayed Google Analytics\n *     requests. This can be used to, e.g., set a custom dimension indicating\n *     that the request was replayed.\n * @param {Function} [options.hitFilter] A function that allows you to modify\n *     the hit parameters prior to replaying\n *     the hit. The function is invoked with the original hit's URLSearchParams\n *     object as its only argument.\n *\n * @memberof workbox-google-analytics\n */\nconst initialize = (options: GoogleAnalyticsInitializeOptions = {}): void => {\n  const cacheName = cacheNames.getGoogleAnalyticsName(options.cacheName);\n\n  const bgSyncPlugin = new BackgroundSyncPlugin(QUEUE_NAME, {\n    maxRetentionTime: MAX_RETENTION_TIME,\n    onSync: createOnSyncCallback(options),\n  });\n\n  const routes = [\n    createGtmJsRoute(cacheName),\n    createAnalyticsJsRoute(cacheName),\n    createGtagJsRoute(cacheName),\n    ...createCollectRoutes(bgSyncPlugin),\n  ];\n\n  const router = new Router();\n  for (const route of routes) {\n    router.registerRoute(route);\n  }\n\n  router.addFetchListener();\n};\n\nexport {initialize};\n"
  },
  {
    "path": "packages/workbox-google-analytics/src/utils/constants.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nexport const QUEUE_NAME = 'workbox-google-analytics';\nexport const MAX_RETENTION_TIME = 60 * 48; // Two days in minutes\nexport const GOOGLE_ANALYTICS_HOST = 'www.google-analytics.com';\nexport const GTM_HOST = 'www.googletagmanager.com';\nexport const ANALYTICS_JS_PATH = '/analytics.js';\nexport const GTAG_JS_PATH = '/gtag/js';\nexport const GTM_JS_PATH = '/gtm.js';\nexport const COLLECT_DEFAULT_PATH = '/collect';\n\n// This RegExp matches all known Measurement Protocol single-hit collect\n// endpoints. Most of the time the default path (/collect) is used, but\n// occasionally an experimental endpoint is used when testing new features,\n// (e.g. /r/collect or /j/collect)\nexport const COLLECT_PATHS_REGEX = /^\\/(\\w+\\/)?collect/;\n"
  },
  {
    "path": "packages/workbox-google-analytics/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [\n    {\"path\": \"../workbox-background-sync/\"},\n    {\"path\": \"../workbox-core/\"},\n    {\"path\": \"../workbox-routing/\"},\n    {\"path\": \"../workbox-strategies/\"}\n  ]\n}\n"
  },
  {
    "path": "packages/workbox-navigation-preload/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-navigation-preload\n"
  },
  {
    "path": "packages/workbox-navigation-preload/package.json",
    "content": "{\n  \"name\": \"workbox-navigation-preload\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This library allows developers to opt-in to using Navigation Preload in their service worker.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"navigation\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.navigationPreload\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-navigation-preload/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:navigation-preload:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-navigation-preload/src/disable.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {isSupported} from './isSupported.js';\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * If the browser supports Navigation Preload, then this will disable it.\n *\n * @memberof workbox-navigation-preload\n */\nfunction disable(): void {\n  if (isSupported()) {\n    self.addEventListener('activate', (event: ExtendableEvent) => {\n      event.waitUntil(\n        self.registration.navigationPreload.disable().then(() => {\n          if (process.env.NODE_ENV !== 'production') {\n            logger.log(`Navigation preload is disabled.`);\n          }\n        }),\n      );\n    });\n  } else {\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(`Navigation preload is not supported in this browser.`);\n    }\n  }\n}\n\nexport {disable};\n"
  },
  {
    "path": "packages/workbox-navigation-preload/src/enable.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {isSupported} from './isSupported.js';\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * If the browser supports Navigation Preload, then this will enable it.\n *\n * @param {string} [headerValue] Optionally, allows developers to\n * [override](https://developers.google.com/web/updates/2017/02/navigation-preload#changing_the_header)\n * the value of the `Service-Worker-Navigation-Preload` header which will be\n * sent to the server when making the navigation request.\n *\n * @memberof workbox-navigation-preload\n */\nfunction enable(headerValue?: string): void {\n  if (isSupported()) {\n    self.addEventListener('activate', (event: ExtendableEvent) => {\n      event.waitUntil(\n        self.registration.navigationPreload.enable().then(() => {\n          // Defaults to Service-Worker-Navigation-Preload: true if not set.\n          if (headerValue) {\n            void self.registration.navigationPreload.setHeaderValue(\n              headerValue,\n            );\n          }\n\n          if (process.env.NODE_ENV !== 'production') {\n            logger.log(`Navigation preload is enabled.`);\n          }\n        }),\n      );\n    });\n  } else {\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(`Navigation preload is not supported in this browser.`);\n    }\n  }\n}\n\nexport {enable};\n"
  },
  {
    "path": "packages/workbox-navigation-preload/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {disable} from './disable.js';\nimport {enable} from './enable.js';\nimport {isSupported} from './isSupported.js';\nimport './_version.js';\n\n// See https://github.com/GoogleChrome/workbox/issues/2946\ninterface NavigationPreloadState {\n  enabled?: boolean;\n  headerValue?: string;\n}\n\ninterface NavigationPreloadManager {\n  disable(): Promise<void>;\n  enable(): Promise<void>;\n  getState(): Promise<NavigationPreloadState>;\n  setHeaderValue(value: string): Promise<void>;\n}\n\ndeclare global {\n  interface ServiceWorkerRegistration {\n    readonly navigationPreload: NavigationPreloadManager;\n  }\n}\n\n/**\n * @module workbox-navigation-preload\n */\n\nexport {disable, enable, isSupported};\n"
  },
  {
    "path": "packages/workbox-navigation-preload/src/isSupported.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * @return {boolean} Whether or not the current browser supports enabling\n * navigation preload.\n *\n * @memberof workbox-navigation-preload\n */\nfunction isSupported(): boolean {\n  return Boolean(self.registration && self.registration.navigationPreload);\n}\n\nexport {isSupported};\n"
  },
  {
    "path": "packages/workbox-navigation-preload/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-precaching/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-precaching\n"
  },
  {
    "path": "packages/workbox-precaching/package.json",
    "content": "{\n  \"name\": \"workbox-precaching\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This module efficiently precaches assets.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.precaching\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\",\n    \"workbox-routing\": \"7.4.0\",\n    \"workbox-strategies\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-precaching/src/PrecacheController.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {waitUntil} from 'workbox-core/_private/waitUntil.js';\nimport {Strategy} from 'workbox-strategies/Strategy.js';\nimport {RouteHandlerCallback, WorkboxPlugin} from 'workbox-core/types.js';\n\nimport {createCacheKey} from './utils/createCacheKey.js';\nimport {PrecacheInstallReportPlugin} from './utils/PrecacheInstallReportPlugin.js';\nimport {PrecacheCacheKeyPlugin} from './utils/PrecacheCacheKeyPlugin.js';\nimport {printCleanupDetails} from './utils/printCleanupDetails.js';\nimport {printInstallDetails} from './utils/printInstallDetails.js';\nimport {PrecacheStrategy} from './PrecacheStrategy.js';\nimport {PrecacheEntry, InstallResult, CleanupResult} from './_types.js';\nimport './_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\ndeclare global {\n  interface ServiceWorkerGlobalScope {\n    __WB_MANIFEST: Array<PrecacheEntry | string>;\n  }\n}\n\ninterface PrecacheControllerOptions {\n  cacheName?: string;\n  plugins?: WorkboxPlugin[];\n  fallbackToNetwork?: boolean;\n}\n\n/**\n * Performs efficient precaching of assets.\n *\n * @memberof workbox-precaching\n */\nclass PrecacheController {\n  private _installAndActiveListenersAdded?: boolean;\n  private readonly _strategy: Strategy;\n  private readonly _urlsToCacheKeys: Map<string, string> = new Map();\n  private readonly _urlsToCacheModes: Map<\n    string,\n    | 'reload'\n    | 'default'\n    | 'no-store'\n    | 'no-cache'\n    | 'force-cache'\n    | 'only-if-cached'\n  > = new Map();\n  private readonly _cacheKeysToIntegrities: Map<string, string> = new Map();\n\n  /**\n   * Create a new PrecacheController.\n   *\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] The cache to use for precaching.\n   * @param {string} [options.plugins] Plugins to use when precaching as well\n   * as responding to fetch events for precached assets.\n   * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n   * get the response from the network if there's a precache miss.\n   */\n  constructor({\n    cacheName,\n    plugins = [],\n    fallbackToNetwork = true,\n  }: PrecacheControllerOptions = {}) {\n    this._strategy = new PrecacheStrategy({\n      cacheName: cacheNames.getPrecacheName(cacheName),\n      plugins: [\n        ...plugins,\n        new PrecacheCacheKeyPlugin({precacheController: this}),\n      ],\n      fallbackToNetwork,\n    });\n\n    // Bind the install and activate methods to the instance.\n    this.install = this.install.bind(this);\n    this.activate = this.activate.bind(this);\n  }\n\n  /**\n   * @type {workbox-precaching.PrecacheStrategy} The strategy created by this controller and\n   * used to cache assets and respond to fetch events.\n   */\n  get strategy(): Strategy {\n    return this._strategy;\n  }\n\n  /**\n   * Adds items to the precache list, removing any duplicates and\n   * stores the files in the\n   * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n   * worker installs.\n   *\n   * This method can be called multiple times.\n   *\n   * @param {Array<Object|string>} [entries=[]] Array of entries to precache.\n   */\n  precache(entries: Array<PrecacheEntry | string>): void {\n    this.addToCacheList(entries);\n\n    if (!this._installAndActiveListenersAdded) {\n      self.addEventListener('install', this.install);\n      self.addEventListener('activate', this.activate);\n      this._installAndActiveListenersAdded = true;\n    }\n  }\n\n  /**\n   * This method will add items to the precache list, removing duplicates\n   * and ensuring the information is valid.\n   *\n   * @param {Array<workbox-precaching.PrecacheController.PrecacheEntry|string>} entries\n   *     Array of entries to precache.\n   */\n  addToCacheList(entries: Array<PrecacheEntry | string>): void {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isArray(entries, {\n        moduleName: 'workbox-precaching',\n        className: 'PrecacheController',\n        funcName: 'addToCacheList',\n        paramName: 'entries',\n      });\n    }\n\n    const urlsToWarnAbout: string[] = [];\n    for (const entry of entries) {\n      // See https://github.com/GoogleChrome/workbox/issues/2259\n      if (typeof entry === 'string') {\n        urlsToWarnAbout.push(entry);\n      } else if (entry && entry.revision === undefined) {\n        urlsToWarnAbout.push(entry.url);\n      }\n\n      const {cacheKey, url} = createCacheKey(entry);\n      const cacheMode =\n        typeof entry !== 'string' && entry.revision ? 'reload' : 'default';\n\n      if (\n        this._urlsToCacheKeys.has(url) &&\n        this._urlsToCacheKeys.get(url) !== cacheKey\n      ) {\n        throw new WorkboxError('add-to-cache-list-conflicting-entries', {\n          firstEntry: this._urlsToCacheKeys.get(url),\n          secondEntry: cacheKey,\n        });\n      }\n\n      if (typeof entry !== 'string' && entry.integrity) {\n        if (\n          this._cacheKeysToIntegrities.has(cacheKey) &&\n          this._cacheKeysToIntegrities.get(cacheKey) !== entry.integrity\n        ) {\n          throw new WorkboxError('add-to-cache-list-conflicting-integrities', {\n            url,\n          });\n        }\n        this._cacheKeysToIntegrities.set(cacheKey, entry.integrity);\n      }\n\n      this._urlsToCacheKeys.set(url, cacheKey);\n      this._urlsToCacheModes.set(url, cacheMode);\n\n      if (urlsToWarnAbout.length > 0) {\n        const warningMessage =\n          `Workbox is precaching URLs without revision ` +\n          `info: ${urlsToWarnAbout.join(', ')}\\nThis is generally NOT safe. ` +\n          `Learn more at https://bit.ly/wb-precache`;\n        if (process.env.NODE_ENV === 'production') {\n          // Use console directly to display this warning without bloating\n          // bundle sizes by pulling in all of the logger codebase in prod.\n          console.warn(warningMessage);\n        } else {\n          logger.warn(warningMessage);\n        }\n      }\n    }\n  }\n\n  /**\n   * Precaches new and updated assets. Call this method from the service worker\n   * install event.\n   *\n   * Note: this method calls `event.waitUntil()` for you, so you do not need\n   * to call it yourself in your event handlers.\n   *\n   * @param {ExtendableEvent} event\n   * @return {Promise<workbox-precaching.InstallResult>}\n   */\n  install(event: ExtendableEvent): Promise<InstallResult> {\n    // waitUntil returns Promise<any>\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n    return waitUntil(event, async () => {\n      const installReportPlugin = new PrecacheInstallReportPlugin();\n      this.strategy.plugins.push(installReportPlugin);\n\n      // Cache entries one at a time.\n      // See https://github.com/GoogleChrome/workbox/issues/2528\n      for (const [url, cacheKey] of this._urlsToCacheKeys) {\n        const integrity = this._cacheKeysToIntegrities.get(cacheKey);\n        const cacheMode = this._urlsToCacheModes.get(url);\n\n        const request = new Request(url, {\n          integrity,\n          cache: cacheMode,\n          credentials: 'same-origin',\n        });\n\n        await Promise.all(\n          this.strategy.handleAll({\n            params: {cacheKey},\n            request,\n            event,\n          }),\n        );\n      }\n\n      const {updatedURLs, notUpdatedURLs} = installReportPlugin;\n\n      if (process.env.NODE_ENV !== 'production') {\n        printInstallDetails(updatedURLs, notUpdatedURLs);\n      }\n\n      return {updatedURLs, notUpdatedURLs};\n    });\n  }\n\n  /**\n   * Deletes assets that are no longer present in the current precache manifest.\n   * Call this method from the service worker activate event.\n   *\n   * Note: this method calls `event.waitUntil()` for you, so you do not need\n   * to call it yourself in your event handlers.\n   *\n   * @param {ExtendableEvent} event\n   * @return {Promise<workbox-precaching.CleanupResult>}\n   */\n  activate(event: ExtendableEvent): Promise<CleanupResult> {\n    // waitUntil returns Promise<any>\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-return\n    return waitUntil(event, async () => {\n      const cache = await self.caches.open(this.strategy.cacheName);\n      const currentlyCachedRequests = await cache.keys();\n      const expectedCacheKeys = new Set(this._urlsToCacheKeys.values());\n\n      const deletedURLs = [];\n      for (const request of currentlyCachedRequests) {\n        if (!expectedCacheKeys.has(request.url)) {\n          await cache.delete(request);\n          deletedURLs.push(request.url);\n        }\n      }\n\n      if (process.env.NODE_ENV !== 'production') {\n        printCleanupDetails(deletedURLs);\n      }\n\n      return {deletedURLs};\n    });\n  }\n\n  /**\n   * Returns a mapping of a precached URL to the corresponding cache key, taking\n   * into account the revision information for the URL.\n   *\n   * @return {Map<string, string>} A URL to cache key mapping.\n   */\n  getURLsToCacheKeys(): Map<string, string> {\n    return this._urlsToCacheKeys;\n  }\n\n  /**\n   * Returns a list of all the URLs that have been precached by the current\n   * service worker.\n   *\n   * @return {Array<string>} The precached URLs.\n   */\n  getCachedURLs(): Array<string> {\n    return [...this._urlsToCacheKeys.keys()];\n  }\n\n  /**\n   * Returns the cache key used for storing a given URL. If that URL is\n   * unversioned, like `/index.html', then the cache key will be the original\n   * URL with a search parameter appended to it.\n   *\n   * @param {string} url A URL whose cache key you want to look up.\n   * @return {string} The versioned URL that corresponds to a cache key\n   * for the original URL, or undefined if that URL isn't precached.\n   */\n  getCacheKeyForURL(url: string): string | undefined {\n    const urlObject = new URL(url, location.href);\n    return this._urlsToCacheKeys.get(urlObject.href);\n  }\n\n  /**\n   * @param {string} url A cache key whose SRI you want to look up.\n   * @return {string} The subresource integrity associated with the cache key,\n   * or undefined if it's not set.\n   */\n  getIntegrityForCacheKey(cacheKey: string): string | undefined {\n    return this._cacheKeysToIntegrities.get(cacheKey);\n  }\n\n  /**\n   * This acts as a drop-in replacement for\n   * [`cache.match()`](https://developer.mozilla.org/en-US/docs/Web/API/Cache/match)\n   * with the following differences:\n   *\n   * - It knows what the name of the precache is, and only checks in that cache.\n   * - It allows you to pass in an \"original\" URL without versioning parameters,\n   * and it will automatically look up the correct cache key for the currently\n   * active revision of that URL.\n   *\n   * E.g., `matchPrecache('index.html')` will find the correct precached\n   * response for the currently active service worker, even if the actual cache\n   * key is `'/index.html?__WB_REVISION__=1234abcd'`.\n   *\n   * @param {string|Request} request The key (without revisioning parameters)\n   * to look up in the precache.\n   * @return {Promise<Response|undefined>}\n   */\n  async matchPrecache(\n    request: string | Request,\n  ): Promise<Response | undefined> {\n    const url = request instanceof Request ? request.url : request;\n    const cacheKey = this.getCacheKeyForURL(url);\n    if (cacheKey) {\n      const cache = await self.caches.open(this.strategy.cacheName);\n      return cache.match(cacheKey);\n    }\n    return undefined;\n  }\n\n  /**\n   * Returns a function that looks up `url` in the precache (taking into\n   * account revision information), and returns the corresponding `Response`.\n   *\n   * @param {string} url The precached URL which will be used to lookup the\n   * `Response`.\n   * @return {workbox-routing~handlerCallback}\n   */\n  createHandlerBoundToURL(url: string): RouteHandlerCallback {\n    const cacheKey = this.getCacheKeyForURL(url);\n    if (!cacheKey) {\n      throw new WorkboxError('non-precached-url', {url});\n    }\n    return (options) => {\n      options.request = new Request(url);\n      options.params = {cacheKey, ...options.params};\n\n      return this.strategy.handle(options);\n    };\n  }\n}\n\nexport {PrecacheController};\n"
  },
  {
    "path": "packages/workbox-precaching/src/PrecacheFallbackPlugin.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport {PrecacheController} from './PrecacheController.js';\n\nimport './_version.js';\n\n/**\n * `PrecacheFallbackPlugin` allows you to specify an \"offline fallback\"\n * response to be used when a given strategy is unable to generate a response.\n *\n * It does this by intercepting the `handlerDidError` plugin callback\n * and returning a precached response, taking the expected revision parameter\n * into account automatically.\n *\n * Unless you explicitly pass in a `PrecacheController` instance to the\n * constructor, the default instance will be used. Generally speaking, most\n * developers will end up using the default.\n *\n * @memberof workbox-precaching\n */\nclass PrecacheFallbackPlugin implements WorkboxPlugin {\n  private readonly _fallbackURL: string;\n  private readonly _precacheController: PrecacheController;\n\n  /**\n   * Constructs a new PrecacheFallbackPlugin with the associated fallbackURL.\n   *\n   * @param {Object} config\n   * @param {string} config.fallbackURL A precached URL to use as the fallback\n   *     if the associated strategy can't generate a response.\n   * @param {PrecacheController} [config.precacheController] An optional\n   *     PrecacheController instance. If not provided, the default\n   *     PrecacheController will be used.\n   */\n  constructor({\n    fallbackURL,\n    precacheController,\n  }: {\n    fallbackURL: string;\n    precacheController?: PrecacheController;\n  }) {\n    this._fallbackURL = fallbackURL;\n    this._precacheController =\n      precacheController || getOrCreatePrecacheController();\n  }\n\n  /**\n   * @return {Promise<Response>} The precache response for the fallback URL.\n   *\n   * @private\n   */\n  handlerDidError: WorkboxPlugin['handlerDidError'] = () =>\n    this._precacheController.matchPrecache(this._fallbackURL);\n}\n\nexport {PrecacheFallbackPlugin};\n"
  },
  {
    "path": "packages/workbox-precaching/src/PrecacheRoute.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {\n  RouteMatchCallback,\n  RouteMatchCallbackOptions,\n} from 'workbox-core/types.js';\nimport {Route} from 'workbox-routing/Route.js';\n\nimport {PrecacheRouteOptions} from './_types.js';\nimport {PrecacheController} from './PrecacheController.js';\nimport {generateURLVariations} from './utils/generateURLVariations.js';\n\nimport './_version.js';\n\n/**\n * A subclass of {@link workbox-routing.Route} that takes a\n * {@link workbox-precaching.PrecacheController}\n * instance and uses it to match incoming requests and handle fetching\n * responses from the precache.\n *\n * @memberof workbox-precaching\n * @extends workbox-routing.Route\n */\nclass PrecacheRoute extends Route {\n  /**\n   * @param {PrecacheController} precacheController A `PrecacheController`\n   * instance used to both match requests and respond to fetch events.\n   * @param {Object} [options] Options to control how requests are matched\n   * against the list of precached URLs.\n   * @param {string} [options.directoryIndex=index.html] The `directoryIndex` will\n   * check cache entries for a URLs ending with '/' to see if there is a hit when\n   * appending the `directoryIndex` value.\n   * @param {Array<RegExp>} [options.ignoreURLParametersMatching=[/^utm_/, /^fbclid$/]] An\n   * array of regex's to remove search params when looking for a cache match.\n   * @param {boolean} [options.cleanURLs=true] The `cleanURLs` option will\n   * check the cache for the URL with a `.html` added to the end of the end.\n   * @param {workbox-precaching~urlManipulation} [options.urlManipulation]\n   * This is a function that should take a URL and return an array of\n   * alternative URLs that should be checked for precache matches.\n   */\n  constructor(\n    precacheController: PrecacheController,\n    options?: PrecacheRouteOptions,\n  ) {\n    const match: RouteMatchCallback = ({\n      request,\n    }: RouteMatchCallbackOptions) => {\n      const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n      for (const possibleURL of generateURLVariations(request.url, options)) {\n        const cacheKey = urlsToCacheKeys.get(possibleURL);\n        if (cacheKey) {\n          const integrity =\n            precacheController.getIntegrityForCacheKey(cacheKey);\n          return {cacheKey, integrity};\n        }\n      }\n      if (process.env.NODE_ENV !== 'production') {\n        logger.debug(\n          `Precaching did not find a match for ` + getFriendlyURL(request.url),\n        );\n      }\n      return;\n    };\n\n    super(match, precacheController.strategy);\n  }\n}\n\nexport {PrecacheRoute};\n"
  },
  {
    "path": "packages/workbox-precaching/src/PrecacheStrategy.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {copyResponse} from 'workbox-core/copyResponse.js';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport {Strategy, StrategyOptions} from 'workbox-strategies/Strategy.js';\nimport {StrategyHandler} from 'workbox-strategies/StrategyHandler.js';\n\nimport './_version.js';\n\ninterface PrecacheStrategyOptions extends StrategyOptions {\n  fallbackToNetwork?: boolean;\n}\n\n/**\n * A {@link workbox-strategies.Strategy} implementation\n * specifically designed to work with\n * {@link workbox-precaching.PrecacheController}\n * to both cache and fetch precached assets.\n *\n * Note: an instance of this class is created automatically when creating a\n * `PrecacheController`; it's generally not necessary to create this yourself.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-precaching\n */\nclass PrecacheStrategy extends Strategy {\n  private readonly _fallbackToNetwork: boolean;\n\n  static readonly defaultPrecacheCacheabilityPlugin: WorkboxPlugin = {\n    async cacheWillUpdate({response}) {\n      if (!response || response.status >= 400) {\n        return null;\n      }\n\n      return response;\n    },\n  };\n\n  static readonly copyRedirectedCacheableResponsesPlugin: WorkboxPlugin = {\n    async cacheWillUpdate({response}) {\n      return response.redirected ? await copyResponse(response) : response;\n    },\n  };\n\n  /**\n   *\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] Cache name to store and retrieve\n   * requests. Defaults to the cache names provided by\n   * {@link workbox-core.cacheNames}.\n   * @param {Array<Object>} [options.plugins] {@link https://developers.google.com/web/tools/workbox/guides/using-plugins|Plugins}\n   * to use in conjunction with this caching strategy.\n   * @param {Object} [options.fetchOptions] Values passed along to the\n   * {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters|init}\n   * of all fetch() requests made by this strategy.\n   * @param {Object} [options.matchOptions] The\n   * {@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions|CacheQueryOptions}\n   * for any `cache.match()` or `cache.put()` calls made by this strategy.\n   * @param {boolean} [options.fallbackToNetwork=true] Whether to attempt to\n   * get the response from the network if there's a precache miss.\n   */\n  constructor(options: PrecacheStrategyOptions = {}) {\n    options.cacheName = cacheNames.getPrecacheName(options.cacheName);\n    super(options);\n\n    this._fallbackToNetwork =\n      options.fallbackToNetwork === false ? false : true;\n\n    // Redirected responses cannot be used to satisfy a navigation request, so\n    // any redirected response must be \"copied\" rather than cloned, so the new\n    // response doesn't contain the `redirected` flag. See:\n    // https://bugs.chromium.org/p/chromium/issues/detail?id=669363&desc=2#c1\n    this.plugins.push(PrecacheStrategy.copyRedirectedCacheableResponsesPlugin);\n  }\n\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    const response = await handler.cacheMatch(request);\n    if (response) {\n      return response;\n    }\n\n    // If this is an `install` event for an entry that isn't already cached,\n    // then populate the cache.\n    if (handler.event && handler.event.type === 'install') {\n      return await this._handleInstall(request, handler);\n    }\n\n    // Getting here means something went wrong. An entry that should have been\n    // precached wasn't found in the cache.\n    return await this._handleFetch(request, handler);\n  }\n\n  async _handleFetch(\n    request: Request,\n    handler: StrategyHandler,\n  ): Promise<Response> {\n    let response;\n    const params = (handler.params || {}) as {\n      cacheKey?: string;\n      integrity?: string;\n    };\n\n    // Fall back to the network if we're configured to do so.\n    if (this._fallbackToNetwork) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.warn(\n          `The precached response for ` +\n            `${getFriendlyURL(request.url)} in ${this.cacheName} was not ` +\n            `found. Falling back to the network.`,\n        );\n      }\n\n      const integrityInManifest = params.integrity;\n      const integrityInRequest = request.integrity;\n      const noIntegrityConflict =\n        !integrityInRequest || integrityInRequest === integrityInManifest;\n\n      // Do not add integrity if the original request is no-cors\n      // See https://github.com/GoogleChrome/workbox/issues/3096\n      response = await handler.fetch(\n        new Request(request, {\n          integrity:\n            request.mode !== 'no-cors'\n              ? integrityInRequest || integrityInManifest\n              : undefined,\n        }),\n      );\n\n      // It's only \"safe\" to repair the cache if we're using SRI to guarantee\n      // that the response matches the precache manifest's expectations,\n      // and there's either a) no integrity property in the incoming request\n      // or b) there is an integrity, and it matches the precache manifest.\n      // See https://github.com/GoogleChrome/workbox/issues/2858\n      // Also if the original request users no-cors we don't use integrity.\n      // See https://github.com/GoogleChrome/workbox/issues/3096\n      if (\n        integrityInManifest &&\n        noIntegrityConflict &&\n        request.mode !== 'no-cors'\n      ) {\n        this._useDefaultCacheabilityPluginIfNeeded();\n        const wasCached = await handler.cachePut(request, response.clone());\n        if (process.env.NODE_ENV !== 'production') {\n          if (wasCached) {\n            logger.log(\n              `A response for ${getFriendlyURL(request.url)} ` +\n                `was used to \"repair\" the precache.`,\n            );\n          }\n        }\n      }\n    } else {\n      // This shouldn't normally happen, but there are edge cases:\n      // https://github.com/GoogleChrome/workbox/issues/1441\n      throw new WorkboxError('missing-precache-entry', {\n        cacheName: this.cacheName,\n        url: request.url,\n      });\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      const cacheKey =\n        params.cacheKey || (await handler.getCacheKey(request, 'read'));\n\n      // Workbox is going to handle the route.\n      // print the routing details to the console.\n      logger.groupCollapsed(\n        `Precaching is responding to: ` + getFriendlyURL(request.url),\n      );\n      logger.log(\n        `Serving the precached url: ${getFriendlyURL(\n          cacheKey instanceof Request ? cacheKey.url : cacheKey,\n        )}`,\n      );\n\n      logger.groupCollapsed(`View request details here.`);\n      logger.log(request);\n      logger.groupEnd();\n\n      logger.groupCollapsed(`View response details here.`);\n      logger.log(response);\n      logger.groupEnd();\n\n      logger.groupEnd();\n    }\n\n    return response;\n  }\n\n  async _handleInstall(\n    request: Request,\n    handler: StrategyHandler,\n  ): Promise<Response> {\n    this._useDefaultCacheabilityPluginIfNeeded();\n\n    const response = await handler.fetch(request);\n\n    // Make sure we defer cachePut() until after we know the response\n    // should be cached; see https://github.com/GoogleChrome/workbox/issues/2737\n    const wasCached = await handler.cachePut(request, response.clone());\n    if (!wasCached) {\n      // Throwing here will lead to the `install` handler failing, which\n      // we want to do if *any* of the responses aren't safe to cache.\n      throw new WorkboxError('bad-precaching-response', {\n        url: request.url,\n        status: response.status,\n      });\n    }\n\n    return response;\n  }\n\n  /**\n   * This method is complex, as there a number of things to account for:\n   *\n   * The `plugins` array can be set at construction, and/or it might be added to\n   * to at any time before the strategy is used.\n   *\n   * At the time the strategy is used (i.e. during an `install` event), there\n   * needs to be at least one plugin that implements `cacheWillUpdate` in the\n   * array, other than `copyRedirectedCacheableResponsesPlugin`.\n   *\n   * - If this method is called and there are no suitable `cacheWillUpdate`\n   * plugins, we need to add `defaultPrecacheCacheabilityPlugin`.\n   *\n   * - If this method is called and there is exactly one `cacheWillUpdate`, then\n   * we don't have to do anything (this might be a previously added\n   * `defaultPrecacheCacheabilityPlugin`, or it might be a custom plugin).\n   *\n   * - If this method is called and there is more than one `cacheWillUpdate`,\n   * then we need to check if one is `defaultPrecacheCacheabilityPlugin`. If so,\n   * we need to remove it. (This situation is unlikely, but it could happen if\n   * the strategy is used multiple times, the first without a `cacheWillUpdate`,\n   * and then later on after manually adding a custom `cacheWillUpdate`.)\n   *\n   * See https://github.com/GoogleChrome/workbox/issues/2737 for more context.\n   *\n   * @private\n   */\n  _useDefaultCacheabilityPluginIfNeeded(): void {\n    let defaultPluginIndex: number | null = null;\n    let cacheWillUpdatePluginCount = 0;\n\n    for (const [index, plugin] of this.plugins.entries()) {\n      // Ignore the copy redirected plugin when determining what to do.\n      if (plugin === PrecacheStrategy.copyRedirectedCacheableResponsesPlugin) {\n        continue;\n      }\n\n      // Save the default plugin's index, in case it needs to be removed.\n      if (plugin === PrecacheStrategy.defaultPrecacheCacheabilityPlugin) {\n        defaultPluginIndex = index;\n      }\n\n      if (plugin.cacheWillUpdate) {\n        cacheWillUpdatePluginCount++;\n      }\n    }\n\n    if (cacheWillUpdatePluginCount === 0) {\n      this.plugins.push(PrecacheStrategy.defaultPrecacheCacheabilityPlugin);\n    } else if (cacheWillUpdatePluginCount > 1 && defaultPluginIndex !== null) {\n      // Only remove the default plugin; multiple custom plugins are allowed.\n      this.plugins.splice(defaultPluginIndex, 1);\n    }\n    // Nothing needs to be done if cacheWillUpdatePluginCount is 1\n  }\n}\n\nexport {PrecacheStrategy};\n"
  },
  {
    "path": "packages/workbox-precaching/src/_types.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\nexport interface InstallResult {\n  updatedURLs: string[];\n  notUpdatedURLs: string[];\n}\n\nexport interface CleanupResult {\n  deletedCacheRequests: string[];\n}\n\nexport declare interface PrecacheEntry {\n  integrity?: string;\n  url: string;\n  revision?: string | null;\n}\n\nexport interface PrecacheRouteOptions {\n  directoryIndex?: string;\n  ignoreURLParametersMatching?: RegExp[];\n  cleanURLs?: boolean;\n  urlManipulation?: urlManipulation;\n}\n\nexport type urlManipulation = ({url}: {url: URL}) => URL[];\n\n// * * * IMPORTANT! * * *\n// ------------------------------------------------------------------------- //\n// jdsoc type definitions cannot be declared above TypeScript definitions or\n// they'll be stripped from the built `.js` files, and they'll only be in the\n// `d.ts` files, which aren't read by the jsdoc generator. As a result we\n// have to put declare them below.\n\n/**\n * @typedef {Object} InstallResult\n * @property {Array<string>} updatedURLs List of URLs that were updated during\n * installation.\n * @property {Array<string>} notUpdatedURLs List of URLs that were already up to\n * date.\n *\n * @memberof workbox-precaching\n */\n\n/**\n * @typedef {Object} CleanupResult\n * @property {Array<string>} deletedCacheRequests List of URLs that were deleted\n * while cleaning up the cache.\n *\n * @memberof workbox-precaching\n */\n\n/**\n * @typedef {Object} PrecacheEntry\n * @property {string} url URL to precache.\n * @property {string} [revision] Revision information for the URL.\n * @property {string} [integrity] Integrity metadata that will be used when\n * making the network request for the URL.\n *\n * @memberof workbox-precaching\n */\n\n/**\n * The \"urlManipulation\" callback can be used to determine if there are any\n * additional permutations of a URL that should be used to check against\n * the available precached files.\n *\n * For example, Workbox supports checking for '/index.html' when the URL\n * '/' is provided. This callback allows additional, custom checks.\n *\n * @callback ~urlManipulation\n * @param {Object} context\n * @param {URL} context.url The request's URL.\n * @return {Array<URL>} To add additional urls to test, return an Array of\n * URLs. Please note that these **should not be strings**, but URL objects.\n *\n * @memberof workbox-precaching\n */\n"
  },
  {
    "path": "packages/workbox-precaching/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:precaching:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-precaching/src/addPlugins.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport './_version.js';\n\n/**\n * Adds plugins to the precaching strategy.\n *\n * @param {Array<Object>} plugins\n *\n * @memberof workbox-precaching\n */\nfunction addPlugins(plugins: WorkboxPlugin[]): void {\n  const precacheController = getOrCreatePrecacheController();\n  precacheController.strategy.plugins.push(...plugins);\n}\n\nexport {addPlugins};\n"
  },
  {
    "path": "packages/workbox-precaching/src/addRoute.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {registerRoute} from 'workbox-routing/registerRoute.js';\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport {PrecacheRoute} from './PrecacheRoute.js';\nimport {PrecacheRouteOptions} from './_types.js';\n\nimport './_version.js';\n\n/**\n * Add a `fetch` listener to the service worker that will\n * respond to\n * [network requests]{@link https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers#Custom_responses_to_requests}\n * with precached assets.\n *\n * Requests for assets that aren't precached, the `FetchEvent` will not be\n * responded to, allowing the event to fall through to other `fetch` event\n * listeners.\n *\n * @param {Object} [options] See the {@link workbox-precaching.PrecacheRoute}\n * options.\n *\n * @memberof workbox-precaching\n */\nfunction addRoute(options?: PrecacheRouteOptions): void {\n  const precacheController = getOrCreatePrecacheController();\n\n  const precacheRoute = new PrecacheRoute(precacheController, options);\n  registerRoute(precacheRoute);\n}\n\nexport {addRoute};\n"
  },
  {
    "path": "packages/workbox-precaching/src/cleanupOutdatedCaches.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {deleteOutdatedCaches} from './utils/deleteOutdatedCaches.js';\nimport './_version.js';\n\n/**\n * Adds an `activate` event listener which will clean up incompatible\n * precaches that were created by older versions of Workbox.\n *\n * @memberof workbox-precaching\n */\nfunction cleanupOutdatedCaches(): void {\n  // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n  self.addEventListener('activate', ((event: ExtendableEvent) => {\n    const cacheName = cacheNames.getPrecacheName();\n\n    event.waitUntil(\n      deleteOutdatedCaches(cacheName).then((cachesDeleted) => {\n        if (process.env.NODE_ENV !== 'production') {\n          if (cachesDeleted.length > 0) {\n            logger.log(\n              `The following out-of-date precaches were cleaned up ` +\n                `automatically:`,\n              cachesDeleted,\n            );\n          }\n        }\n      }),\n    );\n  }) as EventListener);\n}\n\nexport {cleanupOutdatedCaches};\n"
  },
  {
    "path": "packages/workbox-precaching/src/createHandlerBoundToURL.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport {RouteHandlerCallback} from 'workbox-core/types.js';\nimport './_version.js';\n\n/**\n * Helper function that calls\n * {@link PrecacheController#createHandlerBoundToURL} on the default\n * {@link PrecacheController} instance.\n *\n * If you are creating your own {@link PrecacheController}, then call the\n * {@link PrecacheController#createHandlerBoundToURL} on that instance,\n * instead of using this function.\n *\n * @param {string} url The precached URL which will be used to lookup the\n * `Response`.\n * @param {boolean} [fallbackToNetwork=true] Whether to attempt to get the\n * response from the network if there's a precache miss.\n * @return {workbox-routing~handlerCallback}\n *\n * @memberof workbox-precaching\n */\nfunction createHandlerBoundToURL(url: string): RouteHandlerCallback {\n  const precacheController = getOrCreatePrecacheController();\n  return precacheController.createHandlerBoundToURL(url);\n}\n\nexport {createHandlerBoundToURL};\n"
  },
  {
    "path": "packages/workbox-precaching/src/getCacheKeyForURL.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport './_version.js';\n\n/**\n * Takes in a URL, and returns the corresponding URL that could be used to\n * lookup the entry in the precache.\n *\n * If a relative URL is provided, the location of the service worker file will\n * be used as the base.\n *\n * For precached entries without revision information, the cache key will be the\n * same as the original URL.\n *\n * For precached entries with revision information, the cache key will be the\n * original URL with the addition of a query parameter used for keeping track of\n * the revision info.\n *\n * @param {string} url The URL whose cache key to look up.\n * @return {string} The cache key that corresponds to that URL.\n *\n * @memberof workbox-precaching\n */\nfunction getCacheKeyForURL(url: string): string | undefined {\n  const precacheController = getOrCreatePrecacheController();\n  return precacheController.getCacheKeyForURL(url);\n}\n\nexport {getCacheKeyForURL};\n"
  },
  {
    "path": "packages/workbox-precaching/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {addPlugins} from './addPlugins.js';\nimport {addRoute} from './addRoute.js';\nimport {cleanupOutdatedCaches} from './cleanupOutdatedCaches.js';\nimport {createHandlerBoundToURL} from './createHandlerBoundToURL.js';\nimport {getCacheKeyForURL} from './getCacheKeyForURL.js';\nimport {matchPrecache} from './matchPrecache.js';\nimport {precache} from './precache.js';\nimport {precacheAndRoute} from './precacheAndRoute.js';\nimport {PrecacheController} from './PrecacheController.js';\nimport {PrecacheRoute} from './PrecacheRoute.js';\nimport {PrecacheStrategy} from './PrecacheStrategy.js';\nimport {PrecacheFallbackPlugin} from './PrecacheFallbackPlugin.js';\n\nimport './_version.js';\n\n/**\n * Most consumers of this module will want to use the\n * {@link workbox-precaching.precacheAndRoute}\n * method to add assets to the cache and respond to network requests with these\n * cached assets.\n *\n * If you require more control over caching and routing, you can use the\n * {@link workbox-precaching.PrecacheController}\n * interface.\n *\n * @module workbox-precaching\n */\n\nexport {\n  addPlugins,\n  addRoute,\n  cleanupOutdatedCaches,\n  createHandlerBoundToURL,\n  getCacheKeyForURL,\n  matchPrecache,\n  precache,\n  precacheAndRoute,\n  PrecacheController,\n  PrecacheRoute,\n  PrecacheStrategy,\n  PrecacheFallbackPlugin,\n};\n\nexport * from './_types.js';\n"
  },
  {
    "path": "packages/workbox-precaching/src/matchPrecache.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\n\nimport './_version.js';\n\n/**\n * Helper function that calls\n * {@link PrecacheController#matchPrecache} on the default\n * {@link PrecacheController} instance.\n *\n * If you are creating your own {@link PrecacheController}, then call\n * {@link PrecacheController#matchPrecache} on that instance,\n * instead of using this function.\n *\n * @param {string|Request} request The key (without revisioning parameters)\n * to look up in the precache.\n * @return {Promise<Response|undefined>}\n *\n * @memberof workbox-precaching\n */\nfunction matchPrecache(\n  request: string | Request,\n): Promise<Response | undefined> {\n  const precacheController = getOrCreatePrecacheController();\n  return precacheController.matchPrecache(request);\n}\n\nexport {matchPrecache};\n"
  },
  {
    "path": "packages/workbox-precaching/src/precache.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from './utils/getOrCreatePrecacheController.js';\nimport {PrecacheEntry} from './_types.js';\nimport './_version.js';\n\n/**\n * Adds items to the precache list, removing any duplicates and\n * stores the files in the\n * {@link workbox-core.cacheNames|\"precache cache\"} when the service\n * worker installs.\n *\n * This method can be called multiple times.\n *\n * Please note: This method **will not** serve any of the cached files for you.\n * It only precaches files. To respond to a network request you call\n * {@link workbox-precaching.addRoute}.\n *\n * If you have a single array of files to precache, you can just call\n * {@link workbox-precaching.precacheAndRoute}.\n *\n * @param {Array<Object|string>} [entries=[]] Array of entries to precache.\n *\n * @memberof workbox-precaching\n */\nfunction precache(entries: Array<PrecacheEntry | string>): void {\n  const precacheController = getOrCreatePrecacheController();\n  precacheController.precache(entries);\n}\n\nexport {precache};\n"
  },
  {
    "path": "packages/workbox-precaching/src/precacheAndRoute.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {addRoute} from './addRoute.js';\nimport {precache} from './precache.js';\nimport {PrecacheRouteOptions, PrecacheEntry} from './_types.js';\nimport './_version.js';\n\n/**\n * This method will add entries to the precache list and add a route to\n * respond to fetch events.\n *\n * This is a convenience method that will call\n * {@link workbox-precaching.precache} and\n * {@link workbox-precaching.addRoute} in a single call.\n *\n * @param {Array<Object|string>} entries Array of entries to precache.\n * @param {Object} [options] See the\n * {@link workbox-precaching.PrecacheRoute} options.\n *\n * @memberof workbox-precaching\n */\nfunction precacheAndRoute(\n  entries: Array<PrecacheEntry | string>,\n  options?: PrecacheRouteOptions,\n): void {\n  precache(entries);\n  addRoute(options);\n}\n\nexport {precacheAndRoute};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/PrecacheCacheKeyPlugin.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin, WorkboxPluginCallbackParam} from 'workbox-core/types.js';\n\nimport {PrecacheController} from '../PrecacheController.js';\n\nimport '../_version.js';\n\n/**\n * A plugin, designed to be used with PrecacheController, to translate URLs into\n * the corresponding cache key, based on the current revision info.\n *\n * @private\n */\nclass PrecacheCacheKeyPlugin implements WorkboxPlugin {\n  private readonly _precacheController: PrecacheController;\n\n  constructor({precacheController}: {precacheController: PrecacheController}) {\n    this._precacheController = precacheController;\n  }\n\n  cacheKeyWillBeUsed: WorkboxPlugin['cacheKeyWillBeUsed'] = async ({\n    request,\n    params,\n  }: WorkboxPluginCallbackParam['cacheKeyWillBeUsed']) => {\n    // Params is type any, can't change right now.\n    /* eslint-disable */\n    const cacheKey =\n      params?.cacheKey ||\n      this._precacheController.getCacheKeyForURL(request.url);\n    /* eslint-enable */\n\n    return cacheKey\n      ? new Request(cacheKey, {headers: request.headers})\n      : request;\n  };\n}\n\nexport {PrecacheCacheKeyPlugin};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/PrecacheInstallReportPlugin.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin, WorkboxPluginCallbackParam} from 'workbox-core/types.js';\n\nimport '../_version.js';\n\n/**\n * A plugin, designed to be used with PrecacheController, to determine the\n * of assets that were updated (or not updated) during the install event.\n *\n * @private\n */\nclass PrecacheInstallReportPlugin implements WorkboxPlugin {\n  updatedURLs: string[] = [];\n  notUpdatedURLs: string[] = [];\n\n  handlerWillStart: WorkboxPlugin['handlerWillStart'] = async ({\n    request,\n    state,\n  }: WorkboxPluginCallbackParam['handlerWillStart']) => {\n    // TODO: `state` should never be undefined...\n    if (state) {\n      state.originalRequest = request;\n    }\n  };\n\n  cachedResponseWillBeUsed: WorkboxPlugin['cachedResponseWillBeUsed'] = async ({\n    event,\n    state,\n    cachedResponse,\n  }: WorkboxPluginCallbackParam['cachedResponseWillBeUsed']) => {\n    if (event.type === 'install') {\n      if (\n        state &&\n        state.originalRequest &&\n        state.originalRequest instanceof Request\n      ) {\n        // TODO: `state` should never be undefined...\n        const url = state.originalRequest.url;\n\n        if (cachedResponse) {\n          this.notUpdatedURLs.push(url);\n        } else {\n          this.updatedURLs.push(url);\n        }\n      }\n    }\n    return cachedResponse;\n  };\n}\n\nexport {PrecacheInstallReportPlugin};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/createCacheKey.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {PrecacheEntry} from '../_types.js';\nimport '../_version.js';\n\ninterface CacheKey {\n  cacheKey: string;\n  url: string;\n}\n\n// Name of the search parameter used to store revision info.\nconst REVISION_SEARCH_PARAM = '__WB_REVISION__';\n\n/**\n * Converts a manifest entry into a versioned URL suitable for precaching.\n *\n * @param {Object|string} entry\n * @return {string} A URL with versioning info.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function createCacheKey(entry: PrecacheEntry | string): CacheKey {\n  if (!entry) {\n    throw new WorkboxError('add-to-cache-list-unexpected-type', {entry});\n  }\n\n  // If a precache manifest entry is a string, it's assumed to be a versioned\n  // URL, like '/app.abcd1234.js'. Return as-is.\n  if (typeof entry === 'string') {\n    const urlObject = new URL(entry, location.href);\n    return {\n      cacheKey: urlObject.href,\n      url: urlObject.href,\n    };\n  }\n\n  const {revision, url} = entry;\n  if (!url) {\n    throw new WorkboxError('add-to-cache-list-unexpected-type', {entry});\n  }\n\n  // If there's just a URL and no revision, then it's also assumed to be a\n  // versioned URL.\n  if (!revision) {\n    const urlObject = new URL(url, location.href);\n    return {\n      cacheKey: urlObject.href,\n      url: urlObject.href,\n    };\n  }\n\n  // Otherwise, construct a properly versioned URL using the custom Workbox\n  // search parameter along with the revision info.\n  const cacheKeyURL = new URL(url, location.href);\n  const originalURL = new URL(url, location.href);\n  cacheKeyURL.searchParams.set(REVISION_SEARCH_PARAM, revision);\n  return {\n    cacheKey: cacheKeyURL.href,\n    url: originalURL.href,\n  };\n}\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/deleteOutdatedCaches.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\nconst SUBSTRING_TO_FIND = '-precache-';\n\n/**\n * Cleans up incompatible precaches that were created by older versions of\n * Workbox, by a service worker registered under the current scope.\n *\n * This is meant to be called as part of the `activate` event.\n *\n * This should be safe to use as long as you don't include `substringToFind`\n * (defaulting to `-precache-`) in your non-precache cache names.\n *\n * @param {string} currentPrecacheName The cache name currently in use for\n * precaching. This cache won't be deleted.\n * @param {string} [substringToFind='-precache-'] Cache names which include this\n * substring will be deleted (excluding `currentPrecacheName`).\n * @return {Array<string>} A list of all the cache names that were deleted.\n *\n * @private\n * @memberof workbox-precaching\n */\nconst deleteOutdatedCaches = async (\n  currentPrecacheName: string,\n  substringToFind: string = SUBSTRING_TO_FIND,\n): Promise<string[]> => {\n  const cacheNames = await self.caches.keys();\n\n  const cacheNamesToDelete = cacheNames.filter((cacheName) => {\n    return (\n      cacheName.includes(substringToFind) &&\n      cacheName.includes(self.registration.scope) &&\n      cacheName !== currentPrecacheName\n    );\n  });\n\n  await Promise.all(\n    cacheNamesToDelete.map((cacheName) => self.caches.delete(cacheName)),\n  );\n\n  return cacheNamesToDelete;\n};\n\nexport {deleteOutdatedCaches};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/generateURLVariations.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {removeIgnoredSearchParams} from './removeIgnoredSearchParams.js';\nimport {PrecacheRouteOptions} from '../_types.js';\nimport '../_version.js';\n\n/**\n * Generator function that yields possible variations on the original URL to\n * check, one at a time.\n *\n * @param {string} url\n * @param {Object} options\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function* generateURLVariations(\n  url: string,\n  {\n    ignoreURLParametersMatching = [/^utm_/, /^fbclid$/],\n    directoryIndex = 'index.html',\n    cleanURLs = true,\n    urlManipulation,\n  }: PrecacheRouteOptions = {},\n): Generator<string, void, unknown> {\n  const urlObject = new URL(url, location.href);\n  urlObject.hash = '';\n  yield urlObject.href;\n\n  const urlWithoutIgnoredParams = removeIgnoredSearchParams(\n    urlObject,\n    ignoreURLParametersMatching,\n  );\n  yield urlWithoutIgnoredParams.href;\n\n  if (directoryIndex && urlWithoutIgnoredParams.pathname.endsWith('/')) {\n    const directoryURL = new URL(urlWithoutIgnoredParams.href);\n    directoryURL.pathname += directoryIndex;\n    yield directoryURL.href;\n  }\n\n  if (cleanURLs) {\n    const cleanURL = new URL(urlWithoutIgnoredParams.href);\n    cleanURL.pathname += '.html';\n    yield cleanURL.href;\n  }\n\n  if (urlManipulation) {\n    const additionalURLs = urlManipulation({url: urlObject});\n    for (const urlToAttempt of additionalURLs) {\n      yield urlToAttempt.href;\n    }\n  }\n}\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/getCacheKeyForURL.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from './getOrCreatePrecacheController.js';\nimport {generateURLVariations} from './generateURLVariations.js';\nimport {PrecacheRouteOptions} from '../_types.js';\nimport '../_version.js';\n\n/**\n * This function will take the request URL and manipulate it based on the\n * configuration options.\n *\n * @param {string} url\n * @param {Object} options\n * @return {string} Returns the URL in the cache that matches the request,\n * if possible.\n *\n * @private\n */\nexport const getCacheKeyForURL = (\n  url: string,\n  options: PrecacheRouteOptions,\n): string | void => {\n  const precacheController = getOrCreatePrecacheController();\n\n  const urlsToCacheKeys = precacheController.getURLsToCacheKeys();\n  for (const possibleURL of generateURLVariations(url, options)) {\n    const possibleCacheKey = urlsToCacheKeys.get(possibleURL);\n    if (possibleCacheKey) {\n      return possibleCacheKey;\n    }\n  }\n};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/getOrCreatePrecacheController.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {PrecacheController} from '../PrecacheController.js';\nimport '../_version.js';\n\nlet precacheController: PrecacheController | undefined;\n\n/**\n * @return {PrecacheController}\n * @private\n */\nexport const getOrCreatePrecacheController = (): PrecacheController => {\n  if (!precacheController) {\n    precacheController = new PrecacheController();\n  }\n  return precacheController;\n};\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/printCleanupDetails.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport '../_version.js';\n\n/**\n * @param {string} groupTitle\n * @param {Array<string>} deletedURLs\n *\n * @private\n */\nconst logGroup = (groupTitle: string, deletedURLs: string[]) => {\n  logger.groupCollapsed(groupTitle);\n\n  for (const url of deletedURLs) {\n    logger.log(url);\n  }\n\n  logger.groupEnd();\n};\n\n/**\n * @param {Array<string>} deletedURLs\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function printCleanupDetails(deletedURLs: string[]): void {\n  const deletionCount = deletedURLs.length;\n  if (deletionCount > 0) {\n    logger.groupCollapsed(\n      `During precaching cleanup, ` +\n        `${deletionCount} cached ` +\n        `request${deletionCount === 1 ? ' was' : 's were'} deleted.`,\n    );\n    logGroup('Deleted Cache Requests', deletedURLs);\n    logger.groupEnd();\n  }\n}\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/printInstallDetails.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport '../_version.js';\n\n/**\n * @param {string} groupTitle\n * @param {Array<string>} urls\n *\n * @private\n */\nfunction _nestedGroup(groupTitle: string, urls: string[]): void {\n  if (urls.length === 0) {\n    return;\n  }\n\n  logger.groupCollapsed(groupTitle);\n\n  for (const url of urls) {\n    logger.log(url);\n  }\n\n  logger.groupEnd();\n}\n\n/**\n * @param {Array<string>} urlsToPrecache\n * @param {Array<string>} urlsAlreadyPrecached\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function printInstallDetails(\n  urlsToPrecache: string[],\n  urlsAlreadyPrecached: string[],\n): void {\n  const precachedCount = urlsToPrecache.length;\n  const alreadyPrecachedCount = urlsAlreadyPrecached.length;\n\n  if (precachedCount || alreadyPrecachedCount) {\n    let message = `Precaching ${precachedCount} file${\n      precachedCount === 1 ? '' : 's'\n    }.`;\n\n    if (alreadyPrecachedCount > 0) {\n      message +=\n        ` ${alreadyPrecachedCount} ` +\n        `file${alreadyPrecachedCount === 1 ? ' is' : 's are'} already cached.`;\n    }\n\n    logger.groupCollapsed(message);\n\n    _nestedGroup(`View newly precached URLs.`, urlsToPrecache);\n    _nestedGroup(`View previously precached URLs.`, urlsAlreadyPrecached);\n    logger.groupEnd();\n  }\n}\n"
  },
  {
    "path": "packages/workbox-precaching/src/utils/removeIgnoredSearchParams.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * Removes any URL search parameters that should be ignored.\n *\n * @param {URL} urlObject The original URL.\n * @param {Array<RegExp>} ignoreURLParametersMatching RegExps to test against\n * each search parameter name. Matches mean that the search parameter should be\n * ignored.\n * @return {URL} The URL with any ignored search parameters removed.\n *\n * @private\n * @memberof workbox-precaching\n */\nexport function removeIgnoredSearchParams(\n  urlObject: URL,\n  ignoreURLParametersMatching: RegExp[] = [],\n): URL {\n  // Convert the iterable into an array at the start of the loop to make sure\n  // deletion doesn't mess up iteration.\n  for (const paramName of [...urlObject.searchParams.keys()]) {\n    if (ignoreURLParametersMatching.some((regExp) => regExp.test(paramName))) {\n      urlObject.searchParams.delete(paramName);\n    }\n  }\n\n  return urlObject;\n}\n"
  },
  {
    "path": "packages/workbox-precaching/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [\n    {\"path\": \"../workbox-core/\"},\n    {\"path\": \"../workbox-routing/\"},\n    {\"path\": \"../workbox-strategies/\"}\n  ]\n}\n"
  },
  {
    "path": "packages/workbox-range-requests/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-range-requests\n"
  },
  {
    "path": "packages/workbox-range-requests/package.json",
    "content": "{\n  \"name\": \"workbox-range-requests\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This library creates a new Response, given a source Response and a Range header value.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"caching\",\n    \"cache\",\n    \"range\",\n    \"media\",\n    \"workbox-plugin\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.rangeRequests\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-range-requests/src/RangeRequestsPlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport {createPartialResponse} from './createPartialResponse.js';\nimport './_version.js';\n\n/**\n * The range request plugin makes it easy for a request with a 'Range' header to\n * be fulfilled by a cached response.\n *\n * It does this by intercepting the `cachedResponseWillBeUsed` plugin callback\n * and returning the appropriate subset of the cached response body.\n *\n * @memberof workbox-range-requests\n */\nclass RangeRequestsPlugin implements WorkboxPlugin {\n  /**\n   * @param {Object} options\n   * @param {Request} options.request The original request, which may or may not\n   * contain a Range: header.\n   * @param {Response} options.cachedResponse The complete cached response.\n   * @return {Promise<Response>} If request contains a 'Range' header, then a\n   * new response with status 206 whose body is a subset of `cachedResponse` is\n   * returned. Otherwise, `cachedResponse` is returned as-is.\n   *\n   * @private\n   */\n  cachedResponseWillBeUsed: WorkboxPlugin['cachedResponseWillBeUsed'] = async ({\n    request,\n    cachedResponse,\n  }) => {\n    // Only return a sliced response if there's something valid in the cache,\n    // and there's a Range: header in the request.\n    if (cachedResponse && request.headers.has('range')) {\n      return await createPartialResponse(request, cachedResponse);\n    }\n\n    // If there was no Range: header, or if cachedResponse wasn't valid, just\n    // pass it through as-is.\n    return cachedResponse;\n  };\n}\n\nexport {RangeRequestsPlugin};\n"
  },
  {
    "path": "packages/workbox-range-requests/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:range-requests:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-range-requests/src/createPartialResponse.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {calculateEffectiveBoundaries} from './utils/calculateEffectiveBoundaries.js';\nimport {parseRangeHeader} from './utils/parseRangeHeader.js';\nimport './_version.js';\n\n/**\n * Given a `Request` and `Response` objects as input, this will return a\n * promise for a new `Response`.\n *\n * If the original `Response` already contains partial content (i.e. it has\n * a status of 206), then this assumes it already fulfills the `Range:`\n * requirements, and will return it as-is.\n *\n * @param {Request} request A request, which should contain a Range:\n * header.\n * @param {Response} originalResponse A response.\n * @return {Promise<Response>} Either a `206 Partial Content` response, with\n * the response body set to the slice of content specified by the request's\n * `Range:` header, or a `416 Range Not Satisfiable` response if the\n * conditions of the `Range:` header can't be met.\n *\n * @memberof workbox-range-requests\n */\nasync function createPartialResponse(\n  request: Request,\n  originalResponse: Response,\n): Promise<Response> {\n  try {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-range-requests',\n        funcName: 'createPartialResponse',\n        paramName: 'request',\n      });\n\n      assert!.isInstance(originalResponse, Response, {\n        moduleName: 'workbox-range-requests',\n        funcName: 'createPartialResponse',\n        paramName: 'originalResponse',\n      });\n    }\n\n    if (originalResponse.status === 206) {\n      // If we already have a 206, then just pass it through as-is;\n      // see https://github.com/GoogleChrome/workbox/issues/1720\n      return originalResponse;\n    }\n\n    const rangeHeader = request.headers.get('range');\n    if (!rangeHeader) {\n      throw new WorkboxError('no-range-header');\n    }\n\n    const boundaries = parseRangeHeader(rangeHeader);\n    const originalBlob = await originalResponse.blob();\n\n    const effectiveBoundaries = calculateEffectiveBoundaries(\n      originalBlob,\n      boundaries.start,\n      boundaries.end,\n    );\n\n    const slicedBlob = originalBlob.slice(\n      effectiveBoundaries.start,\n      effectiveBoundaries.end,\n    );\n    const slicedBlobSize = slicedBlob.size;\n\n    const slicedResponse = new Response(slicedBlob, {\n      // Status code 206 is for a Partial Content response.\n      // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206\n      status: 206,\n      statusText: 'Partial Content',\n      headers: originalResponse.headers,\n    });\n\n    slicedResponse.headers.set('Content-Length', String(slicedBlobSize));\n    slicedResponse.headers.set(\n      'Content-Range',\n      `bytes ${effectiveBoundaries.start}-${effectiveBoundaries.end - 1}/` +\n        `${originalBlob.size}`,\n    );\n\n    return slicedResponse;\n  } catch (error) {\n    if (process.env.NODE_ENV !== 'production') {\n      logger.warn(\n        `Unable to construct a partial response; returning a ` +\n          `416 Range Not Satisfiable response instead.`,\n      );\n      logger.groupCollapsed(`View details here.`);\n      logger.log(error);\n      logger.log(request);\n      logger.log(originalResponse);\n      logger.groupEnd();\n    }\n\n    return new Response('', {\n      status: 416,\n      statusText: 'Range Not Satisfiable',\n    });\n  }\n}\n\nexport {createPartialResponse};\n"
  },
  {
    "path": "packages/workbox-range-requests/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {createPartialResponse} from './createPartialResponse.js';\nimport {RangeRequestsPlugin} from './RangeRequestsPlugin.js';\nimport './_version.js';\n\n/**\n * @module workbox-range-requests\n */\n\nexport {createPartialResponse, RangeRequestsPlugin};\n"
  },
  {
    "path": "packages/workbox-range-requests/src/utils/calculateEffectiveBoundaries.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {assert} from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n\n/**\n * @param {Blob} blob A source blob.\n * @param {number} [start] The offset to use as the start of the\n * slice.\n * @param {number} [end] The offset to use as the end of the slice.\n * @return {Object} An object with `start` and `end` properties, reflecting\n * the effective boundaries to use given the size of the blob.\n *\n * @private\n */\nfunction calculateEffectiveBoundaries(\n  blob: Blob,\n  start?: number,\n  end?: number,\n): {start: number; end: number} {\n  if (process.env.NODE_ENV !== 'production') {\n    assert!.isInstance(blob, Blob, {\n      moduleName: 'workbox-range-requests',\n      funcName: 'calculateEffectiveBoundaries',\n      paramName: 'blob',\n    });\n  }\n\n  const blobSize = blob.size;\n\n  if ((end && end > blobSize) || (start && start < 0)) {\n    throw new WorkboxError('range-not-satisfiable', {\n      size: blobSize,\n      end,\n      start,\n    });\n  }\n\n  let effectiveStart: number;\n  let effectiveEnd: number;\n\n  if (start !== undefined && end !== undefined) {\n    effectiveStart = start;\n    // Range values are inclusive, so add 1 to the value.\n    effectiveEnd = end + 1;\n  } else if (start !== undefined && end === undefined) {\n    effectiveStart = start;\n    effectiveEnd = blobSize;\n  } else if (end !== undefined && start === undefined) {\n    effectiveStart = blobSize - end;\n    effectiveEnd = blobSize;\n  }\n\n  return {\n    start: effectiveStart!,\n    end: effectiveEnd!,\n  };\n}\n\nexport {calculateEffectiveBoundaries};\n"
  },
  {
    "path": "packages/workbox-range-requests/src/utils/parseRangeHeader.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {assert} from 'workbox-core/_private/assert.js';\nimport '../_version.js';\n\n/**\n * @param {string} rangeHeader A Range: header value.\n * @return {Object} An object with `start` and `end` properties, reflecting\n * the parsed value of the Range: header. If either the `start` or `end` are\n * omitted, then `null` will be returned.\n *\n * @private\n */\nfunction parseRangeHeader(rangeHeader: string): {start?: number; end?: number} {\n  if (process.env.NODE_ENV !== 'production') {\n    assert!.isType(rangeHeader, 'string', {\n      moduleName: 'workbox-range-requests',\n      funcName: 'parseRangeHeader',\n      paramName: 'rangeHeader',\n    });\n  }\n\n  const normalizedRangeHeader = rangeHeader.trim().toLowerCase();\n  if (!normalizedRangeHeader.startsWith('bytes=')) {\n    throw new WorkboxError('unit-must-be-bytes', {normalizedRangeHeader});\n  }\n\n  // Specifying multiple ranges separate by commas is valid syntax, but this\n  // library only attempts to handle a single, contiguous sequence of bytes.\n  // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range#Syntax\n  if (normalizedRangeHeader.includes(',')) {\n    throw new WorkboxError('single-range-only', {normalizedRangeHeader});\n  }\n\n  const rangeParts = /(\\d*)-(\\d*)/.exec(normalizedRangeHeader);\n  // We need either at least one of the start or end values.\n  if (!rangeParts || !(rangeParts[1] || rangeParts[2])) {\n    throw new WorkboxError('invalid-range-values', {normalizedRangeHeader});\n  }\n\n  return {\n    start: rangeParts[1] === '' ? undefined : Number(rangeParts[1]),\n    end: rangeParts[2] === '' ? undefined : Number(rangeParts[2]),\n  };\n}\n\nexport {parseRangeHeader};\n"
  },
  {
    "path": "packages/workbox-range-requests/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-recipes/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-recipes\n"
  },
  {
    "path": "packages/workbox-recipes/package.json",
    "content": "{\n  \"name\": \"workbox-recipes\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A service worker helper library to manage common request and caching patterns\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"router\",\n    \"routing\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.recipes\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-cacheable-response\": \"7.4.0\",\n    \"workbox-core\": \"7.4.0\",\n    \"workbox-expiration\": \"7.4.0\",\n    \"workbox-precaching\": \"7.4.0\",\n    \"workbox-routing\": \"7.4.0\",\n    \"workbox-strategies\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-recipes/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:recipes:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-recipes/src/googleFontsCache.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport {registerRoute} from 'workbox-routing/registerRoute.js';\nimport {StaleWhileRevalidate} from 'workbox-strategies/StaleWhileRevalidate.js';\nimport {CacheFirst} from 'workbox-strategies/CacheFirst.js';\nimport {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';\nimport {ExpirationPlugin} from 'workbox-expiration/ExpirationPlugin.js';\n\nimport './_version.js';\n\nexport interface GoogleFontCacheOptions {\n  cachePrefix?: string;\n  maxAgeSeconds?: number;\n  maxEntries?: number;\n}\n\n/**\n * An implementation of the [Google fonts]{@link https://developers.google.com/web/tools/workbox/guides/common-recipes#google_fonts} caching recipe\n *\n * @memberof workbox-recipes\n *\n * @param {Object} [options]\n * @param {string} [options.cachePrefix] Cache prefix for caching stylesheets and webfonts. Defaults to google-fonts\n * @param {number} [options.maxAgeSeconds] Maximum age, in seconds, that font entries will be cached for. Defaults to 1 year\n * @param {number} [options.maxEntries] Maximum number of fonts that will be cached. Defaults to 30\n */\nfunction googleFontsCache(options: GoogleFontCacheOptions = {}): void {\n  const sheetCacheName = `${options.cachePrefix || 'google-fonts'}-stylesheets`;\n  const fontCacheName = `${options.cachePrefix || 'google-fonts'}-webfonts`;\n  const maxAgeSeconds = options.maxAgeSeconds || 60 * 60 * 24 * 365;\n  const maxEntries = options.maxEntries || 30;\n\n  // Cache the Google Fonts stylesheets with a stale-while-revalidate strategy.\n  registerRoute(\n    ({url}) => url.origin === 'https://fonts.googleapis.com',\n    new StaleWhileRevalidate({\n      cacheName: sheetCacheName,\n    }),\n  );\n\n  // Cache the underlying font files with a cache-first strategy for 1 year.\n  registerRoute(\n    ({url}) => url.origin === 'https://fonts.gstatic.com',\n    new CacheFirst({\n      cacheName: fontCacheName,\n      plugins: [\n        new CacheableResponsePlugin({\n          statuses: [0, 200],\n        }),\n        new ExpirationPlugin({\n          maxAgeSeconds,\n          maxEntries,\n        }),\n      ],\n    }),\n  );\n}\n\nexport {googleFontsCache};\n"
  },
  {
    "path": "packages/workbox-recipes/src/imageCache.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport {warmStrategyCache} from './warmStrategyCache';\nimport {registerRoute} from 'workbox-routing/registerRoute.js';\nimport {CacheFirst} from 'workbox-strategies/CacheFirst.js';\nimport {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';\nimport {ExpirationPlugin} from 'workbox-expiration/ExpirationPlugin.js';\nimport {\n  RouteMatchCallback,\n  RouteMatchCallbackOptions,\n  WorkboxPlugin,\n} from 'workbox-core/types.js';\n\nimport './_version.js';\n\nexport interface ImageCacheOptions {\n  cacheName?: string;\n  matchCallback?: RouteMatchCallback;\n  maxAgeSeconds?: number;\n  maxEntries?: number;\n  plugins?: Array<WorkboxPlugin>;\n  warmCache?: Array<string>;\n}\n\n/**\n * An implementation of the [image caching recipe]{@link https://developers.google.com/web/tools/workbox/guides/common-recipes#caching_images}\n *\n * @memberof workbox-recipes\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Name for cache. Defaults to images\n * @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.destination === 'image';\n * @param {number} [options.maxAgeSeconds] Maximum age, in seconds, that font entries will be cached for. Defaults to 30 days\n * @param {number} [options.maxEntries] Maximum number of images that will be cached. Defaults to 60\n * @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe\n * @param {string[]} [options.warmCache] Paths to call to use to warm this cache\n */\nfunction imageCache(options: ImageCacheOptions = {}): void {\n  const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) =>\n    request.destination === 'image';\n\n  const cacheName = options.cacheName || 'images';\n  const matchCallback = options.matchCallback || defaultMatchCallback;\n  const maxAgeSeconds = options.maxAgeSeconds || 30 * 24 * 60 * 60;\n  const maxEntries = options.maxEntries || 60;\n  const plugins = options.plugins || [];\n  plugins.push(\n    new CacheableResponsePlugin({\n      statuses: [0, 200],\n    }),\n  );\n  plugins.push(\n    new ExpirationPlugin({\n      maxEntries,\n      maxAgeSeconds,\n    }),\n  );\n\n  const strategy = new CacheFirst({\n    cacheName,\n    plugins,\n  });\n\n  registerRoute(matchCallback, strategy);\n\n  // Warms the cache\n  if (options.warmCache) {\n    warmStrategyCache({urls: options.warmCache, strategy});\n  }\n}\n\nexport {imageCache};\n"
  },
  {
    "path": "packages/workbox-recipes/src/index.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {googleFontsCache, GoogleFontCacheOptions} from './googleFontsCache';\nimport {imageCache, ImageCacheOptions} from './imageCache';\nimport {\n  staticResourceCache,\n  StaticResourceOptions,\n} from './staticResourceCache';\nimport {pageCache, PageCacheOptions} from './pageCache';\nimport {offlineFallback, OfflineFallbackOptions} from './offlineFallback';\nimport {warmStrategyCache, WarmStrategyCacheOptions} from './warmStrategyCache';\n\nimport './_version.js';\n\n/**\n * @module workbox-recipes\n */\n\nexport {\n  GoogleFontCacheOptions,\n  googleFontsCache,\n  imageCache,\n  ImageCacheOptions,\n  offlineFallback,\n  OfflineFallbackOptions,\n  pageCache,\n  PageCacheOptions,\n  staticResourceCache,\n  StaticResourceOptions,\n  warmStrategyCache,\n  WarmStrategyCacheOptions,\n};\n"
  },
  {
    "path": "packages/workbox-recipes/src/offlineFallback.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport {setCatchHandler} from 'workbox-routing/setCatchHandler.js';\nimport {matchPrecache} from 'workbox-precaching/matchPrecache.js';\nimport {RouteHandler, RouteHandlerCallbackOptions} from 'workbox-core/types.js';\n\nimport './_version.js';\n\nexport interface OfflineFallbackOptions {\n  pageFallback?: string;\n  imageFallback?: string;\n  fontFallback?: string;\n}\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * An implementation of the [comprehensive fallbacks recipe]{@link https://developers.google.com/web/tools/workbox/guides/advanced-recipes#comprehensive_fallbacks}. Be sure to include the fallbacks in your precache injection\n *\n * @memberof workbox-recipes\n *\n * @param {Object} [options]\n * @param {string} [options.pageFallback] Precache name to match for pag fallbacks. Defaults to offline.html\n * @param {string} [options.imageFallback] Precache name to match for image fallbacks.\n * @param {string} [options.fontFallback] Precache name to match for font fallbacks.\n */\nfunction offlineFallback(options: OfflineFallbackOptions = {}): void {\n  const pageFallback = options.pageFallback || 'offline.html';\n  const imageFallback = options.imageFallback || false;\n  const fontFallback = options.fontFallback || false;\n\n  self.addEventListener('install', (event) => {\n    const files = [pageFallback];\n    if (imageFallback) {\n      files.push(imageFallback);\n    }\n    if (fontFallback) {\n      files.push(fontFallback);\n    }\n\n    event.waitUntil(\n      self.caches\n        .open('workbox-offline-fallbacks')\n        .then((cache) => cache.addAll(files)),\n    );\n  });\n\n  const handler: RouteHandler = async (\n    options: RouteHandlerCallbackOptions,\n  ) => {\n    const dest = options.request.destination;\n    const cache = await self.caches.open('workbox-offline-fallbacks');\n\n    if (dest === 'document') {\n      const match =\n        (await matchPrecache(pageFallback)) ||\n        (await cache.match(pageFallback));\n      return match || Response.error();\n    }\n\n    if (dest === 'image' && imageFallback !== false) {\n      const match =\n        (await matchPrecache(imageFallback)) ||\n        (await cache.match(imageFallback));\n      return match || Response.error();\n    }\n\n    if (dest === 'font' && fontFallback !== false) {\n      const match =\n        (await matchPrecache(fontFallback)) ||\n        (await cache.match(fontFallback));\n      return match || Response.error();\n    }\n\n    return Response.error();\n  };\n\n  setCatchHandler(handler);\n}\n\nexport {offlineFallback};\n"
  },
  {
    "path": "packages/workbox-recipes/src/pageCache.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport {warmStrategyCache} from './warmStrategyCache';\nimport {registerRoute} from 'workbox-routing/registerRoute.js';\nimport {NetworkFirst} from 'workbox-strategies/NetworkFirst.js';\nimport {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';\nimport {\n  RouteMatchCallback,\n  RouteMatchCallbackOptions,\n  WorkboxPlugin,\n} from 'workbox-core/types.js';\n\nimport './_version.js';\n\nexport interface PageCacheOptions {\n  cacheName?: string;\n  matchCallback?: RouteMatchCallback;\n  networkTimeoutSeconds?: number;\n  plugins?: Array<WorkboxPlugin>;\n  warmCache?: Array<string>;\n}\n\n/**\n * An implementation of a page caching recipe with a network timeout\n *\n * @memberof workbox-recipes\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Name for cache. Defaults to pages\n * @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.mode === 'navigate';\n * @param {number} [options.networkTimoutSeconds] Maximum amount of time, in seconds, to wait on the network before falling back to cache. Defaults to 3\n * @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe\n * @param {string[]} [options.warmCache] Paths to call to use to warm this cache\n */\nfunction pageCache(options: PageCacheOptions = {}): void {\n  const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) =>\n    request.mode === 'navigate';\n\n  const cacheName = options.cacheName || 'pages';\n  const matchCallback = options.matchCallback || defaultMatchCallback;\n  const networkTimeoutSeconds = options.networkTimeoutSeconds || 3;\n  const plugins = options.plugins || [];\n  plugins.push(\n    new CacheableResponsePlugin({\n      statuses: [0, 200],\n    }),\n  );\n\n  const strategy = new NetworkFirst({\n    networkTimeoutSeconds,\n    cacheName,\n    plugins,\n  });\n\n  // Registers the route\n  registerRoute(matchCallback, strategy);\n\n  // Warms the cache\n  if (options.warmCache) {\n    warmStrategyCache({urls: options.warmCache, strategy});\n  }\n}\n\nexport {pageCache};\n"
  },
  {
    "path": "packages/workbox-recipes/src/staticResourceCache.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\nimport {warmStrategyCache} from './warmStrategyCache';\nimport {registerRoute} from 'workbox-routing/registerRoute.js';\nimport {StaleWhileRevalidate} from 'workbox-strategies/StaleWhileRevalidate.js';\nimport {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.js';\nimport {\n  RouteMatchCallback,\n  RouteMatchCallbackOptions,\n  WorkboxPlugin,\n} from 'workbox-core/types.js';\n\nimport './_version.js';\n\nexport interface StaticResourceOptions {\n  cacheName?: string;\n  matchCallback?: RouteMatchCallback;\n  plugins?: Array<WorkboxPlugin>;\n  warmCache?: Array<string>;\n}\n\n/**\n * An implementation of the [CSS and JavaScript files recipe]{@link https://developers.google.com/web/tools/workbox/guides/common-recipes#cache_css_and_javascript_files}\n *\n * @memberof workbox-recipes\n *\n * @param {Object} [options]\n * @param {string} [options.cacheName] Name for cache. Defaults to static-resources\n * @param {RouteMatchCallback} [options.matchCallback] Workbox callback function to call to match to. Defaults to request.destination === 'style' || request.destination === 'script' || request.destination === 'worker';\n * @param {WorkboxPlugin[]} [options.plugins] Additional plugins to use for this recipe\n * @param {string[]} [options.warmCache] Paths to call to use to warm this cache\n */\nfunction staticResourceCache(options: StaticResourceOptions = {}): void {\n  const defaultMatchCallback = ({request}: RouteMatchCallbackOptions) =>\n    request.destination === 'style' ||\n    request.destination === 'script' ||\n    request.destination === 'worker';\n\n  const cacheName = options.cacheName || 'static-resources';\n  const matchCallback = options.matchCallback || defaultMatchCallback;\n  const plugins = options.plugins || [];\n  plugins.push(\n    new CacheableResponsePlugin({\n      statuses: [0, 200],\n    }),\n  );\n\n  const strategy = new StaleWhileRevalidate({\n    cacheName,\n    plugins,\n  });\n\n  registerRoute(matchCallback, strategy);\n\n  // Warms the cache\n  if (options.warmCache) {\n    warmStrategyCache({urls: options.warmCache, strategy});\n  }\n}\n\nexport {staticResourceCache};\n"
  },
  {
    "path": "packages/workbox-recipes/src/warmStrategyCache.ts",
    "content": "import {Strategy} from 'workbox-strategies/Strategy.js';\n\nimport './_version.js';\n\nexport interface WarmStrategyCacheOptions {\n  urls: Array<string>;\n  strategy: Strategy;\n}\n\n// Give TypeScript the correct global.\ndeclare let self: ServiceWorkerGlobalScope;\n\n/**\n * @memberof workbox-recipes\n \n * @param {Object} options \n * @param {string[]} options.urls Paths to warm the strategy's cache with\n * @param {Strategy} options.strategy Strategy to use\n */\nfunction warmStrategyCache(options: WarmStrategyCacheOptions): void {\n  self.addEventListener('install', (event) => {\n    const done = options.urls.map(\n      (path) =>\n        options.strategy.handleAll({\n          event,\n          request: new Request(path),\n        })[1],\n    );\n\n    event.waitUntil(Promise.all(done));\n  });\n}\n\nexport {warmStrategyCache};\n"
  },
  {
    "path": "packages/workbox-recipes/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [\n    {\"path\": \"../workbox-core/\"},\n    {\"path\": \"../workbox-routing/\"},\n    {\"path\": \"../workbox-strategies/\"},\n    {\"path\": \"../workbox-cacheable-response/\"},\n    {\"path\": \"../workbox-expiration/\"},\n    {\"path\": \"../workbox-precaching/\"}\n  ]\n}\n"
  },
  {
    "path": "packages/workbox-routing/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-routing\n"
  },
  {
    "path": "packages/workbox-routing/package.json",
    "content": "{\n  \"name\": \"workbox-routing\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A service worker helper library to route request URLs to handlers.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"router\",\n    \"routing\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.routing\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-routing/src/NavigationRoute.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {RouteHandler, RouteMatchCallbackOptions} from 'workbox-core/types.js';\n\nimport {Route} from './Route.js';\n\nimport './_version.js';\n\nexport interface NavigationRouteMatchOptions {\n  allowlist?: RegExp[];\n  denylist?: RegExp[];\n}\n\n/**\n * NavigationRoute makes it easy to create a\n * {@link workbox-routing.Route} that matches for browser\n * [navigation requests]{@link https://developers.google.com/web/fundamentals/primers/service-workers/high-performance-loading#first_what_are_navigation_requests}.\n *\n * It will only match incoming Requests whose\n * {@link https://fetch.spec.whatwg.org/#concept-request-mode|mode}\n * is set to `navigate`.\n *\n * You can optionally only apply this route to a subset of navigation requests\n * by using one or both of the `denylist` and `allowlist` parameters.\n *\n * @memberof workbox-routing\n * @extends workbox-routing.Route\n */\nclass NavigationRoute extends Route {\n  private readonly _allowlist: RegExp[];\n  private readonly _denylist: RegExp[];\n\n  /**\n   * If both `denylist` and `allowlist` are provided, the `denylist` will\n   * take precedence and the request will not match this route.\n   *\n   * The regular expressions in `allowlist` and `denylist`\n   * are matched against the concatenated\n   * [`pathname`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/pathname}\n   * and [`search`]{@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/search}\n   * portions of the requested URL.\n   *\n   * *Note*: These RegExps may be evaluated against every destination URL during\n   * a navigation. Avoid using\n   * [complex RegExps](https://github.com/GoogleChrome/workbox/issues/3077),\n   * or else your users may see delays when navigating your site.\n   *\n   * @param {workbox-routing~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   * @param {Object} options\n   * @param {Array<RegExp>} [options.denylist] If any of these patterns match,\n   * the route will not handle the request (even if a allowlist RegExp matches).\n   * @param {Array<RegExp>} [options.allowlist=[/./]] If any of these patterns\n   * match the URL's pathname and search parameter, the route will handle the\n   * request (assuming the denylist doesn't match).\n   */\n  constructor(\n    handler: RouteHandler,\n    {allowlist = [/./], denylist = []}: NavigationRouteMatchOptions = {},\n  ) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isArrayOfClass(allowlist, RegExp, {\n        moduleName: 'workbox-routing',\n        className: 'NavigationRoute',\n        funcName: 'constructor',\n        paramName: 'options.allowlist',\n      });\n      assert!.isArrayOfClass(denylist, RegExp, {\n        moduleName: 'workbox-routing',\n        className: 'NavigationRoute',\n        funcName: 'constructor',\n        paramName: 'options.denylist',\n      });\n    }\n\n    super(\n      (options: RouteMatchCallbackOptions) => this._match(options),\n      handler,\n    );\n\n    this._allowlist = allowlist;\n    this._denylist = denylist;\n  }\n\n  /**\n   * Routes match handler.\n   *\n   * @param {Object} options\n   * @param {URL} options.url\n   * @param {Request} options.request\n   * @return {boolean}\n   *\n   * @private\n   */\n  private _match({url, request}: RouteMatchCallbackOptions): boolean {\n    if (request && request.mode !== 'navigate') {\n      return false;\n    }\n\n    const pathnameAndSearch = url.pathname + url.search;\n\n    for (const regExp of this._denylist) {\n      if (regExp.test(pathnameAndSearch)) {\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `The navigation route ${pathnameAndSearch} is not ` +\n              `being used, since the URL matches this denylist pattern: ` +\n              `${regExp.toString()}`,\n          );\n        }\n        return false;\n      }\n    }\n\n    if (this._allowlist.some((regExp) => regExp.test(pathnameAndSearch))) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.debug(\n          `The navigation route ${pathnameAndSearch} ` + `is being used.`,\n        );\n      }\n      return true;\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        `The navigation route ${pathnameAndSearch} is not ` +\n          `being used, since the URL being navigated to doesn't ` +\n          `match the allowlist.`,\n      );\n    }\n    return false;\n  }\n}\n\nexport {NavigationRoute};\n"
  },
  {
    "path": "packages/workbox-routing/src/RegExpRoute.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {\n  RouteHandler,\n  RouteMatchCallback,\n  RouteMatchCallbackOptions,\n} from 'workbox-core/types.js';\n\nimport {HTTPMethod} from './utils/constants.js';\nimport {Route} from './Route.js';\n\nimport './_version.js';\n\n/**\n * RegExpRoute makes it easy to create a regular expression based\n * {@link workbox-routing.Route}.\n *\n * For same-origin requests the RegExp only needs to match part of the URL. For\n * requests against third-party servers, you must define a RegExp that matches\n * the start of the URL.\n *\n * @memberof workbox-routing\n * @extends workbox-routing.Route\n */\nclass RegExpRoute extends Route {\n  /**\n   * If the regular expression contains\n   * [capture groups]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#grouping-back-references},\n   * the captured values will be passed to the\n   * {@link workbox-routing~handlerCallback} `params`\n   * argument.\n   *\n   * @param {RegExp} regExp The regular expression to match against URLs.\n   * @param {workbox-routing~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   * @param {string} [method='GET'] The HTTP method to match the Route\n   * against.\n   */\n  constructor(regExp: RegExp, handler: RouteHandler, method?: HTTPMethod) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(regExp, RegExp, {\n        moduleName: 'workbox-routing',\n        className: 'RegExpRoute',\n        funcName: 'constructor',\n        paramName: 'pattern',\n      });\n    }\n\n    const match: RouteMatchCallback = ({url}: RouteMatchCallbackOptions) => {\n      const result = regExp.exec(url.href);\n\n      // Return immediately if there's no match.\n      if (!result) {\n        return;\n      }\n\n      // Require that the match start at the first character in the URL string\n      // if it's a cross-origin request.\n      // See https://github.com/GoogleChrome/workbox/issues/281 for the context\n      // behind this behavior.\n      if (url.origin !== location.origin && result.index !== 0) {\n        if (process.env.NODE_ENV !== 'production') {\n          logger.debug(\n            `The regular expression '${regExp.toString()}' only partially matched ` +\n              `against the cross-origin URL '${url.toString()}'. RegExpRoute's will only ` +\n              `handle cross-origin requests if they match the entire URL.`,\n          );\n        }\n\n        return;\n      }\n\n      // If the route matches, but there aren't any capture groups defined, then\n      // this will return [], which is truthy and therefore sufficient to\n      // indicate a match.\n      // If there are capture groups, then it will return their values.\n      return result.slice(1);\n    };\n\n    super(match, handler, method);\n  }\n}\n\nexport {RegExpRoute};\n"
  },
  {
    "path": "packages/workbox-routing/src/Route.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {HTTPMethod, defaultMethod, validMethods} from './utils/constants.js';\nimport {normalizeHandler} from './utils/normalizeHandler.js';\nimport {\n  RouteHandler,\n  RouteHandlerObject,\n  RouteMatchCallback,\n} from 'workbox-core/types.js';\nimport './_version.js';\n\n/**\n * A `Route` consists of a pair of callback functions, \"match\" and \"handler\".\n * The \"match\" callback determine if a route should be used to \"handle\" a\n * request by returning a non-falsy value if it can. The \"handler\" callback\n * is called when there is a match and should return a Promise that resolves\n * to a `Response`.\n *\n * @memberof workbox-routing\n */\nclass Route {\n  handler: RouteHandlerObject;\n  match: RouteMatchCallback;\n  method: HTTPMethod;\n  catchHandler?: RouteHandlerObject;\n\n  /**\n   * Constructor for Route class.\n   *\n   * @param {workbox-routing~matchCallback} match\n   * A callback function that determines whether the route matches a given\n   * `fetch` event by returning a non-falsy value.\n   * @param {workbox-routing~handlerCallback} handler A callback\n   * function that returns a Promise resolving to a Response.\n   * @param {string} [method='GET'] The HTTP method to match the Route\n   * against.\n   */\n  constructor(\n    match: RouteMatchCallback,\n    handler: RouteHandler,\n    method: HTTPMethod = defaultMethod,\n  ) {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(match, 'function', {\n        moduleName: 'workbox-routing',\n        className: 'Route',\n        funcName: 'constructor',\n        paramName: 'match',\n      });\n\n      if (method) {\n        assert!.isOneOf(method, validMethods, {paramName: 'method'});\n      }\n    }\n\n    // These values are referenced directly by Router so cannot be\n    // altered by minificaton.\n    this.handler = normalizeHandler(handler);\n    this.match = match;\n    this.method = method;\n  }\n\n  /**\n   *\n   * @param {workbox-routing-handlerCallback} handler A callback\n   * function that returns a Promise resolving to a Response\n   */\n  setCatchHandler(handler: RouteHandler): void {\n    this.catchHandler = normalizeHandler(handler);\n  }\n}\n\nexport {Route};\n"
  },
  {
    "path": "packages/workbox-routing/src/Router.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {\n  RouteHandler,\n  RouteHandlerObject,\n  RouteHandlerCallbackOptions,\n  RouteMatchCallbackOptions,\n} from 'workbox-core/types.js';\nimport {HTTPMethod, defaultMethod} from './utils/constants.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {normalizeHandler} from './utils/normalizeHandler.js';\nimport {Route} from './Route.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport './_version.js';\n\ntype RequestArgs = string | [string, RequestInit?];\n\ninterface CacheURLsMessageData {\n  type: string;\n  payload: {\n    urlsToCache: RequestArgs[];\n  };\n}\n\n/**\n * The Router can be used to process a `FetchEvent` using one or more\n * {@link workbox-routing.Route}, responding with a `Response` if\n * a matching route exists.\n *\n * If no route matches a given a request, the Router will use a \"default\"\n * handler if one is defined.\n *\n * Should the matching Route throw an error, the Router will use a \"catch\"\n * handler if one is defined to gracefully deal with issues and respond with a\n * Request.\n *\n * If a request matches multiple routes, the **earliest** registered route will\n * be used to respond to the request.\n *\n * @memberof workbox-routing\n */\nclass Router {\n  private readonly _routes: Map<HTTPMethod, Route[]>;\n  private readonly _defaultHandlerMap: Map<HTTPMethod, RouteHandlerObject>;\n  private _catchHandler?: RouteHandlerObject;\n\n  /**\n   * Initializes a new Router.\n   */\n  constructor() {\n    this._routes = new Map();\n    this._defaultHandlerMap = new Map();\n  }\n\n  /**\n   * @return {Map<string, Array<workbox-routing.Route>>} routes A `Map` of HTTP\n   * method name ('GET', etc.) to an array of all the corresponding `Route`\n   * instances that are registered.\n   */\n  get routes(): Map<HTTPMethod, Route[]> {\n    return this._routes;\n  }\n\n  /**\n   * Adds a fetch event listener to respond to events when a route matches\n   * the event's request.\n   */\n  addFetchListener(): void {\n    // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n    self.addEventListener('fetch', ((event: FetchEvent) => {\n      const {request} = event;\n      const responsePromise = this.handleRequest({request, event});\n      if (responsePromise) {\n        event.respondWith(responsePromise);\n      }\n    }) as EventListener);\n  }\n\n  /**\n   * Adds a message event listener for URLs to cache from the window.\n   * This is useful to cache resources loaded on the page prior to when the\n   * service worker started controlling it.\n   *\n   * The format of the message data sent from the window should be as follows.\n   * Where the `urlsToCache` array may consist of URL strings or an array of\n   * URL string + `requestInit` object (the same as you'd pass to `fetch()`).\n   *\n   * ```\n   * {\n   *   type: 'CACHE_URLS',\n   *   payload: {\n   *     urlsToCache: [\n   *       './script1.js',\n   *       './script2.js',\n   *       ['./script3.js', {mode: 'no-cors'}],\n   *     ],\n   *   },\n   * }\n   * ```\n   */\n  addCacheListener(): void {\n    // See https://github.com/Microsoft/TypeScript/issues/28357#issuecomment-436484705\n    self.addEventListener('message', ((event: ExtendableMessageEvent) => {\n      // event.data is type 'any'\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n      if (event.data && event.data.type === 'CACHE_URLS') {\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        const {payload}: CacheURLsMessageData = event.data;\n\n        if (process.env.NODE_ENV !== 'production') {\n          logger.debug(`Caching URLs from the window`, payload.urlsToCache);\n        }\n\n        const requestPromises = Promise.all(\n          payload.urlsToCache.map((entry: string | [string, RequestInit?]) => {\n            if (typeof entry === 'string') {\n              entry = [entry];\n            }\n\n            const request = new Request(...entry);\n            return this.handleRequest({request, event});\n\n            // TODO(philipwalton): TypeScript errors without this typecast for\n            // some reason (probably a bug). The real type here should work but\n            // doesn't: `Array<Promise<Response> | undefined>`.\n          }) as any[],\n        ); // TypeScript\n\n        event.waitUntil(requestPromises);\n\n        // If a MessageChannel was used, reply to the message on success.\n        if (event.ports && event.ports[0]) {\n          void requestPromises.then(() => event.ports[0].postMessage(true));\n        }\n      }\n    }) as EventListener);\n  }\n\n  /**\n   * Apply the routing rules to a FetchEvent object to get a Response from an\n   * appropriate Route's handler.\n   *\n   * @param {Object} options\n   * @param {Request} options.request The request to handle.\n   * @param {ExtendableEvent} options.event The event that triggered the\n   *     request.\n   * @return {Promise<Response>|undefined} A promise is returned if a\n   *     registered route can handle the request. If there is no matching\n   *     route and there's no `defaultHandler`, `undefined` is returned.\n   */\n  handleRequest({\n    request,\n    event,\n  }: {\n    request: Request;\n    event: ExtendableEvent;\n  }): Promise<Response> | undefined {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'handleRequest',\n        paramName: 'options.request',\n      });\n    }\n\n    const url = new URL(request.url, location.href);\n    if (!url.protocol.startsWith('http')) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.debug(\n          `Workbox Router only supports URLs that start with 'http'.`,\n        );\n      }\n      return;\n    }\n\n    const sameOrigin = url.origin === location.origin;\n    const {params, route} = this.findMatchingRoute({\n      event,\n      request,\n      sameOrigin,\n      url,\n    });\n    let handler = route && route.handler;\n\n    const debugMessages = [];\n    if (process.env.NODE_ENV !== 'production') {\n      if (handler) {\n        debugMessages.push([`Found a route to handle this request:`, route]);\n\n        if (params) {\n          debugMessages.push([\n            `Passing the following params to the route's handler:`,\n            params,\n          ]);\n        }\n      }\n    }\n\n    // If we don't have a handler because there was no matching route, then\n    // fall back to defaultHandler if that's defined.\n    const method = request.method as HTTPMethod;\n    if (!handler && this._defaultHandlerMap.has(method)) {\n      if (process.env.NODE_ENV !== 'production') {\n        debugMessages.push(\n          `Failed to find a matching route. Falling ` +\n            `back to the default handler for ${method}.`,\n        );\n      }\n      handler = this._defaultHandlerMap.get(method);\n    }\n\n    if (!handler) {\n      if (process.env.NODE_ENV !== 'production') {\n        // No handler so Workbox will do nothing. If logs is set of debug\n        // i.e. verbose, we should print out this information.\n        logger.debug(`No route found for: ${getFriendlyURL(url)}`);\n      }\n      return;\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      // We have a handler, meaning Workbox is going to handle the route.\n      // print the routing details to the console.\n      logger.groupCollapsed(`Router is responding to: ${getFriendlyURL(url)}`);\n\n      debugMessages.forEach((msg) => {\n        if (Array.isArray(msg)) {\n          logger.log(...msg);\n        } else {\n          logger.log(msg);\n        }\n      });\n\n      logger.groupEnd();\n    }\n\n    // Wrap in try and catch in case the handle method throws a synchronous\n    // error. It should still callback to the catch handler.\n    let responsePromise;\n    try {\n      responsePromise = handler.handle({url, request, event, params});\n    } catch (err) {\n      responsePromise = Promise.reject(err);\n    }\n\n    // Get route's catch handler, if it exists\n    const catchHandler = route && route.catchHandler;\n\n    if (\n      responsePromise instanceof Promise &&\n      (this._catchHandler || catchHandler)\n    ) {\n      responsePromise = responsePromise.catch(async (err) => {\n        // If there's a route catch handler, process that first\n        if (catchHandler) {\n          if (process.env.NODE_ENV !== 'production') {\n            // Still include URL here as it will be async from the console group\n            // and may not make sense without the URL\n            logger.groupCollapsed(\n              `Error thrown when responding to: ` +\n                ` ${getFriendlyURL(\n                  url,\n                )}. Falling back to route's Catch Handler.`,\n            );\n            logger.error(`Error thrown by:`, route);\n            logger.error(err);\n            logger.groupEnd();\n          }\n\n          try {\n            return await catchHandler.handle({url, request, event, params});\n          } catch (catchErr) {\n            if (catchErr instanceof Error) {\n              err = catchErr;\n            }\n          }\n        }\n\n        if (this._catchHandler) {\n          if (process.env.NODE_ENV !== 'production') {\n            // Still include URL here as it will be async from the console group\n            // and may not make sense without the URL\n            logger.groupCollapsed(\n              `Error thrown when responding to: ` +\n                ` ${getFriendlyURL(\n                  url,\n                )}. Falling back to global Catch Handler.`,\n            );\n            logger.error(`Error thrown by:`, route);\n            logger.error(err);\n            logger.groupEnd();\n          }\n          return this._catchHandler.handle({url, request, event});\n        }\n\n        throw err;\n      });\n    }\n\n    return responsePromise;\n  }\n\n  /**\n   * Checks a request and URL (and optionally an event) against the list of\n   * registered routes, and if there's a match, returns the corresponding\n   * route along with any params generated by the match.\n   *\n   * @param {Object} options\n   * @param {URL} options.url\n   * @param {boolean} options.sameOrigin The result of comparing `url.origin`\n   *     against the current origin.\n   * @param {Request} options.request The request to match.\n   * @param {Event} options.event The corresponding event.\n   * @return {Object} An object with `route` and `params` properties.\n   *     They are populated if a matching route was found or `undefined`\n   *     otherwise.\n   */\n  findMatchingRoute({\n    url,\n    sameOrigin,\n    request,\n    event,\n  }: RouteMatchCallbackOptions): {\n    route?: Route;\n    params?: RouteHandlerCallbackOptions['params'];\n  } {\n    const routes = this._routes.get(request.method as HTTPMethod) || [];\n    for (const route of routes) {\n      let params: Promise<any> | undefined;\n      // route.match returns type any, not possible to change right now.\n      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n      const matchResult = route.match({url, sameOrigin, request, event});\n      if (matchResult) {\n        if (process.env.NODE_ENV !== 'production') {\n          // Warn developers that using an async matchCallback is almost always\n          // not the right thing to do.\n          if (matchResult instanceof Promise) {\n            logger.warn(\n              `While routing ${getFriendlyURL(url)}, an async ` +\n                `matchCallback function was used. Please convert the ` +\n                `following route to use a synchronous matchCallback function:`,\n              route,\n            );\n          }\n        }\n\n        // See https://github.com/GoogleChrome/workbox/issues/2079\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        params = matchResult;\n        if (Array.isArray(params) && params.length === 0) {\n          // Instead of passing an empty array in as params, use undefined.\n          params = undefined;\n        } else if (\n          matchResult.constructor === Object && // eslint-disable-line\n          Object.keys(matchResult).length === 0\n        ) {\n          // Instead of passing an empty object in as params, use undefined.\n          params = undefined;\n        } else if (typeof matchResult === 'boolean') {\n          // For the boolean value true (rather than just something truth-y),\n          // don't set params.\n          // See https://github.com/GoogleChrome/workbox/pull/2134#issuecomment-513924353\n          params = undefined;\n        }\n\n        // Return early if have a match.\n        return {route, params};\n      }\n    }\n    // If no match was found above, return and empty object.\n    return {};\n  }\n\n  /**\n   * Define a default `handler` that's called when no routes explicitly\n   * match the incoming request.\n   *\n   * Each HTTP method ('GET', 'POST', etc.) gets its own default handler.\n   *\n   * Without a default handler, unmatched requests will go against the\n   * network as if there were no service worker present.\n   *\n   * @param {workbox-routing~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   * @param {string} [method='GET'] The HTTP method to associate with this\n   * default handler. Each method has its own default.\n   */\n  setDefaultHandler(\n    handler: RouteHandler,\n    method: HTTPMethod = defaultMethod,\n  ): void {\n    this._defaultHandlerMap.set(method, normalizeHandler(handler));\n  }\n\n  /**\n   * If a Route throws an error while handling a request, this `handler`\n   * will be called and given a chance to provide a response.\n   *\n   * @param {workbox-routing~handlerCallback} handler A callback\n   * function that returns a Promise resulting in a Response.\n   */\n  setCatchHandler(handler: RouteHandler): void {\n    this._catchHandler = normalizeHandler(handler);\n  }\n\n  /**\n   * Registers a route with the router.\n   *\n   * @param {workbox-routing.Route} route The route to register.\n   */\n  registerRoute(route: Route): void {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(route, 'object', {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'registerRoute',\n        paramName: 'route',\n      });\n\n      assert!.hasMethod(route, 'match', {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'registerRoute',\n        paramName: 'route',\n      });\n\n      assert!.isType(route.handler, 'object', {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'registerRoute',\n        paramName: 'route',\n      });\n\n      assert!.hasMethod(route.handler, 'handle', {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'registerRoute',\n        paramName: 'route.handler',\n      });\n\n      assert!.isType(route.method, 'string', {\n        moduleName: 'workbox-routing',\n        className: 'Router',\n        funcName: 'registerRoute',\n        paramName: 'route.method',\n      });\n    }\n\n    if (!this._routes.has(route.method)) {\n      this._routes.set(route.method, []);\n    }\n\n    // Give precedence to all of the earlier routes by adding this additional\n    // route to the end of the array.\n    this._routes.get(route.method)!.push(route);\n  }\n\n  /**\n   * Unregisters a route with the router.\n   *\n   * @param {workbox-routing.Route} route The route to unregister.\n   */\n  unregisterRoute(route: Route): void {\n    if (!this._routes.has(route.method)) {\n      throw new WorkboxError('unregister-route-but-not-found-with-method', {\n        method: route.method,\n      });\n    }\n\n    const routeIndex = this._routes.get(route.method)!.indexOf(route);\n    if (routeIndex > -1) {\n      this._routes.get(route.method)!.splice(routeIndex, 1);\n    } else {\n      throw new WorkboxError('unregister-route-route-not-registered');\n    }\n  }\n}\n\nexport {Router};\n"
  },
  {
    "path": "packages/workbox-routing/src/_types.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n// * * * IMPORTANT! * * *\n// ------------------------------------------------------------------------- //\n// jdsoc type definitions cannot be declared above TypeScript definitions or\n// they'll be stripped from the built `.js` files, and they'll only be in the\n// `d.ts` files, which aren't read by the jsdoc generator. As a result we\n// have to put declare them below.\n\n/**\n * The \"match\" callback is used to determine if a `Route` should apply for a\n * particular URL. When matching occurs in response to a fetch event from the\n * client, the `event` object is supplied in addition to the `url`, `request`,\n * and `sameOrigin` value. However, since the match callback can be invoked\n * outside of a fetch event, matching logic should not assume the `event`\n * object will always be available.\n *\n * If the match callback returns a truthy value, the matching route's\n * {@link workbox-routing~handlerCallback} will be\n * invoked immediately. If the value returned is a non-empty array or object,\n * that value will be set on the handler's `context.params` argument.\n *\n * @callback ~matchCallback\n * @param {Object} context\n * @param {Request} context.request The corresponding request.\n * @param {URL} context.url The request's URL.\n * @param {ExtendableEvent} context.event The corresponding event that triggered\n *     the request.\n * @param {boolean} context.sameOrigin The result of comparing `url.origin`\n *     against the current origin.\n * @return {*} To signify a match, return a truthy value.\n *\n * @memberof workbox-routing\n */\n\n/**\n * The \"handler\" callback is invoked whenever a `Router` matches a URL to a\n * `Route` via its {@link workbox-routing~matchCallback}\n * callback. This callback should return a Promise that resolves with a\n * `Response`.\n *\n * If a non-empty array or object is returned by the\n * {@link workbox-routing~matchCallback} it\n * will be passed in as the handler's `context.params` argument.\n *\n * @callback ~handlerCallback\n * @param {Object} context\n * @param {Request|string} context.request The corresponding request.\n * @param {URL} context.url The URL that matched, if available.\n * @param {ExtendableEvent} context.event The corresponding event that triggered\n *     the request.\n * @param {Object} [context.params] Array or Object parameters returned by the\n *     Route's {@link workbox-routing~matchCallback}.\n *     This will be undefined if an empty array or object were returned.\n * @return {Promise<Response>} The response that will fulfill the request.\n *\n * @memberof workbox-routing\n */\n"
  },
  {
    "path": "packages/workbox-routing/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:routing:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-routing/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {\n  NavigationRoute,\n  NavigationRouteMatchOptions,\n} from './NavigationRoute.js';\nimport {RegExpRoute} from './RegExpRoute.js';\nimport {registerRoute} from './registerRoute.js';\nimport {Route} from './Route.js';\nimport {Router} from './Router.js';\nimport {setCatchHandler} from './setCatchHandler.js';\nimport {setDefaultHandler} from './setDefaultHandler.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-routing\n */\n\nexport {\n  NavigationRoute,\n  RegExpRoute,\n  registerRoute,\n  Route,\n  Router,\n  setCatchHandler,\n  setDefaultHandler,\n  NavigationRouteMatchOptions,\n};\n"
  },
  {
    "path": "packages/workbox-routing/src/registerRoute.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {RouteHandler, RouteMatchCallback} from 'workbox-core/types.js';\n\nimport {Route} from './Route.js';\nimport {RegExpRoute} from './RegExpRoute.js';\nimport {HTTPMethod} from './utils/constants.js';\nimport {getOrCreateDefaultRouter} from './utils/getOrCreateDefaultRouter.js';\n\nimport './_version.js';\n\n/**\n * Easily register a RegExp, string, or function with a caching\n * strategy to a singleton Router instance.\n *\n * This method will generate a Route for you if needed and\n * call {@link workbox-routing.Router#registerRoute}.\n *\n * @param {RegExp|string|workbox-routing.Route~matchCallback|workbox-routing.Route} capture\n * If the capture param is a `Route`, all other arguments will be ignored.\n * @param {workbox-routing~handlerCallback} [handler] A callback\n * function that returns a Promise resulting in a Response. This parameter\n * is required if `capture` is not a `Route` object.\n * @param {string} [method='GET'] The HTTP method to match the Route\n * against.\n * @return {workbox-routing.Route} The generated `Route`.\n *\n * @memberof workbox-routing\n */\nfunction registerRoute(\n  capture: RegExp | string | RouteMatchCallback | Route,\n  handler?: RouteHandler,\n  method?: HTTPMethod,\n): Route {\n  let route;\n\n  if (typeof capture === 'string') {\n    const captureUrl = new URL(capture, location.href);\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (!(capture.startsWith('/') || capture.startsWith('http'))) {\n        throw new WorkboxError('invalid-string', {\n          moduleName: 'workbox-routing',\n          funcName: 'registerRoute',\n          paramName: 'capture',\n        });\n      }\n\n      // We want to check if Express-style wildcards are in the pathname only.\n      // TODO: Remove this log message in v4.\n      const valueToCheck = capture.startsWith('http')\n        ? captureUrl.pathname\n        : capture;\n\n      // See https://github.com/pillarjs/path-to-regexp#parameters\n      const wildcards = '[*:?+]';\n      if (new RegExp(`${wildcards}`).exec(valueToCheck)) {\n        logger.debug(\n          `The '$capture' parameter contains an Express-style wildcard ` +\n            `character (${wildcards}). Strings are now always interpreted as ` +\n            `exact matches; use a RegExp for partial or wildcard matches.`,\n        );\n      }\n    }\n\n    const matchCallback: RouteMatchCallback = ({url}) => {\n      if (process.env.NODE_ENV !== 'production') {\n        if (\n          url.pathname === captureUrl.pathname &&\n          url.origin !== captureUrl.origin\n        ) {\n          logger.debug(\n            `${capture} only partially matches the cross-origin URL ` +\n              `${url.toString()}. This route will only handle cross-origin requests ` +\n              `if they match the entire URL.`,\n          );\n        }\n      }\n\n      return url.href === captureUrl.href;\n    };\n\n    // If `capture` is a string then `handler` and `method` must be present.\n    route = new Route(matchCallback, handler!, method);\n  } else if (capture instanceof RegExp) {\n    // If `capture` is a `RegExp` then `handler` and `method` must be present.\n    route = new RegExpRoute(capture, handler!, method);\n  } else if (typeof capture === 'function') {\n    // If `capture` is a function then `handler` and `method` must be present.\n    route = new Route(capture, handler!, method);\n  } else if (capture instanceof Route) {\n    route = capture;\n  } else {\n    throw new WorkboxError('unsupported-route-type', {\n      moduleName: 'workbox-routing',\n      funcName: 'registerRoute',\n      paramName: 'capture',\n    });\n  }\n\n  const defaultRouter = getOrCreateDefaultRouter();\n  defaultRouter.registerRoute(route);\n\n  return route;\n}\n\nexport {registerRoute};\n"
  },
  {
    "path": "packages/workbox-routing/src/setCatchHandler.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RouteHandler} from 'workbox-core/types.js';\n\nimport {getOrCreateDefaultRouter} from './utils/getOrCreateDefaultRouter.js';\n\nimport './_version.js';\n\n/**\n * If a Route throws an error while handling a request, this `handler`\n * will be called and given a chance to provide a response.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n *\n * @memberof workbox-routing\n */\nfunction setCatchHandler(handler: RouteHandler): void {\n  const defaultRouter = getOrCreateDefaultRouter();\n  defaultRouter.setCatchHandler(handler);\n}\n\nexport {setCatchHandler};\n"
  },
  {
    "path": "packages/workbox-routing/src/setDefaultHandler.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RouteHandler} from 'workbox-core/types.js';\n\nimport {getOrCreateDefaultRouter} from './utils/getOrCreateDefaultRouter.js';\n\nimport './_version.js';\n\n/**\n * Define a default `handler` that's called when no routes explicitly\n * match the incoming request.\n *\n * Without a default handler, unmatched requests will go against the\n * network as if there were no service worker present.\n *\n * @param {workbox-routing~handlerCallback} handler A callback\n * function that returns a Promise resulting in a Response.\n *\n * @memberof workbox-routing\n */\nfunction setDefaultHandler(handler: RouteHandler): void {\n  const defaultRouter = getOrCreateDefaultRouter();\n  defaultRouter.setDefaultHandler(handler);\n}\n\nexport {setDefaultHandler};\n"
  },
  {
    "path": "packages/workbox-routing/src/utils/constants.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\nexport type HTTPMethod = 'DELETE' | 'GET' | 'HEAD' | 'PATCH' | 'POST' | 'PUT';\n\n/**\n * The default HTTP method, 'GET', used when there's no specific method\n * configured for a route.\n *\n * @type {string}\n *\n * @private\n */\nexport const defaultMethod: HTTPMethod = 'GET';\n\n/**\n * The list of valid HTTP methods associated with requests that could be routed.\n *\n * @type {Array<string>}\n *\n * @private\n */\nexport const validMethods: HTTPMethod[] = [\n  'DELETE',\n  'GET',\n  'HEAD',\n  'PATCH',\n  'POST',\n  'PUT',\n];\n"
  },
  {
    "path": "packages/workbox-routing/src/utils/getOrCreateDefaultRouter.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Router} from '../Router.js';\nimport '../_version.js';\n\nlet defaultRouter: Router;\n\n/**\n * Creates a new, singleton Router instance if one does not exist. If one\n * does already exist, that instance is returned.\n *\n * @private\n * @return {Router}\n */\nexport const getOrCreateDefaultRouter = (): Router => {\n  if (!defaultRouter) {\n    defaultRouter = new Router();\n\n    // The helpers that use the default Router assume these listeners exist.\n    defaultRouter.addFetchListener();\n    defaultRouter.addCacheListener();\n  }\n  return defaultRouter;\n};\n"
  },
  {
    "path": "packages/workbox-routing/src/utils/normalizeHandler.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {RouteHandler, RouteHandlerObject} from 'workbox-core/types.js';\n\nimport '../_version.js';\n\n/**\n * @param {function()|Object} handler Either a function, or an object with a\n * 'handle' method.\n * @return {Object} An object with a handle method.\n *\n * @private\n */\nexport const normalizeHandler = (handler: RouteHandler): RouteHandlerObject => {\n  if (handler && typeof handler === 'object') {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.hasMethod(handler, 'handle', {\n        moduleName: 'workbox-routing',\n        className: 'Route',\n        funcName: 'constructor',\n        paramName: 'handler',\n      });\n    }\n    return handler;\n  } else {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isType(handler, 'function', {\n        moduleName: 'workbox-routing',\n        className: 'Route',\n        funcName: 'constructor',\n        paramName: 'handler',\n      });\n    }\n    return {handle: handler};\n  }\n};\n"
  },
  {
    "path": "packages/workbox-routing/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-strategies/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-strategies\n"
  },
  {
    "path": "packages/workbox-strategies/package.json",
    "content": "{\n  \"name\": \"workbox-strategies\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A service worker helper library implementing common caching strategies.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"router\",\n    \"routing\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.strategies\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-strategies/src/CacheFirst.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {Strategy} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport {messages} from './utils/messages.js';\nimport './_version.js';\n\n/**\n * An implementation of a [cache-first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-first-falling-back-to-network)\n * request strategy.\n *\n * A cache first strategy is useful for assets that have been revisioned,\n * such as URLs like `/styles/example.a8f5f1.css`, since they\n * can be cached for long periods of time.\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass CacheFirst extends Strategy {\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    const logs = [];\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-strategies',\n        className: this.constructor.name,\n        funcName: 'makeRequest',\n        paramName: 'request',\n      });\n    }\n\n    let response = await handler.cacheMatch(request);\n\n    let error: Error | undefined = undefined;\n    if (!response) {\n      if (process.env.NODE_ENV !== 'production') {\n        logs.push(\n          `No response found in the '${this.cacheName}' cache. ` +\n            `Will respond with a network request.`,\n        );\n      }\n      try {\n        response = await handler.fetchAndCachePut(request);\n      } catch (err) {\n        if (err instanceof Error) {\n          error = err;\n        }\n      }\n\n      if (process.env.NODE_ENV !== 'production') {\n        if (response) {\n          logs.push(`Got response from network.`);\n        } else {\n          logs.push(`Unable to get a response from the network.`);\n        }\n      }\n    } else {\n      if (process.env.NODE_ENV !== 'production') {\n        logs.push(`Found a cached response in the '${this.cacheName}' cache.`);\n      }\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.groupCollapsed(\n        messages.strategyStart(this.constructor.name, request),\n      );\n      for (const log of logs) {\n        logger.log(log);\n      }\n      messages.printFinalResponse(response);\n      logger.groupEnd();\n    }\n\n    if (!response) {\n      throw new WorkboxError('no-response', {url: request.url, error});\n    }\n    return response;\n  }\n}\n\nexport {CacheFirst};\n"
  },
  {
    "path": "packages/workbox-strategies/src/CacheOnly.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {Strategy} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport {messages} from './utils/messages.js';\nimport './_version.js';\n\n/**\n * An implementation of a [cache-only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#cache-only)\n * request strategy.\n *\n * This class is useful if you want to take advantage of any\n * [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/).\n *\n * If there is no cache match, this will throw a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass CacheOnly extends Strategy {\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-strategies',\n        className: this.constructor.name,\n        funcName: 'makeRequest',\n        paramName: 'request',\n      });\n    }\n\n    const response = await handler.cacheMatch(request);\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.groupCollapsed(\n        messages.strategyStart(this.constructor.name, request),\n      );\n      if (response) {\n        logger.log(\n          `Found a cached response in the '${this.cacheName}' ` + `cache.`,\n        );\n        messages.printFinalResponse(response);\n      } else {\n        logger.log(`No response found in the '${this.cacheName}' cache.`);\n      }\n      logger.groupEnd();\n    }\n\n    if (!response) {\n      throw new WorkboxError('no-response', {url: request.url});\n    }\n    return response;\n  }\n}\n\nexport {CacheOnly};\n"
  },
  {
    "path": "packages/workbox-strategies/src/NetworkFirst.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {cacheOkAndOpaquePlugin} from './plugins/cacheOkAndOpaquePlugin.js';\nimport {Strategy, StrategyOptions} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport {messages} from './utils/messages.js';\nimport './_version.js';\n\nexport interface NetworkFirstOptions extends StrategyOptions {\n  networkTimeoutSeconds?: number;\n}\n\n/**\n * An implementation of a\n * [network first](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-first-falling-back-to-cache)\n * request strategy.\n *\n * By default, this strategy will cache responses with a 200 status code as\n * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).\n * Opaque responses are are cross-origin requests where the response doesn't\n * support [CORS](https://enable-cors.org/).\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass NetworkFirst extends Strategy {\n  private readonly _networkTimeoutSeconds: number;\n\n  /**\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] Cache name to store and retrieve\n   * requests. Defaults to cache names provided by\n   * {@link workbox-core.cacheNames}.\n   * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n   * to use in conjunction with this caching strategy.\n   * @param {Object} [options.fetchOptions] Values passed along to the\n   * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n   * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n   * `fetch()` requests made by this strategy.\n   * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n   * @param {number} [options.networkTimeoutSeconds] If set, any network requests\n   * that fail to respond within the timeout will fallback to the cache.\n   *\n   * This option can be used to combat\n   * \"[lie-fi]{@link https://developers.google.com/web/fundamentals/performance/poor-connectivity/#lie-fi}\"\n   * scenarios.\n   */\n  constructor(options: NetworkFirstOptions = {}) {\n    super(options);\n\n    // If this instance contains no plugins with a 'cacheWillUpdate' callback,\n    // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.\n    if (!this.plugins.some((p) => 'cacheWillUpdate' in p)) {\n      this.plugins.unshift(cacheOkAndOpaquePlugin);\n    }\n\n    this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;\n    if (process.env.NODE_ENV !== 'production') {\n      if (this._networkTimeoutSeconds) {\n        assert!.isType(this._networkTimeoutSeconds, 'number', {\n          moduleName: 'workbox-strategies',\n          className: this.constructor.name,\n          funcName: 'constructor',\n          paramName: 'networkTimeoutSeconds',\n        });\n      }\n    }\n  }\n\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    const logs: any[] = [];\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-strategies',\n        className: this.constructor.name,\n        funcName: 'handle',\n        paramName: 'makeRequest',\n      });\n    }\n\n    const promises: Promise<Response | undefined>[] = [];\n    let timeoutId: number | undefined;\n\n    if (this._networkTimeoutSeconds) {\n      const {id, promise} = this._getTimeoutPromise({request, logs, handler});\n      timeoutId = id;\n      promises.push(promise);\n    }\n\n    const networkPromise = this._getNetworkPromise({\n      timeoutId,\n      request,\n      logs,\n      handler,\n    });\n\n    promises.push(networkPromise);\n\n    const response = await handler.waitUntil(\n      (async () => {\n        // Promise.race() will resolve as soon as the first promise resolves.\n        return (\n          (await handler.waitUntil(Promise.race(promises))) ||\n          // If Promise.race() resolved with null, it might be due to a network\n          // timeout + a cache miss. If that were to happen, we'd rather wait until\n          // the networkPromise resolves instead of returning null.\n          // Note that it's fine to await an already-resolved promise, so we don't\n          // have to check to see if it's still \"in flight\".\n          (await networkPromise)\n        );\n      })(),\n    );\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.groupCollapsed(\n        messages.strategyStart(this.constructor.name, request),\n      );\n      for (const log of logs) {\n        logger.log(log);\n      }\n      messages.printFinalResponse(response);\n      logger.groupEnd();\n    }\n\n    if (!response) {\n      throw new WorkboxError('no-response', {url: request.url});\n    }\n    return response;\n  }\n\n  /**\n   * @param {Object} options\n   * @param {Request} options.request\n   * @param {Array} options.logs A reference to the logs array\n   * @param {Event} options.event\n   * @return {Promise<Response>}\n   *\n   * @private\n   */\n  private _getTimeoutPromise({\n    request,\n    logs,\n    handler,\n  }: {\n    request: Request;\n    logs: any[];\n    handler: StrategyHandler;\n  }): {promise: Promise<Response | undefined>; id?: number} {\n    let timeoutId;\n    const timeoutPromise: Promise<Response | undefined> = new Promise(\n      (resolve) => {\n        const onNetworkTimeout = async () => {\n          if (process.env.NODE_ENV !== 'production') {\n            logs.push(\n              `Timing out the network response at ` +\n                `${this._networkTimeoutSeconds} seconds.`,\n            );\n          }\n          resolve(await handler.cacheMatch(request));\n        };\n        timeoutId = setTimeout(\n          onNetworkTimeout,\n          this._networkTimeoutSeconds * 1000,\n        );\n      },\n    );\n\n    return {\n      promise: timeoutPromise,\n      id: timeoutId,\n    };\n  }\n\n  /**\n   * @param {Object} options\n   * @param {number|undefined} options.timeoutId\n   * @param {Request} options.request\n   * @param {Array} options.logs A reference to the logs Array.\n   * @param {Event} options.event\n   * @return {Promise<Response>}\n   *\n   * @private\n   */\n  async _getNetworkPromise({\n    timeoutId,\n    request,\n    logs,\n    handler,\n  }: {\n    request: Request;\n    logs: any[];\n    timeoutId?: number;\n    handler: StrategyHandler;\n  }): Promise<Response | undefined> {\n    let error;\n    let response;\n    try {\n      response = await handler.fetchAndCachePut(request);\n    } catch (fetchError) {\n      if (fetchError instanceof Error) {\n        error = fetchError;\n      }\n    }\n\n    if (timeoutId) {\n      clearTimeout(timeoutId);\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (response) {\n        logs.push(`Got response from network.`);\n      } else {\n        logs.push(\n          `Unable to get a response from the network. Will respond ` +\n            `with a cached response.`,\n        );\n      }\n    }\n\n    if (error || !response) {\n      response = await handler.cacheMatch(request);\n\n      if (process.env.NODE_ENV !== 'production') {\n        if (response) {\n          logs.push(\n            `Found a cached response in the '${this.cacheName}'` + ` cache.`,\n          );\n        } else {\n          logs.push(`No response found in the '${this.cacheName}' cache.`);\n        }\n      }\n    }\n\n    return response;\n  }\n}\n\nexport {NetworkFirst};\n"
  },
  {
    "path": "packages/workbox-strategies/src/NetworkOnly.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {timeout} from 'workbox-core/_private/timeout.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {Strategy, StrategyOptions} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport {messages} from './utils/messages.js';\nimport './_version.js';\n\ninterface NetworkOnlyOptions\n  extends Omit<StrategyOptions, 'cacheName' | 'matchOptions'> {\n  networkTimeoutSeconds?: number;\n}\n\n/**\n * An implementation of a\n * [network-only](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#network-only)\n * request strategy.\n *\n * This class is useful if you want to take advantage of any\n * [Workbox plugins](https://developer.chrome.com/docs/workbox/using-plugins/).\n *\n * If the network request fails, this will throw a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass NetworkOnly extends Strategy {\n  private readonly _networkTimeoutSeconds: number;\n\n  /**\n   * @param {Object} [options]\n   * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n   * to use in conjunction with this caching strategy.\n   * @param {Object} [options.fetchOptions] Values passed along to the\n   * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n   * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n   * `fetch()` requests made by this strategy.\n   * @param {number} [options.networkTimeoutSeconds] If set, any network requests\n   * that fail to respond within the timeout will result in a network error.\n   */\n  constructor(options: NetworkOnlyOptions = {}) {\n    super(options);\n\n    this._networkTimeoutSeconds = options.networkTimeoutSeconds || 0;\n  }\n\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-strategies',\n        className: this.constructor.name,\n        funcName: '_handle',\n        paramName: 'request',\n      });\n    }\n\n    let error: Error | undefined = undefined;\n    let response: Response | undefined;\n\n    try {\n      const promises: Promise<Response | undefined>[] = [\n        handler.fetch(request),\n      ];\n\n      if (this._networkTimeoutSeconds) {\n        const timeoutPromise = timeout(\n          this._networkTimeoutSeconds * 1000,\n        ) as Promise<undefined>;\n        promises.push(timeoutPromise);\n      }\n\n      response = await Promise.race(promises);\n      if (!response) {\n        throw new Error(\n          `Timed out the network response after ` +\n            `${this._networkTimeoutSeconds} seconds.`,\n        );\n      }\n    } catch (err) {\n      if (err instanceof Error) {\n        error = err;\n      }\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.groupCollapsed(\n        messages.strategyStart(this.constructor.name, request),\n      );\n      if (response) {\n        logger.log(`Got response from network.`);\n      } else {\n        logger.log(`Unable to get a response from the network.`);\n      }\n      messages.printFinalResponse(response);\n      logger.groupEnd();\n    }\n\n    if (!response) {\n      throw new WorkboxError('no-response', {url: request.url, error});\n    }\n    return response;\n  }\n}\n\nexport {NetworkOnly, NetworkOnlyOptions};\n"
  },
  {
    "path": "packages/workbox-strategies/src/StaleWhileRevalidate.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport {cacheOkAndOpaquePlugin} from './plugins/cacheOkAndOpaquePlugin.js';\nimport {Strategy, StrategyOptions} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport {messages} from './utils/messages.js';\nimport './_version.js';\n\n/**\n * An implementation of a\n * [stale-while-revalidate](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#stale-while-revalidate)\n * request strategy.\n *\n * Resources are requested from both the cache and the network in parallel.\n * The strategy will respond with the cached version if available, otherwise\n * wait for the network response. The cache is updated with the network response\n * with each successful request.\n *\n * By default, this strategy will cache responses with a 200 status code as\n * well as [opaque responses](https://developer.chrome.com/docs/workbox/caching-resources-during-runtime/#opaque-responses).\n * Opaque responses are cross-origin requests where the response doesn't\n * support [CORS](https://enable-cors.org/).\n *\n * If the network request fails, and there is no cache match, this will throw\n * a `WorkboxError` exception.\n *\n * @extends workbox-strategies.Strategy\n * @memberof workbox-strategies\n */\nclass StaleWhileRevalidate extends Strategy {\n  /**\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] Cache name to store and retrieve\n   * requests. Defaults to cache names provided by\n   * {@link workbox-core.cacheNames}.\n   * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n   * to use in conjunction with this caching strategy.\n   * @param {Object} [options.fetchOptions] Values passed along to the\n   * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n   * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n   * `fetch()` requests made by this strategy.\n   * @param {Object} [options.matchOptions] [`CacheQueryOptions`](https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions)\n   */\n  constructor(options: StrategyOptions = {}) {\n    super(options);\n\n    // If this instance contains no plugins with a 'cacheWillUpdate' callback,\n    // prepend the `cacheOkAndOpaquePlugin` plugin to the plugins list.\n    if (!this.plugins.some((p) => 'cacheWillUpdate' in p)) {\n      this.plugins.unshift(cacheOkAndOpaquePlugin);\n    }\n  }\n\n  /**\n   * @private\n   * @param {Request|string} request A request to run this strategy for.\n   * @param {workbox-strategies.StrategyHandler} handler The event that\n   *     triggered the request.\n   * @return {Promise<Response>}\n   */\n  async _handle(request: Request, handler: StrategyHandler): Promise<Response> {\n    const logs = [];\n\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(request, Request, {\n        moduleName: 'workbox-strategies',\n        className: this.constructor.name,\n        funcName: 'handle',\n        paramName: 'request',\n      });\n    }\n\n    const fetchAndCachePromise = handler.fetchAndCachePut(request).catch(() => {\n      // Swallow this error because a 'no-response' error will be thrown in\n      // main handler return flow. This will be in the `waitUntil()` flow.\n    });\n    void handler.waitUntil(fetchAndCachePromise);\n\n    let response = await handler.cacheMatch(request);\n\n    let error;\n    if (response) {\n      if (process.env.NODE_ENV !== 'production') {\n        logs.push(\n          `Found a cached response in the '${this.cacheName}'` +\n            ` cache. Will update with the network response in the background.`,\n        );\n      }\n    } else {\n      if (process.env.NODE_ENV !== 'production') {\n        logs.push(\n          `No response found in the '${this.cacheName}' cache. ` +\n            `Will wait for the network response.`,\n        );\n      }\n      try {\n        // NOTE(philipwalton): Really annoying that we have to type cast here.\n        // https://github.com/microsoft/TypeScript/issues/20006\n        response = (await fetchAndCachePromise) as Response | undefined;\n      } catch (err) {\n        if (err instanceof Error) {\n          error = err;\n        }\n      }\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.groupCollapsed(\n        messages.strategyStart(this.constructor.name, request),\n      );\n      for (const log of logs) {\n        logger.log(log);\n      }\n      messages.printFinalResponse(response);\n      logger.groupEnd();\n    }\n\n    if (!response) {\n      throw new WorkboxError('no-response', {url: request.url, error});\n    }\n    return response;\n  }\n}\n\nexport {StaleWhileRevalidate};\n"
  },
  {
    "path": "packages/workbox-strategies/src/Strategy.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {\n  HandlerCallbackOptions,\n  RouteHandlerObject,\n  WorkboxPlugin,\n} from 'workbox-core/types.js';\n\nimport {StrategyHandler} from './StrategyHandler.js';\n\nimport './_version.js';\n\nexport interface StrategyOptions {\n  cacheName?: string;\n  plugins?: WorkboxPlugin[];\n  fetchOptions?: RequestInit;\n  matchOptions?: CacheQueryOptions;\n}\n\n/**\n * An abstract base class that all other strategy classes must extend from:\n *\n * @memberof workbox-strategies\n */\nabstract class Strategy implements RouteHandlerObject {\n  cacheName: string;\n  plugins: WorkboxPlugin[];\n  fetchOptions?: RequestInit;\n  matchOptions?: CacheQueryOptions;\n\n  protected abstract _handle(\n    request: Request,\n    handler: StrategyHandler,\n  ): Promise<Response | undefined>;\n\n  /**\n   * Creates a new instance of the strategy and sets all documented option\n   * properties as public instance properties.\n   *\n   * Note: if a custom strategy class extends the base Strategy class and does\n   * not need more than these properties, it does not need to define its own\n   * constructor.\n   *\n   * @param {Object} [options]\n   * @param {string} [options.cacheName] Cache name to store and retrieve\n   * requests. Defaults to the cache names provided by\n   * {@link workbox-core.cacheNames}.\n   * @param {Array<Object>} [options.plugins] [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n   * to use in conjunction with this caching strategy.\n   * @param {Object} [options.fetchOptions] Values passed along to the\n   * [`init`](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters)\n   * of [non-navigation](https://github.com/GoogleChrome/workbox/issues/1796)\n   * `fetch()` requests made by this strategy.\n   * @param {Object} [options.matchOptions] The\n   * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n   * for any `cache.match()` or `cache.put()` calls made by this strategy.\n   */\n  constructor(options: StrategyOptions = {}) {\n    /**\n     * Cache name to store and retrieve\n     * requests. Defaults to the cache names provided by\n     * {@link workbox-core.cacheNames}.\n     *\n     * @type {string}\n     */\n    this.cacheName = cacheNames.getRuntimeName(options.cacheName);\n    /**\n     * The list\n     * [Plugins]{@link https://developers.google.com/web/tools/workbox/guides/using-plugins}\n     * used by this strategy.\n     *\n     * @type {Array<Object>}\n     */\n    this.plugins = options.plugins || [];\n    /**\n     * Values passed along to the\n     * [`init`]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters}\n     * of all fetch() requests made by this strategy.\n     *\n     * @type {Object}\n     */\n    this.fetchOptions = options.fetchOptions;\n    /**\n     * The\n     * [`CacheQueryOptions`]{@link https://w3c.github.io/ServiceWorker/#dictdef-cachequeryoptions}\n     * for any `cache.match()` or `cache.put()` calls made by this strategy.\n     *\n     * @type {Object}\n     */\n    this.matchOptions = options.matchOptions;\n  }\n\n  /**\n   * Perform a request strategy and returns a `Promise` that will resolve with\n   * a `Response`, invoking all relevant plugin callbacks.\n   *\n   * When a strategy instance is registered with a Workbox\n   * {@link workbox-routing.Route}, this method is automatically\n   * called when the route matches.\n   *\n   * Alternatively, this method can be used in a standalone `FetchEvent`\n   * listener by passing it to `event.respondWith()`.\n   *\n   * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n   *     properties listed below.\n   * @param {Request|string} options.request A request to run this strategy for.\n   * @param {ExtendableEvent} options.event The event associated with the\n   *     request.\n   * @param {URL} [options.url]\n   * @param {*} [options.params]\n   */\n  handle(options: FetchEvent | HandlerCallbackOptions): Promise<Response> {\n    const [responseDone] = this.handleAll(options);\n    return responseDone;\n  }\n\n  /**\n   * Similar to {@link workbox-strategies.Strategy~handle}, but\n   * instead of just returning a `Promise` that resolves to a `Response` it\n   * it will return an tuple of `[response, done]` promises, where the former\n   * (`response`) is equivalent to what `handle()` returns, and the latter is a\n   * Promise that will resolve once any promises that were added to\n   * `event.waitUntil()` as part of performing the strategy have completed.\n   *\n   * You can await the `done` promise to ensure any extra work performed by\n   * the strategy (usually caching responses) completes successfully.\n   *\n   * @param {FetchEvent|Object} options A `FetchEvent` or an object with the\n   *     properties listed below.\n   * @param {Request|string} options.request A request to run this strategy for.\n   * @param {ExtendableEvent} options.event The event associated with the\n   *     request.\n   * @param {URL} [options.url]\n   * @param {*} [options.params]\n   * @return {Array<Promise>} A tuple of [response, done]\n   *     promises that can be used to determine when the response resolves as\n   *     well as when the handler has completed all its work.\n   */\n  handleAll(\n    options: FetchEvent | HandlerCallbackOptions,\n  ): [Promise<Response>, Promise<void>] {\n    // Allow for flexible options to be passed.\n    if (options instanceof FetchEvent) {\n      options = {\n        event: options,\n        request: options.request,\n      };\n    }\n\n    const event = options.event;\n    const request =\n      typeof options.request === 'string'\n        ? new Request(options.request)\n        : options.request;\n    const params = 'params' in options ? options.params : undefined;\n\n    const handler = new StrategyHandler(this, {event, request, params});\n\n    const responseDone = this._getResponse(handler, request, event);\n    const handlerDone = this._awaitComplete(\n      responseDone,\n      handler,\n      request,\n      event,\n    );\n\n    // Return an array of promises, suitable for use with Promise.all().\n    return [responseDone, handlerDone];\n  }\n\n  async _getResponse(\n    handler: StrategyHandler,\n    request: Request,\n    event: ExtendableEvent,\n  ): Promise<Response> {\n    await handler.runCallbacks('handlerWillStart', {event, request});\n\n    let response: Response | undefined = undefined;\n    try {\n      response = await this._handle(request, handler);\n      // The \"official\" Strategy subclasses all throw this error automatically,\n      // but in case a third-party Strategy doesn't, ensure that we have a\n      // consistent failure when there's no response or an error response.\n      if (!response || response.type === 'error') {\n        throw new WorkboxError('no-response', {url: request.url});\n      }\n    } catch (error) {\n      if (error instanceof Error) {\n        for (const callback of handler.iterateCallbacks('handlerDidError')) {\n          response = await callback({error, event, request});\n          if (response) {\n            break;\n          }\n        }\n      }\n\n      if (!response) {\n        throw error;\n      } else if (process.env.NODE_ENV !== 'production') {\n        logger.log(\n          `While responding to '${getFriendlyURL(request.url)}', ` +\n            `an ${\n              error instanceof Error ? error.toString() : ''\n            } error occurred. Using a fallback response provided by ` +\n            `a handlerDidError plugin.`,\n        );\n      }\n    }\n\n    for (const callback of handler.iterateCallbacks('handlerWillRespond')) {\n      response = await callback({event, request, response});\n    }\n\n    return response;\n  }\n\n  async _awaitComplete(\n    responseDone: Promise<Response>,\n    handler: StrategyHandler,\n    request: Request,\n    event: ExtendableEvent,\n  ): Promise<void> {\n    let response;\n    let error;\n\n    try {\n      response = await responseDone;\n    } catch (error) {\n      // Ignore errors, as response errors should be caught via the `response`\n      // promise above. The `done` promise will only throw for errors in\n      // promises passed to `handler.waitUntil()`.\n    }\n\n    try {\n      await handler.runCallbacks('handlerDidRespond', {\n        event,\n        request,\n        response,\n      });\n      await handler.doneWaiting();\n    } catch (waitUntilError) {\n      if (waitUntilError instanceof Error) {\n        error = waitUntilError;\n      }\n    }\n\n    await handler.runCallbacks('handlerDidComplete', {\n      event,\n      request,\n      response,\n      error: error as Error,\n    });\n    handler.destroy();\n\n    if (error) {\n      throw error;\n    }\n  }\n}\n\nexport {Strategy};\n\n/**\n * Classes extending the `Strategy` based class should implement this method,\n * and leverage the {@link workbox-strategies.StrategyHandler}\n * arg to perform all fetching and cache logic, which will ensure all relevant\n * cache, cache options, fetch options and plugins are used (per the current\n * strategy instance).\n *\n * @name _handle\n * @instance\n * @abstract\n * @function\n * @param {Request} request\n * @param {workbox-strategies.StrategyHandler} handler\n * @return {Promise<Response>}\n *\n * @memberof workbox-strategies.Strategy\n */\n"
  },
  {
    "path": "packages/workbox-strategies/src/StrategyHandler.ts",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {cacheMatchIgnoreParams} from 'workbox-core/_private/cacheMatchIgnoreParams.js';\nimport {Deferred} from 'workbox-core/_private/Deferred.js';\nimport {executeQuotaErrorCallbacks} from 'workbox-core/_private/executeQuotaErrorCallbacks.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {timeout} from 'workbox-core/_private/timeout.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\nimport {\n  HandlerCallbackOptions,\n  MapLikeObject,\n  WorkboxPlugin,\n  WorkboxPluginCallbackParam,\n} from 'workbox-core/types.js';\n\nimport {Strategy} from './Strategy.js';\nimport './_version.js';\n\nfunction toRequest(input: RequestInfo) {\n  return typeof input === 'string' ? new Request(input) : input;\n}\n\n/**\n * A class created every time a Strategy instance calls\n * {@link workbox-strategies.Strategy~handle} or\n * {@link workbox-strategies.Strategy~handleAll} that wraps all fetch and\n * cache actions around plugin callbacks and keeps track of when the strategy\n * is \"done\" (i.e. all added `event.waitUntil()` promises have resolved).\n *\n * @memberof workbox-strategies\n */\nclass StrategyHandler {\n  public request!: Request;\n  public url?: URL;\n  public event: ExtendableEvent;\n  public params?: any;\n\n  private _cacheKeys: Record<string, Request> = {};\n\n  private readonly _strategy: Strategy;\n  private readonly _extendLifetimePromises: Promise<any>[];\n  private readonly _handlerDeferred: Deferred<any>;\n  private readonly _plugins: WorkboxPlugin[];\n  private readonly _pluginStateMap: Map<WorkboxPlugin, MapLikeObject>;\n\n  /**\n   * Creates a new instance associated with the passed strategy and event\n   * that's handling the request.\n   *\n   * The constructor also initializes the state that will be passed to each of\n   * the plugins handling this request.\n   *\n   * @param {workbox-strategies.Strategy} strategy\n   * @param {Object} options\n   * @param {Request|string} options.request A request to run this strategy for.\n   * @param {ExtendableEvent} options.event The event associated with the\n   *     request.\n   * @param {URL} [options.url]\n   * @param {*} [options.params] The return value from the\n   *     {@link workbox-routing~matchCallback} (if applicable).\n   */\n  constructor(strategy: Strategy, options: HandlerCallbackOptions) {\n    /**\n     * The request the strategy is performing (passed to the strategy's\n     * `handle()` or `handleAll()` method).\n     * @name request\n     * @instance\n     * @type {Request}\n     * @memberof workbox-strategies.StrategyHandler\n     */\n    /**\n     * The event associated with this request.\n     * @name event\n     * @instance\n     * @type {ExtendableEvent}\n     * @memberof workbox-strategies.StrategyHandler\n     */\n    /**\n     * A `URL` instance of `request.url` (if passed to the strategy's\n     * `handle()` or `handleAll()` method).\n     * Note: the `url` param will be present if the strategy was invoked\n     * from a workbox `Route` object.\n     * @name url\n     * @instance\n     * @type {URL|undefined}\n     * @memberof workbox-strategies.StrategyHandler\n     */\n    /**\n     * A `param` value (if passed to the strategy's\n     * `handle()` or `handleAll()` method).\n     * Note: the `param` param will be present if the strategy was invoked\n     * from a workbox `Route` object and the\n     * {@link workbox-routing~matchCallback} returned\n     * a truthy value (it will be that value).\n     * @name params\n     * @instance\n     * @type {*|undefined}\n     * @memberof workbox-strategies.StrategyHandler\n     */\n    if (process.env.NODE_ENV !== 'production') {\n      assert!.isInstance(options.event, ExtendableEvent, {\n        moduleName: 'workbox-strategies',\n        className: 'StrategyHandler',\n        funcName: 'constructor',\n        paramName: 'options.event',\n      });\n    }\n\n    Object.assign(this, options);\n\n    this.event = options.event;\n    this._strategy = strategy;\n    this._handlerDeferred = new Deferred();\n    this._extendLifetimePromises = [];\n\n    // Copy the plugins list (since it's mutable on the strategy),\n    // so any mutations don't affect this handler instance.\n    this._plugins = [...strategy.plugins];\n    this._pluginStateMap = new Map();\n    for (const plugin of this._plugins) {\n      this._pluginStateMap.set(plugin, {});\n    }\n\n    this.event.waitUntil(this._handlerDeferred.promise);\n  }\n\n  /**\n   * Fetches a given request (and invokes any applicable plugin callback\n   * methods) using the `fetchOptions` (for non-navigation requests) and\n   * `plugins` defined on the `Strategy` object.\n   *\n   * The following plugin lifecycle methods are invoked when using this method:\n   * - `requestWillFetch()`\n   * - `fetchDidSucceed()`\n   * - `fetchDidFail()`\n   *\n   * @param {Request|string} input The URL or request to fetch.\n   * @return {Promise<Response>}\n   */\n  async fetch(input: RequestInfo): Promise<Response> {\n    const {event} = this;\n    let request: Request = toRequest(input);\n\n    if (\n      request.mode === 'navigate' &&\n      event instanceof FetchEvent &&\n      event.preloadResponse\n    ) {\n      const possiblePreloadResponse = (await event.preloadResponse) as\n        | Response\n        | undefined;\n      if (possiblePreloadResponse) {\n        if (process.env.NODE_ENV !== 'production') {\n          logger.log(\n            `Using a preloaded navigation response for ` +\n              `'${getFriendlyURL(request.url)}'`,\n          );\n        }\n        return possiblePreloadResponse;\n      }\n    }\n\n    // If there is a fetchDidFail plugin, we need to save a clone of the\n    // original request before it's either modified by a requestWillFetch\n    // plugin or before the original request's body is consumed via fetch().\n    const originalRequest = this.hasCallback('fetchDidFail')\n      ? request.clone()\n      : null;\n\n    try {\n      for (const cb of this.iterateCallbacks('requestWillFetch')) {\n        request = await cb({request: request.clone(), event});\n      }\n    } catch (err) {\n      if (err instanceof Error) {\n        throw new WorkboxError('plugin-error-request-will-fetch', {\n          thrownErrorMessage: err.message,\n        });\n      }\n    }\n\n    // The request can be altered by plugins with `requestWillFetch` making\n    // the original request (most likely from a `fetch` event) different\n    // from the Request we make. Pass both to `fetchDidFail` to aid debugging.\n    const pluginFilteredRequest: Request = request.clone();\n\n    try {\n      let fetchResponse: Response;\n\n      // See https://github.com/GoogleChrome/workbox/issues/1796\n      fetchResponse = await fetch(\n        request,\n        request.mode === 'navigate' ? undefined : this._strategy.fetchOptions,\n      );\n\n      if (process.env.NODE_ENV !== 'production') {\n        logger.debug(\n          `Network request for ` +\n            `'${getFriendlyURL(request.url)}' returned a response with ` +\n            `status '${fetchResponse.status}'.`,\n        );\n      }\n\n      for (const callback of this.iterateCallbacks('fetchDidSucceed')) {\n        fetchResponse = await callback({\n          event,\n          request: pluginFilteredRequest,\n          response: fetchResponse,\n        });\n      }\n      return fetchResponse;\n    } catch (error) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.log(\n          `Network request for ` +\n            `'${getFriendlyURL(request.url)}' threw an error.`,\n          error,\n        );\n      }\n\n      // `originalRequest` will only exist if a `fetchDidFail` callback\n      // is being used (see above).\n      if (originalRequest) {\n        await this.runCallbacks('fetchDidFail', {\n          error: error as Error,\n          event,\n          originalRequest: originalRequest.clone(),\n          request: pluginFilteredRequest.clone(),\n        });\n      }\n      throw error;\n    }\n  }\n\n  /**\n   * Calls `this.fetch()` and (in the background) runs `this.cachePut()` on\n   * the response generated by `this.fetch()`.\n   *\n   * The call to `this.cachePut()` automatically invokes `this.waitUntil()`,\n   * so you do not have to manually call `waitUntil()` on the event.\n   *\n   * @param {Request|string} input The request or URL to fetch and cache.\n   * @return {Promise<Response>}\n   */\n  async fetchAndCachePut(input: RequestInfo): Promise<Response> {\n    const response = await this.fetch(input);\n    const responseClone = response.clone();\n\n    void this.waitUntil(this.cachePut(input, responseClone));\n\n    return response;\n  }\n\n  /**\n   * Matches a request from the cache (and invokes any applicable plugin\n   * callback methods) using the `cacheName`, `matchOptions`, and `plugins`\n   * defined on the strategy object.\n   *\n   * The following plugin lifecycle methods are invoked when using this method:\n   * - cacheKeyWillBeUsed()\n   * - cachedResponseWillBeUsed()\n   *\n   * @param {Request|string} key The Request or URL to use as the cache key.\n   * @return {Promise<Response|undefined>} A matching response, if found.\n   */\n  async cacheMatch(key: RequestInfo): Promise<Response | undefined> {\n    const request: Request = toRequest(key);\n    let cachedResponse: Response | undefined;\n    const {cacheName, matchOptions} = this._strategy;\n\n    const effectiveRequest = await this.getCacheKey(request, 'read');\n    const multiMatchOptions = {...matchOptions, ...{cacheName}};\n\n    cachedResponse = await caches.match(effectiveRequest, multiMatchOptions);\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (cachedResponse) {\n        logger.debug(`Found a cached response in '${cacheName}'.`);\n      } else {\n        logger.debug(`No cached response found in '${cacheName}'.`);\n      }\n    }\n\n    for (const callback of this.iterateCallbacks('cachedResponseWillBeUsed')) {\n      cachedResponse =\n        (await callback({\n          cacheName,\n          matchOptions,\n          cachedResponse,\n          request: effectiveRequest,\n          event: this.event,\n        })) || undefined;\n    }\n    return cachedResponse;\n  }\n\n  /**\n   * Puts a request/response pair in the cache (and invokes any applicable\n   * plugin callback methods) using the `cacheName` and `plugins` defined on\n   * the strategy object.\n   *\n   * The following plugin lifecycle methods are invoked when using this method:\n   * - cacheKeyWillBeUsed()\n   * - cacheWillUpdate()\n   * - cacheDidUpdate()\n   *\n   * @param {Request|string} key The request or URL to use as the cache key.\n   * @param {Response} response The response to cache.\n   * @return {Promise<boolean>} `false` if a cacheWillUpdate caused the response\n   * not be cached, and `true` otherwise.\n   */\n  async cachePut(key: RequestInfo, response: Response): Promise<boolean> {\n    const request: Request = toRequest(key);\n\n    // Run in the next task to avoid blocking other cache reads.\n    // https://github.com/w3c/ServiceWorker/issues/1397\n    await timeout(0);\n\n    const effectiveRequest = await this.getCacheKey(request, 'write');\n\n    if (process.env.NODE_ENV !== 'production') {\n      if (effectiveRequest.method && effectiveRequest.method !== 'GET') {\n        throw new WorkboxError('attempt-to-cache-non-get-request', {\n          url: getFriendlyURL(effectiveRequest.url),\n          method: effectiveRequest.method,\n        });\n      }\n\n      // See https://github.com/GoogleChrome/workbox/issues/2818\n      const vary = response.headers.get('Vary');\n      if (vary) {\n        logger.debug(\n          `The response for ${getFriendlyURL(effectiveRequest.url)} ` +\n            `has a 'Vary: ${vary}' header. ` +\n            `Consider setting the {ignoreVary: true} option on your strategy ` +\n            `to ensure cache matching and deletion works as expected.`,\n        );\n      }\n    }\n\n    if (!response) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.error(\n          `Cannot cache non-existent response for ` +\n            `'${getFriendlyURL(effectiveRequest.url)}'.`,\n        );\n      }\n\n      throw new WorkboxError('cache-put-with-no-response', {\n        url: getFriendlyURL(effectiveRequest.url),\n      });\n    }\n\n    const responseToCache = await this._ensureResponseSafeToCache(response);\n\n    if (!responseToCache) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.debug(\n          `Response '${getFriendlyURL(effectiveRequest.url)}' ` +\n            `will not be cached.`,\n          responseToCache,\n        );\n      }\n      return false;\n    }\n\n    const {cacheName, matchOptions} = this._strategy;\n    const cache = await self.caches.open(cacheName);\n\n    const hasCacheUpdateCallback = this.hasCallback('cacheDidUpdate');\n    const oldResponse = hasCacheUpdateCallback\n      ? await cacheMatchIgnoreParams(\n          // TODO(philipwalton): the `__WB_REVISION__` param is a precaching\n          // feature. Consider into ways to only add this behavior if using\n          // precaching.\n          cache,\n          effectiveRequest.clone(),\n          ['__WB_REVISION__'],\n          matchOptions,\n        )\n      : null;\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.debug(\n        `Updating the '${cacheName}' cache with a new Response ` +\n          `for ${getFriendlyURL(effectiveRequest.url)}.`,\n      );\n    }\n\n    try {\n      await cache.put(\n        effectiveRequest,\n        hasCacheUpdateCallback ? responseToCache.clone() : responseToCache,\n      );\n    } catch (error) {\n      if (error instanceof Error) {\n        // See https://developer.mozilla.org/en-US/docs/Web/API/DOMException#exception-QuotaExceededError\n        if (error.name === 'QuotaExceededError') {\n          await executeQuotaErrorCallbacks();\n        }\n        throw error;\n      }\n    }\n\n    for (const callback of this.iterateCallbacks('cacheDidUpdate')) {\n      await callback({\n        cacheName,\n        oldResponse,\n        newResponse: responseToCache.clone(),\n        request: effectiveRequest,\n        event: this.event,\n      });\n    }\n\n    return true;\n  }\n\n  /**\n   * Checks the list of plugins for the `cacheKeyWillBeUsed` callback, and\n   * executes any of those callbacks found in sequence. The final `Request`\n   * object returned by the last plugin is treated as the cache key for cache\n   * reads and/or writes. If no `cacheKeyWillBeUsed` plugin callbacks have\n   * been registered, the passed request is returned unmodified\n   *\n   * @param {Request} request\n   * @param {string} mode\n   * @return {Promise<Request>}\n   */\n  async getCacheKey(\n    request: Request,\n    mode: 'read' | 'write',\n  ): Promise<Request> {\n    const key = `${request.url} | ${mode}`;\n    if (!this._cacheKeys[key]) {\n      let effectiveRequest = request;\n\n      for (const callback of this.iterateCallbacks('cacheKeyWillBeUsed')) {\n        effectiveRequest = toRequest(\n          await callback({\n            mode,\n            request: effectiveRequest,\n            event: this.event,\n            // params has a type any can't change right now.\n            params: this.params, // eslint-disable-line\n          }),\n        );\n      }\n\n      this._cacheKeys[key] = effectiveRequest;\n    }\n    return this._cacheKeys[key];\n  }\n\n  /**\n   * Returns true if the strategy has at least one plugin with the given\n   * callback.\n   *\n   * @param {string} name The name of the callback to check for.\n   * @return {boolean}\n   */\n  hasCallback<C extends keyof WorkboxPlugin>(name: C): boolean {\n    for (const plugin of this._strategy.plugins) {\n      if (name in plugin) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  /**\n   * Runs all plugin callbacks matching the given name, in order, passing the\n   * given param object (merged ith the current plugin state) as the only\n   * argument.\n   *\n   * Note: since this method runs all plugins, it's not suitable for cases\n   * where the return value of a callback needs to be applied prior to calling\n   * the next callback. See\n   * {@link workbox-strategies.StrategyHandler#iterateCallbacks}\n   * below for how to handle that case.\n   *\n   * @param {string} name The name of the callback to run within each plugin.\n   * @param {Object} param The object to pass as the first (and only) param\n   *     when executing each callback. This object will be merged with the\n   *     current plugin state prior to callback execution.\n   */\n  async runCallbacks<C extends keyof NonNullable<WorkboxPlugin>>(\n    name: C,\n    param: Omit<WorkboxPluginCallbackParam[C], 'state'>,\n  ): Promise<void> {\n    for (const callback of this.iterateCallbacks(name)) {\n      // TODO(philipwalton): not sure why `any` is needed. It seems like\n      // this should work with `as WorkboxPluginCallbackParam[C]`.\n      await callback(param as any);\n    }\n  }\n\n  /**\n   * Accepts a callback and returns an iterable of matching plugin callbacks,\n   * where each callback is wrapped with the current handler state (i.e. when\n   * you call each callback, whatever object parameter you pass it will\n   * be merged with the plugin's current state).\n   *\n   * @param {string} name The name fo the callback to run\n   * @return {Array<Function>}\n   */\n  *iterateCallbacks<C extends keyof WorkboxPlugin>(\n    name: C,\n  ): Generator<NonNullable<WorkboxPlugin[C]>> {\n    for (const plugin of this._strategy.plugins) {\n      if (typeof plugin[name] === 'function') {\n        const state = this._pluginStateMap.get(plugin);\n        const statefulCallback = (\n          param: Omit<WorkboxPluginCallbackParam[C], 'state'>,\n        ) => {\n          const statefulParam = {...param, state};\n\n          // TODO(philipwalton): not sure why `any` is needed. It seems like\n          // this should work with `as WorkboxPluginCallbackParam[C]`.\n          return plugin[name]!(statefulParam as any);\n        };\n        yield statefulCallback as NonNullable<WorkboxPlugin[C]>;\n      }\n    }\n  }\n\n  /**\n   * Adds a promise to the\n   * [extend lifetime promises]{@link https://w3c.github.io/ServiceWorker/#extendableevent-extend-lifetime-promises}\n   * of the event associated with the request being handled (usually a\n   * `FetchEvent`).\n   *\n   * Note: you can await\n   * {@link workbox-strategies.StrategyHandler~doneWaiting}\n   * to know when all added promises have settled.\n   *\n   * @param {Promise} promise A promise to add to the extend lifetime promises\n   *     of the event that triggered the request.\n   */\n  waitUntil<T>(promise: Promise<T>): Promise<T> {\n    this._extendLifetimePromises.push(promise);\n    return promise;\n  }\n\n  /**\n   * Returns a promise that resolves once all promises passed to\n   * {@link workbox-strategies.StrategyHandler~waitUntil}\n   * have settled.\n   *\n   * Note: any work done after `doneWaiting()` settles should be manually\n   * passed to an event's `waitUntil()` method (not this handler's\n   * `waitUntil()` method), otherwise the service worker thread may be killed\n   * prior to your work completing.\n   */\n  async doneWaiting(): Promise<void> {\n    while (this._extendLifetimePromises.length) {\n      const promises = this._extendLifetimePromises.splice(0);\n      const result = await Promise.allSettled(promises);\n      const firstRejection = result.find((i) => i.status === 'rejected');\n      if (firstRejection) {\n        throw (firstRejection as PromiseRejectedResult).reason;\n      }\n    }\n  }\n\n  /**\n   * Stops running the strategy and immediately resolves any pending\n   * `waitUntil()` promises.\n   */\n  destroy(): void {\n    this._handlerDeferred.resolve(null);\n  }\n\n  /**\n   * This method will call cacheWillUpdate on the available plugins (or use\n   * status === 200) to determine if the Response is safe and valid to cache.\n   *\n   * @param {Request} options.request\n   * @param {Response} options.response\n   * @return {Promise<Response|undefined>}\n   *\n   * @private\n   */\n  async _ensureResponseSafeToCache(\n    response: Response,\n  ): Promise<Response | undefined> {\n    let responseToCache: Response | undefined = response;\n    let pluginsUsed = false;\n\n    for (const callback of this.iterateCallbacks('cacheWillUpdate')) {\n      responseToCache =\n        (await callback({\n          request: this.request,\n          response: responseToCache,\n          event: this.event,\n        })) || undefined;\n      pluginsUsed = true;\n\n      if (!responseToCache) {\n        break;\n      }\n    }\n\n    if (!pluginsUsed) {\n      if (responseToCache && responseToCache.status !== 200) {\n        responseToCache = undefined;\n      }\n      if (process.env.NODE_ENV !== 'production') {\n        if (responseToCache) {\n          if (responseToCache.status !== 200) {\n            if (responseToCache.status === 0) {\n              logger.warn(\n                `The response for '${this.request.url}' ` +\n                  `is an opaque response. The caching strategy that you're ` +\n                  `using will not cache opaque responses by default.`,\n              );\n            } else {\n              logger.debug(\n                `The response for '${this.request.url}' ` +\n                  `returned a status code of '${response.status}' and won't ` +\n                  `be cached as a result.`,\n              );\n            }\n          }\n        }\n      }\n    }\n\n    return responseToCache;\n  }\n}\n\nexport {StrategyHandler};\n"
  },
  {
    "path": "packages/workbox-strategies/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:strategies:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-strategies/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheFirst} from './CacheFirst.js';\nimport {CacheOnly} from './CacheOnly.js';\nimport {NetworkFirst, NetworkFirstOptions} from './NetworkFirst.js';\nimport {NetworkOnly, NetworkOnlyOptions} from './NetworkOnly.js';\nimport {StaleWhileRevalidate} from './StaleWhileRevalidate.js';\nimport {Strategy, StrategyOptions} from './Strategy.js';\nimport {StrategyHandler} from './StrategyHandler.js';\nimport './_version.js';\n\n// See https://github.com/GoogleChrome/workbox/issues/2946\ndeclare global {\n  interface FetchEvent {\n    // See https://github.com/GoogleChrome/workbox/issues/2974\n    readonly preloadResponse: Promise<any>;\n  }\n}\n\n/**\n * There are common caching strategies that most service workers will need\n * and use. This module provides simple implementations of these strategies.\n *\n * @module workbox-strategies\n */\n\nexport {\n  CacheFirst,\n  CacheOnly,\n  NetworkFirst,\n  NetworkFirstOptions,\n  NetworkOnly,\n  NetworkOnlyOptions,\n  StaleWhileRevalidate,\n  Strategy,\n  StrategyHandler,\n  StrategyOptions,\n};\n"
  },
  {
    "path": "packages/workbox-strategies/src/plugins/cacheOkAndOpaquePlugin.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxPlugin} from 'workbox-core/types.js';\nimport '../_version.js';\n\nexport const cacheOkAndOpaquePlugin: WorkboxPlugin = {\n  /**\n   * Returns a valid response (to allow caching) if the status is 200 (OK) or\n   * 0 (opaque).\n   *\n   * @param {Object} options\n   * @param {Response} options.response\n   * @return {Response|null}\n   *\n   * @private\n   */\n  cacheWillUpdate: async ({response}) => {\n    if (response.status === 200 || response.status === 0) {\n      return response;\n    }\n    return null;\n  },\n};\n"
  },
  {
    "path": "packages/workbox-strategies/src/utils/messages.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.js';\nimport '../_version.js';\n\nexport const messages = {\n  strategyStart: (strategyName: string, request: Request): string =>\n    `Using ${strategyName} to respond to '${getFriendlyURL(request.url)}'`,\n  printFinalResponse: (response?: Response): void => {\n    if (response) {\n      logger.groupCollapsed(`View the final response here.`);\n      logger.log(response || '[No response returned]');\n      logger.groupEnd();\n    }\n  },\n};\n"
  },
  {
    "path": "packages/workbox-strategies/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-streams/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/reference-docs/latest/module-workbox-streams\n"
  },
  {
    "path": "packages/workbox-streams/package.json",
    "content": "{\n  \"name\": \"workbox-streams\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"A library that makes it easier to work with Streams in the browser.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"streams\",\n    \"readablestream\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox.streams\",\n    \"packageType\": \"sw\"\n  },\n  \"main\": \"index.js\",\n  \"module\": \"index.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"workbox-core\": \"7.4.0\",\n    \"workbox-routing\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-streams/src/_types.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\nexport type StreamSource = Response | ReadableStream | BodyInit;\n\n// * * * IMPORTANT! * * *\n// ------------------------------------------------------------------------- //\n// jdsoc type definitions cannot be declared above TypeScript definitions or\n// they'll be stripped from the built `.js` files, and they'll only be in the\n// `d.ts` files, which aren't read by the jsdoc generator. As a result we\n// have to put declare them below.\n\n/**\n * @typedef {Response|ReadableStream|BodyInit} StreamSource\n * @memberof workbox-streams\n */\n"
  },
  {
    "path": "packages/workbox-streams/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:streams:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-streams/src/concatenate.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert.js';\nimport {Deferred} from 'workbox-core/_private/Deferred.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {StreamSource} from './_types.js';\nimport {WorkboxError} from 'workbox-core/_private/WorkboxError.js';\n\nimport './_version.js';\n\n/**\n * Takes either a Response, a ReadableStream, or a\n * [BodyInit](https://fetch.spec.whatwg.org/#bodyinit) and returns the\n * ReadableStreamReader object associated with it.\n *\n * @param {workbox-streams.StreamSource} source\n * @return {ReadableStreamReader}\n * @private\n */\nfunction _getReaderFromSource(\n  source: StreamSource,\n): ReadableStreamReader<unknown> {\n  if (source instanceof Response) {\n    // See https://github.com/GoogleChrome/workbox/issues/2998\n    if (source.body) {\n      return source.body.getReader();\n    }\n    throw new WorkboxError('opaque-streams-source', {type: source.type});\n  }\n  if (source instanceof ReadableStream) {\n    return source.getReader();\n  }\n  return new Response(source as BodyInit).body!.getReader();\n}\n\n/**\n * Takes multiple source Promises, each of which could resolve to a Response, a\n * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit).\n *\n * Returns an object exposing a ReadableStream with each individual stream's\n * data returned in sequence, along with a Promise which signals when the\n * stream is finished (useful for passing to a FetchEvent's waitUntil()).\n *\n * @param {Array<Promise<workbox-streams.StreamSource>>} sourcePromises\n * @return {Object<{done: Promise, stream: ReadableStream}>}\n *\n * @memberof workbox-streams\n */\nfunction concatenate(sourcePromises: Promise<StreamSource>[]): {\n  done: Promise<void>;\n  stream: ReadableStream;\n} {\n  if (process.env.NODE_ENV !== 'production') {\n    assert!.isArray(sourcePromises, {\n      moduleName: 'workbox-streams',\n      funcName: 'concatenate',\n      paramName: 'sourcePromises',\n    });\n  }\n\n  const readerPromises = sourcePromises.map((sourcePromise) => {\n    return Promise.resolve(sourcePromise).then((source) => {\n      return _getReaderFromSource(source);\n    });\n  });\n\n  const streamDeferred: Deferred<void> = new Deferred();\n\n  let i = 0;\n  const logMessages: any[] = [];\n  const stream = new ReadableStream({\n    pull(controller: ReadableStreamDefaultController<any>) {\n      return readerPromises[i]\n        .then((reader) => {\n          if (reader instanceof ReadableStreamDefaultReader) {\n            return reader.read();\n          } else {\n            return;\n          }\n        })\n        .then((result) => {\n          if (result?.done) {\n            if (process.env.NODE_ENV !== 'production') {\n              logMessages.push([\n                'Reached the end of source:',\n                sourcePromises[i],\n              ]);\n            }\n\n            i++;\n            if (i >= readerPromises.length) {\n              // Log all the messages in the group at once in a single group.\n              if (process.env.NODE_ENV !== 'production') {\n                logger.groupCollapsed(\n                  `Concatenating ${readerPromises.length} sources.`,\n                );\n                for (const message of logMessages) {\n                  if (Array.isArray(message)) {\n                    logger.log(...message);\n                  } else {\n                    logger.log(message);\n                  }\n                }\n                logger.log('Finished reading all sources.');\n                logger.groupEnd();\n              }\n\n              controller.close();\n              streamDeferred.resolve();\n              return;\n            }\n\n            // The `pull` method is defined because we're inside it.\n            return this.pull!(controller);\n          } else {\n            controller.enqueue(result?.value);\n          }\n        })\n        .catch((error) => {\n          if (process.env.NODE_ENV !== 'production') {\n            logger.error('An error occurred:', error);\n          }\n          streamDeferred.reject(error);\n          throw error;\n        });\n    },\n\n    cancel() {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.warn('The ReadableStream was cancelled.');\n      }\n\n      streamDeferred.resolve();\n    },\n  });\n\n  return {done: streamDeferred.promise, stream};\n}\n\nexport {concatenate};\n"
  },
  {
    "path": "packages/workbox-streams/src/concatenateToResponse.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {createHeaders} from './utils/createHeaders.js';\nimport {concatenate} from './concatenate.js';\nimport {StreamSource} from './_types.js';\nimport './_version.js';\n\n/**\n * Takes multiple source Promises, each of which could resolve to a Response, a\n * ReadableStream, or a [BodyInit](https://fetch.spec.whatwg.org/#bodyinit),\n * along with a\n * [HeadersInit](https://fetch.spec.whatwg.org/#typedefdef-headersinit).\n *\n * Returns an object exposing a Response whose body consists of each individual\n * stream's data returned in sequence, along with a Promise which signals when\n * the stream is finished (useful for passing to a FetchEvent's waitUntil()).\n *\n * @param {Array<Promise<workbox-streams.StreamSource>>} sourcePromises\n * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n * `'text/html'` will be used by default.\n * @return {Object<{done: Promise, response: Response}>}\n *\n * @memberof workbox-streams\n */\nfunction concatenateToResponse(\n  sourcePromises: Promise<StreamSource>[],\n  headersInit: HeadersInit,\n): {done: Promise<void>; response: Response} {\n  const {done, stream} = concatenate(sourcePromises);\n\n  const headers = createHeaders(headersInit);\n  const response = new Response(stream, {headers});\n\n  return {done, response};\n}\n\nexport {concatenateToResponse};\n"
  },
  {
    "path": "packages/workbox-streams/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {concatenate} from './concatenate.js';\nimport {concatenateToResponse} from './concatenateToResponse.js';\nimport {isSupported} from './isSupported.js';\nimport {strategy, StreamsHandlerCallback} from './strategy.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-streams\n */\n\nexport {\n  concatenate,\n  concatenateToResponse,\n  isSupported,\n  strategy,\n  StreamsHandlerCallback,\n};\n\nexport * from './_types.js';\n"
  },
  {
    "path": "packages/workbox-streams/src/isSupported.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {canConstructReadableStream} from 'workbox-core/_private/canConstructReadableStream.js';\nimport './_version.js';\n\n/**\n * This is a utility method that determines whether the current browser supports\n * the features required to create streamed responses. Currently, it checks if\n * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n * can be created.\n *\n * @return {boolean} `true`, if the current browser meets the requirements for\n * streaming responses, and `false` otherwise.\n *\n * @memberof workbox-streams\n */\nfunction isSupported(): boolean {\n  return canConstructReadableStream();\n}\n\nexport {isSupported};\n"
  },
  {
    "path": "packages/workbox-streams/src/strategy.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {\n  RouteHandlerCallback,\n  RouteHandlerCallbackOptions,\n} from 'workbox-core/types.js';\nimport {createHeaders} from './utils/createHeaders.js';\nimport {concatenateToResponse} from './concatenateToResponse.js';\nimport {isSupported} from './isSupported.js';\nimport {StreamSource} from './_types.js';\nimport './_version.js';\n\nexport interface StreamsHandlerCallback {\n  ({url, request, event, params}: RouteHandlerCallbackOptions):\n    | Promise<StreamSource>\n    | StreamSource;\n}\n\n/**\n * A shortcut to create a strategy that could be dropped-in to Workbox's router.\n *\n * On browsers that do not support constructing new `ReadableStream`s, this\n * strategy will automatically wait for all the `sourceFunctions` to complete,\n * and create a final response that concatenates their values together.\n *\n * @param {Array<function({event, request, url, params})>} sourceFunctions\n * An array of functions similar to {@link workbox-routing~handlerCallback}\n * but that instead return a {@link workbox-streams.StreamSource} (or a\n * Promise which resolves to one).\n * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n * `'text/html'` will be used by default.\n * @return {workbox-routing~handlerCallback}\n * @memberof workbox-streams\n */\nfunction strategy(\n  sourceFunctions: StreamsHandlerCallback[],\n  headersInit: HeadersInit,\n): RouteHandlerCallback {\n  return async ({event, request, url, params}: RouteHandlerCallbackOptions) => {\n    const sourcePromises = sourceFunctions.map((fn) => {\n      // Ensure the return value of the function is always a promise.\n      return Promise.resolve(fn({event, request, url, params}));\n    });\n\n    if (isSupported()) {\n      const {done, response} = concatenateToResponse(\n        sourcePromises,\n        headersInit,\n      );\n\n      if (event) {\n        event.waitUntil(done);\n      }\n      return response;\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        `The current browser doesn't support creating response ` +\n          `streams. Falling back to non-streaming response instead.`,\n      );\n    }\n\n    // Fallback to waiting for everything to finish, and concatenating the\n    // responses.\n    const blobPartsPromises = sourcePromises.map(async (sourcePromise) => {\n      const source = await sourcePromise;\n      if (source instanceof Response) {\n        return source.blob();\n      } else {\n        // Technically, a `StreamSource` object can include any valid\n        // `BodyInit` type, including `FormData` and `URLSearchParams`, which\n        // cannot be passed to the Blob constructor directly, so we have to\n        // convert them to actual Blobs first.\n        return new Response(source).blob();\n      }\n    });\n    const blobParts = await Promise.all(blobPartsPromises);\n    const headers = createHeaders(headersInit);\n\n    // Constructing a new Response from a Blob source is well-supported.\n    // So is constructing a new Blob from multiple source Blobs or strings.\n    return new Response(new Blob(blobParts), {headers});\n  };\n}\n\nexport {strategy};\n"
  },
  {
    "path": "packages/workbox-streams/src/utils/createHeaders.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * This is a utility method that determines whether the current browser supports\n * the features required to create streamed responses. Currently, it checks if\n * [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream/ReadableStream)\n * is available.\n *\n * @private\n * @param {HeadersInit} [headersInit] If there's no `Content-Type` specified,\n * `'text/html'` will be used by default.\n * @return {boolean} `true`, if the current browser meets the requirements for\n * streaming responses, and `false` otherwise.\n *\n * @memberof workbox-streams\n */\nfunction createHeaders(headersInit = {}): Headers {\n  // See https://github.com/GoogleChrome/workbox/issues/1461\n  const headers = new Headers(headersInit);\n  if (!headers.has('content-type')) {\n    headers.set('content-type', 'text/html');\n  }\n  return headers;\n}\n\nexport {createHeaders};\n"
  },
  {
    "path": "packages/workbox-streams/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-core/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-sw/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-sw\n"
  },
  {
    "path": "packages/workbox-sw/_types.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.mjs';\n\n/**\n * A `ModulePathCallback` function can be used to modify the modify the where\n * Workbox modules are loaded.\n *\n * @callback ~ModulePathCallback\n * @param {string} moduleName The name of the module to load (i.e.\n * 'workbox-core', 'workbox-precaching' etc.).\n * @param {boolean} debug When true, `dev` builds should be loaded, otherwise\n * load `prod` builds.\n * @return {string} This callback should return a path of module. This will\n * be passed to `importScripts()`.\n *\n * @memberof workbox\n */\n"
  },
  {
    "path": "packages/workbox-sw/_version.mjs",
    "content": "try{self['workbox:sw:7.4.0']&&_()}catch(e){}// eslint-disable-line"
  },
  {
    "path": "packages/workbox-sw/controllers/WorkboxSW.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.mjs';\n\nconst CDN_PATH = `WORKBOX_CDN_ROOT_URL`;\n\nconst MODULE_KEY_TO_NAME_MAPPING = {\n  /**\n   * @name backgroundSync\n   * @memberof workbox\n   * @see module:workbox-background-sync\n   */\n  backgroundSync: 'background-sync',\n  /**\n   * @name broadcastUpdate\n   * @memberof workbox\n   * @see module:workbox-broadcast-update\n   */\n  broadcastUpdate: 'broadcast-update',\n  /**\n   * @name cacheableResponse\n   * @memberof workbox\n   * @see module:workbox-cacheable-response\n   */\n  cacheableResponse: 'cacheable-response',\n  /**\n   * @name core\n   * @memberof workbox\n   * @see module:workbox-core\n   */\n  core: 'core',\n  /**\n   * @name expiration\n   * @memberof workbox\n   * @see module:workbox-expiration\n   */\n  expiration: 'expiration',\n  /**\n   * @name googleAnalytics\n   * @memberof workbox\n   * @see module:workbox-google-analytics\n   */\n  googleAnalytics: 'offline-ga',\n  /**\n   * @name navigationPreload\n   * @memberof workbox\n   * @see module:workbox-navigation-preload\n   */\n  navigationPreload: 'navigation-preload',\n  /**\n   * @name precaching\n   * @memberof workbox\n   * @see module:workbox-precaching\n   */\n  precaching: 'precaching',\n  /**\n   * @name rangeRequests\n   * @memberof workbox\n   * @see module:workbox-range-requests\n   */\n  rangeRequests: 'range-requests',\n  /**\n   * @name routing\n   * @memberof workbox\n   * @see module:workbox-routing\n   */\n  routing: 'routing',\n  /**\n   * @name strategies\n   * @memberof workbox\n   * @see module:workbox-strategies\n   */\n  strategies: 'strategies',\n  /**\n   * @name streams\n   * @memberof workbox\n   * @see module:workbox-streams\n   */\n  streams: 'streams',\n  /**\n   * @name recipes\n   * @memberof workbox\n   * @see module:workbox-recipes\n   */\n  recipes: 'recipes',\n};\n\n/**\n * This class can be used to make it easy to use the various parts of\n * Workbox.\n *\n * @private\n */\nexport class WorkboxSW {\n  /**\n   * Creates a proxy that automatically loads workbox namespaces on demand.\n   *\n   * @private\n   */\n  constructor() {\n    this.v = {};\n    this._options = {\n      debug: self.location.hostname === 'localhost',\n      modulePathPrefix: null,\n      modulePathCb: null,\n    };\n\n    this._env = this._options.debug ? 'dev' : 'prod';\n    this._modulesLoaded = false;\n\n    return new Proxy(this, {\n      get(target, key) {\n        if (target[key]) {\n          return target[key];\n        }\n\n        const moduleName = MODULE_KEY_TO_NAME_MAPPING[key];\n        if (moduleName) {\n          target.loadModule(`workbox-${moduleName}`);\n        }\n\n        return target[key];\n      },\n    });\n  }\n\n  /**\n   * Updates the configuration options. You can specify whether to treat as a\n   * debug build and whether to use a CDN or a specific path when importing\n   * other workbox-modules\n   *\n   * @param {Object} [options]\n   * @param {boolean} [options.debug] If true, `dev` builds are using, otherwise\n   * `prod` builds are used. By default, `prod` is used unless on localhost.\n   * @param {Function} [options.modulePathPrefix] To avoid using the CDN with\n   * `workbox-sw` set the path prefix of where modules should be loaded from.\n   * For example `modulePathPrefix: '/third_party/workbox/v3.0.0/'`.\n   * @param {workbox~ModulePathCallback} [options.modulePathCb] If defined,\n   * this callback will be responsible for determining the path of each\n   * workbox module.\n   *\n   * @alias workbox.setConfig\n   */\n  setConfig(options = {}) {\n    if (!this._modulesLoaded) {\n      Object.assign(this._options, options);\n      this._env = this._options.debug ? 'dev' : 'prod';\n    } else {\n      throw new Error('Config must be set before accessing workbox.* modules');\n    }\n  }\n\n  /**\n   * Load a Workbox module by passing in the appropriate module name.\n   *\n   * This is not generally needed unless you know there are modules that are\n   * dynamically used and you want to safe guard use of the module while the\n   * user may be offline.\n   *\n   * @param {string} moduleName\n   *\n   * @alias workbox.loadModule\n   */\n  loadModule(moduleName) {\n    const modulePath = this._getImportPath(moduleName);\n    try {\n      importScripts(modulePath);\n      this._modulesLoaded = true;\n    } catch (err) {\n      // TODO Add context of this error if using the CDN vs the local file.\n\n      // We can't rely on workbox-core being loaded so using console\n      // eslint-disable-next-line\n      console.error(\n          `Unable to import module '${moduleName}' from '${modulePath}'.`);\n      throw err;\n    }\n  }\n\n  /**\n   * This method will get the path / CDN URL to be used for importScript calls.\n   *\n   * @param {string} moduleName\n   * @return {string} URL to the desired module.\n   *\n   * @private\n   */\n  _getImportPath(moduleName) {\n    if (this._options.modulePathCb) {\n      return this._options.modulePathCb(moduleName, this._options.debug);\n    }\n\n    // TODO: This needs to be dynamic some how.\n    let pathParts = [CDN_PATH];\n\n    const fileName = `${moduleName}.${this._env}.js`;\n\n    const pathPrefix = this._options.modulePathPrefix;\n    if (pathPrefix) {\n      // Split to avoid issues with developers ending / not ending with slash\n      pathParts = pathPrefix.split('/');\n\n      // We don't need a slash at the end as we will be adding\n      // a filename regardless\n      if (pathParts[pathParts.length - 1] === '') {\n        pathParts.splice(pathParts.length - 1, 1);\n      }\n    }\n\n    pathParts.push(fileName);\n\n    return pathParts.join('/');\n  }\n}\n"
  },
  {
    "path": "packages/workbox-sw/index.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxSW} from './controllers/WorkboxSW.mjs';\nimport './_version.mjs';\n\n/**\n * @namespace workbox\n */\n\n// Don't export anything, just expose a global.\nself.workbox = new WorkboxSW();\n"
  },
  {
    "path": "packages/workbox-sw/package.json",
    "content": "{\n  \"name\": \"workbox-sw\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"This module makes it easy to get started with the Workbox service worker libraries.\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\"\n  ],\n  \"workbox\": {\n    \"browserNamespace\": \"workbox\",\n    \"packageType\": \"sw\",\n    \"prodOnly\": true\n  },\n  \"main\": \"build/workbox-sw.js\",\n  \"module\": \"index.mjs\"\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/README.md",
    "content": "This module's documentation can be found at https://developer.chrome.com/docs/workbox/modules/workbox-webpack-plugin/\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/package.json",
    "content": "{\n  \"name\": \"workbox-webpack-plugin\",\n  \"version\": \"7.4.0\",\n  \"description\": \"A plugin for your Webpack build process, helping you generate a manifest of local files that workbox-sw should precache.\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"webpack\",\n    \"service worker\",\n    \"caching\",\n    \"fetch requests\",\n    \"offline\",\n    \"file manifest\"\n  ],\n  \"workbox\": {\n    \"packageType\": \"node_ts\"\n  },\n  \"main\": \"build/index.js\",\n  \"types\": \"build/index.d.ts\",\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"dependencies\": {\n    \"fast-json-stable-stringify\": \"^2.1.0\",\n    \"pretty-bytes\": \"^5.4.1\",\n    \"upath\": \"^1.2.0\",\n    \"webpack-sources\": \"^1.4.3\",\n    \"workbox-build\": \"7.4.0\"\n  },\n  \"peerDependencies\": {\n    \"webpack\": \"^4.4.0 || ^5.91.0\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^20.14.8\",\n    \"@types/webpack\": \"^5.28.1\"\n  },\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/GoogleChrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\"\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/generate-sw.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {validateWebpackGenerateSWOptions} from 'workbox-build/build/lib/validate-options';\nimport {bundle} from 'workbox-build/build/lib/bundle';\nimport {populateSWTemplate} from 'workbox-build/build/lib/populate-sw-template';\nimport prettyBytes from 'pretty-bytes';\nimport webpack from 'webpack';\nimport {ManifestEntry, WebpackGenerateSWOptions} from 'workbox-build';\nimport {getScriptFilesForChunks} from './lib/get-script-files-for-chunks';\nimport {getManifestEntriesFromCompilation} from './lib/get-manifest-entries-from-compilation';\nimport {relativeToOutputPath} from './lib/relative-to-output-path';\n\n// webpack v4/v5 compatibility:\n// https://github.com/webpack/webpack/issues/11425#issuecomment-686607633\nconst {RawSource} = webpack.sources || require('webpack-sources');\n\n// Used to keep track of swDest files written by *any* instance of this plugin.\n// See https://github.com/GoogleChrome/workbox/issues/2181\nconst _generatedAssetNames = new Set<string>();\n\nexport interface GenerateSWConfig extends WebpackGenerateSWOptions {\n  manifestEntries?: Array<ManifestEntry>;\n}\n\n/**\n * This class supports creating a new, ready-to-use service worker file as\n * part of the webpack compilation process.\n *\n * Use an instance of `GenerateSW` in the\n * [`plugins` array](https://webpack.js.org/concepts/plugins/#usage) of a\n * webpack config.\n *\n * ```\n * // The following lists some common options; see the rest of the documentation\n * // for the full set of options and defaults.\n * new GenerateSW({\n *   exclude: [/.../, '...'],\n *   maximumFileSizeToCacheInBytes: ...,\n *   navigateFallback: '...',\n *   runtimeCaching: [{\n *     // Routing via a matchCallback function:\n *     urlPattern: ({request, url}) => ...,\n *     handler: '...',\n *     options: {\n *       cacheName: '...',\n *       expiration: {\n *         maxEntries: ...,\n *       },\n *     },\n *   }, {\n *     // Routing via a RegExp:\n *     urlPattern: new RegExp('...'),\n *     handler: '...',\n *     options: {\n *       cacheName: '...',\n *       plugins: [..., ...],\n *     },\n *   }],\n *   skipWaiting: ...,\n * });\n * ```\n *\n * @memberof module:workbox-webpack-plugin\n */\nclass GenerateSW {\n  protected config: GenerateSWConfig;\n  private alreadyCalled: boolean;\n\n  /**\n   * Creates an instance of GenerateSW.\n   */\n  constructor(config: GenerateSWConfig = {}) {\n    this.config = config;\n    this.alreadyCalled = false;\n  }\n\n  /**\n   * @param {Object} [compiler] default compiler object passed from webpack\n   *\n   * @private\n   */\n  propagateWebpackConfig(compiler: webpack.Compiler): void {\n    // Because this.config is listed last, properties that are already set\n    // there take precedence over derived properties from the compiler.\n    this.config = Object.assign(\n      {\n        mode: compiler.options.mode,\n        sourcemap: Boolean(compiler.options.devtool),\n      },\n      this.config,\n    );\n  }\n\n  /**\n   * @param {Object} [compiler] default compiler object passed from webpack\n   *\n   * @private\n   */\n  apply(compiler: webpack.Compiler): void {\n    this.propagateWebpackConfig(compiler);\n\n    // webpack v4/v5 compatibility:\n    // https://github.com/webpack/webpack/issues/11425#issuecomment-690387207\n    if (webpack.version.startsWith('4.')) {\n      compiler.hooks.emit.tapPromise(this.constructor.name, (compilation) =>\n        this.addAssets(compilation).catch((error) => {\n          compilation.errors.push(error);\n        }),\n      );\n    } else {\n      const {PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER} = webpack.Compilation;\n      // Specifically hook into thisCompilation, as per\n      // https://github.com/webpack/webpack/issues/11425#issuecomment-690547848\n      compiler.hooks.thisCompilation.tap(\n        this.constructor.name,\n        (compilation) => {\n          compilation.hooks.processAssets.tapPromise(\n            {\n              name: this.constructor.name,\n              // TODO(jeffposnick): This may need to change eventually.\n              // See https://github.com/webpack/webpack/issues/11822#issuecomment-726184972\n              stage: PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10,\n            },\n            () =>\n              this.addAssets(compilation).catch(\n                (error: webpack.WebpackError) => {\n                  compilation.errors.push(error);\n                },\n              ),\n          );\n        },\n      );\n    }\n  }\n\n  /**\n   * @param {Object} compilation The webpack compilation.\n   *\n   * @private\n   */\n  async addAssets(compilation: webpack.Compilation): Promise<void> {\n    // See https://github.com/GoogleChrome/workbox/issues/1790\n    if (this.alreadyCalled) {\n      const warningMessage =\n        `${this.constructor.name} has been called ` +\n        `multiple times, perhaps due to running webpack in --watch mode. The ` +\n        `precache manifest generated after the first call may be inaccurate! ` +\n        `Please see https://github.com/GoogleChrome/workbox/issues/1790 for ` +\n        `more information.`;\n\n      if (\n        !compilation.warnings.some(\n          (warning) =>\n            warning instanceof Error && warning.message === warningMessage,\n        )\n      ) {\n        compilation.warnings.push(\n          Error(warningMessage) as webpack.WebpackError,\n        );\n      }\n    } else {\n      this.alreadyCalled = true;\n    }\n\n    let config: GenerateSWConfig = {};\n    try {\n      // emit might be called multiple times; instead of modifying this.config,\n      // use a validated copy.\n      // See https://github.com/GoogleChrome/workbox/issues/2158\n      config = validateWebpackGenerateSWOptions(this.config);\n    } catch (error) {\n      if (error instanceof Error) {\n        throw new Error(\n          `Please check your ${this.constructor.name} plugin ` +\n            `configuration:\\n${error.message}`,\n        );\n      }\n    }\n\n    // Ensure that we don't precache any of the assets generated by *any*\n    // instance of this plugin.\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    config.exclude!.push(({asset}) => _generatedAssetNames.has(asset.name));\n\n    if (config.importScriptsViaChunks) {\n      // Anything loaded via importScripts() is implicitly cached by the service\n      // worker, and should not be added to the precache manifest.\n      config.excludeChunks = (config.excludeChunks || []).concat(\n        config.importScriptsViaChunks,\n      );\n\n      const scripts = getScriptFilesForChunks(\n        compilation,\n        config.importScriptsViaChunks,\n      );\n\n      config.importScripts = (config.importScripts || []).concat(scripts);\n    }\n\n    const {size, sortedEntries} = await getManifestEntriesFromCompilation(\n      compilation,\n      config,\n    );\n    config.manifestEntries = sortedEntries;\n\n    const unbundledCode = populateSWTemplate(config);\n\n    const files = await bundle({\n      babelPresetEnvTargets: config.babelPresetEnvTargets,\n      inlineWorkboxRuntime: config.inlineWorkboxRuntime,\n      mode: config.mode,\n      sourcemap: config.sourcemap,\n      swDest: relativeToOutputPath(compilation, config.swDest!),\n      unbundledCode,\n    });\n\n    for (const file of files) {\n      compilation.emitAsset(\n        file.name,\n        new RawSource(Buffer.from(file.contents as Uint8Array)),\n        {\n          // See https://github.com/webpack-contrib/compression-webpack-plugin/issues/218#issuecomment-726196160\n          minimized: config.mode === 'production',\n        },\n      );\n      _generatedAssetNames.add(file.name);\n    }\n\n    if (compilation.getLogger) {\n      const logger = compilation.getLogger(this.constructor.name);\n      logger.info(`The service worker at ${config.swDest ?? ''} will precache\n        ${config.manifestEntries.length} URLs, totaling ${prettyBytes(size)}.`);\n    }\n  }\n}\n\nexport {GenerateSW};\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/index.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {GenerateSW, GenerateSWConfig} from './generate-sw';\nimport {InjectManifest} from './inject-manifest';\n\n/**\n * @module workbox-webpack-plugin\n */\nexport {GenerateSW, GenerateSWConfig, InjectManifest};\n\n// TODO: remove this in v7.\n// See https://github.com/GoogleChrome/workbox/issues/3033\nexport default {GenerateSW, InjectManifest};\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/inject-manifest.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {escapeRegExp} from 'workbox-build/build/lib/escape-regexp';\nimport {replaceAndUpdateSourceMap} from 'workbox-build/build/lib/replace-and-update-source-map';\nimport {validateWebpackInjectManifestOptions} from 'workbox-build/build/lib/validate-options';\nimport prettyBytes from 'pretty-bytes';\nimport stringify from 'fast-json-stable-stringify';\nimport upath from 'upath';\nimport webpack from 'webpack';\n\nimport {getManifestEntriesFromCompilation} from './lib/get-manifest-entries-from-compilation';\nimport {getSourcemapAssetName} from './lib/get-sourcemap-asset-name';\nimport {relativeToOutputPath} from './lib/relative-to-output-path';\nimport {WebpackInjectManifestOptions} from 'workbox-build';\n// Used to keep track of swDest files written by *any* instance of this plugin.\n// See https://github.com/GoogleChrome/workbox/issues/2181\nconst _generatedAssetNames = new Set<string>();\n\n// SingleEntryPlugin in v4 was renamed to EntryPlugin in v5.\nconst SingleEntryPlugin = webpack.EntryPlugin || webpack.SingleEntryPlugin;\n\n// webpack v4/v5 compatibility:\n// https://github.com/webpack/webpack/issues/11425#issuecomment-686607633\nconst {RawSource} = webpack.sources || require('webpack-sources');\n\n/**\n * This class supports compiling a service worker file provided via `swSrc`,\n * and injecting into that service worker a list of URLs and revision\n * information for precaching based on the webpack asset pipeline.\n *\n * Use an instance of `InjectManifest` in the\n * [`plugins` array](https://webpack.js.org/concepts/plugins/#usage) of a\n * webpack config.\n *\n * In addition to injecting the manifest, this plugin will perform a compilation\n * of the `swSrc` file, using the options from the main webpack configuration.\n *\n * ```\n * // The following lists some common options; see the rest of the documentation\n * // for the full set of options and defaults.\n * new InjectManifest({\n *   exclude: [/.../, '...'],\n *   maximumFileSizeToCacheInBytes: ...,\n *   swSrc: '...',\n * });\n * ```\n *\n * @memberof module:workbox-webpack-plugin\n */\nclass InjectManifest {\n  protected config: WebpackInjectManifestOptions;\n  private alreadyCalled: boolean;\n\n  /**\n   * Creates an instance of InjectManifest.\n   */\n  constructor(config: WebpackInjectManifestOptions) {\n    this.config = config;\n    this.alreadyCalled = false;\n  }\n\n  /**\n   * @param {Object} [compiler] default compiler object passed from webpack\n   *\n   * @private\n   */\n  propagateWebpackConfig(compiler: webpack.Compiler): void {\n    // Because this.config is listed last, properties that are already set\n    // there take precedence over derived properties from the compiler.\n    this.config = Object.assign(\n      {\n        mode: compiler.options.mode,\n        // Use swSrc with a hardcoded .js extension, in case swSrc is a .ts file.\n        swDest: upath.parse(this.config.swSrc).name + '.js',\n      },\n      this.config,\n    );\n  }\n\n  /**\n   * @param {Object} [compiler] default compiler object passed from webpack\n   *\n   * @private\n   */\n  apply(compiler: webpack.Compiler): void {\n    this.propagateWebpackConfig(compiler);\n\n    compiler.hooks.make.tapPromise(this.constructor.name, (compilation) =>\n      this.handleMake(compilation, compiler).catch(\n        (error: webpack.WebpackError) => {\n          compilation.errors.push(error);\n        },\n      ),\n    );\n\n    // webpack v4/v5 compatibility:\n    // https://github.com/webpack/webpack/issues/11425#issuecomment-690387207\n    if (webpack.version?.startsWith('4.')) {\n      compiler.hooks.emit.tapPromise(this.constructor.name, (compilation) =>\n        this.addAssets(compilation).catch((error: webpack.WebpackError) => {\n          compilation.errors.push(error);\n        }),\n      );\n    } else {\n      const {PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER} = webpack.Compilation;\n      // Specifically hook into thisCompilation, as per\n      // https://github.com/webpack/webpack/issues/11425#issuecomment-690547848\n      compiler.hooks.thisCompilation.tap(\n        this.constructor.name,\n        (compilation) => {\n          compilation.hooks.processAssets.tapPromise(\n            {\n              name: this.constructor.name,\n              // TODO(jeffposnick): This may need to change eventually.\n              // See https://github.com/webpack/webpack/issues/11822#issuecomment-726184972\n              stage: PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - 10,\n            },\n            () =>\n              this.addAssets(compilation).catch(\n                (error: webpack.WebpackError) => {\n                  compilation.errors.push(error);\n                },\n              ),\n          );\n        },\n      );\n    }\n  }\n\n  /**\n   * @param {Object} compilation The webpack compilation.\n   * @param {Object} parentCompiler The webpack parent compiler.\n   *\n   * @private\n   */\n  async performChildCompilation(\n    compilation: webpack.Compilation,\n    parentCompiler: webpack.Compiler,\n  ): Promise<void> {\n    const outputOptions = {\n      path: parentCompiler.options.output.path,\n      filename: this.config.swDest,\n    };\n\n    const childCompiler = compilation.createChildCompiler(\n      this.constructor.name,\n      outputOptions,\n      [],\n    );\n\n    childCompiler.context = parentCompiler.context;\n    childCompiler.inputFileSystem = parentCompiler.inputFileSystem;\n    childCompiler.outputFileSystem = parentCompiler.outputFileSystem;\n\n    if (Array.isArray(this.config.webpackCompilationPlugins)) {\n      for (const plugin of this.config.webpackCompilationPlugins) {\n        // plugin has a generic type, eslint complains for an unsafe\n        // assign and unsafe use\n        // eslint-disable-next-line\n        plugin.apply(childCompiler);\n      }\n    }\n\n    new SingleEntryPlugin(\n      parentCompiler.context,\n      this.config.swSrc,\n      this.constructor.name,\n    ).apply(childCompiler);\n\n    await new Promise<void>((resolve, reject) => {\n      childCompiler.runAsChild((error, _entries, childCompilation) => {\n        if (error) {\n          reject(error);\n        } else {\n          compilation.warnings = compilation.warnings.concat(\n            childCompilation?.warnings ?? [],\n          );\n          compilation.errors = compilation.errors.concat(\n            childCompilation?.errors ?? [],\n          );\n\n          resolve();\n        }\n      });\n    });\n  }\n\n  /**\n   * @param {Object} compilation The webpack compilation.\n   * @param {Object} parentCompiler The webpack parent compiler.\n   *\n   * @private\n   */\n  addSrcToAssets(\n    compilation: webpack.Compilation,\n    parentCompiler: webpack.Compiler,\n  ): void {\n    // eslint-disable-next-line\n    const source = (parentCompiler.inputFileSystem as any).readFileSync(\n      this.config.swSrc,\n    );\n    compilation.emitAsset(this.config.swDest!, new RawSource(source));\n  }\n\n  /**\n   * @param {Object} compilation The webpack compilation.\n   * @param {Object} parentCompiler The webpack parent compiler.\n   *\n   * @private\n   */\n  async handleMake(\n    compilation: webpack.Compilation,\n    parentCompiler: webpack.Compiler,\n  ): Promise<void> {\n    try {\n      this.config = validateWebpackInjectManifestOptions(this.config);\n    } catch (error) {\n      if (error instanceof Error) {\n        throw new Error(\n          `Please check your ${this.constructor.name} plugin ` +\n            `configuration:\\n${error.message}`,\n        );\n      }\n    }\n\n    this.config.swDest = relativeToOutputPath(compilation, this.config.swDest!);\n    _generatedAssetNames.add(this.config.swDest);\n\n    if (this.config.compileSrc) {\n      await this.performChildCompilation(compilation, parentCompiler);\n    } else {\n      this.addSrcToAssets(compilation, parentCompiler);\n      // This used to be a fatal error, but just warn at runtime because we\n      // can't validate it easily.\n      if (\n        Array.isArray(this.config.webpackCompilationPlugins) &&\n        this.config.webpackCompilationPlugins.length > 0\n      ) {\n        compilation.warnings.push(\n          new Error(\n            'compileSrc is false, so the ' +\n              'webpackCompilationPlugins option will be ignored.',\n          ) as webpack.WebpackError,\n        );\n      }\n    }\n  }\n\n  /**\n   * @param {Object} compilation The webpack compilation.\n   *\n   * @private\n   */\n  async addAssets(compilation: webpack.Compilation): Promise<void> {\n    // See https://github.com/GoogleChrome/workbox/issues/1790\n    if (this.alreadyCalled) {\n      const warningMessage =\n        `${this.constructor.name} has been called ` +\n        `multiple times, perhaps due to running webpack in --watch mode. The ` +\n        `precache manifest generated after the first call may be inaccurate! ` +\n        `Please see https://github.com/GoogleChrome/workbox/issues/1790 for ` +\n        `more information.`;\n\n      if (\n        !compilation.warnings.some(\n          (warning) =>\n            warning instanceof Error && warning.message === warningMessage,\n        )\n      ) {\n        compilation.warnings.push(\n          new Error(warningMessage) as webpack.WebpackError,\n        );\n      }\n    } else {\n      this.alreadyCalled = true;\n    }\n\n    const config = Object.assign({}, this.config);\n\n    // Ensure that we don't precache any of the assets generated by *any*\n    // instance of this plugin.\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n    config.exclude!.push(({asset}) => _generatedAssetNames.has(asset.name));\n\n    // See https://webpack.js.org/contribute/plugin-patterns/#monitoring-the-watch-graph\n    const absoluteSwSrc = upath.resolve(this.config.swSrc);\n    compilation.fileDependencies.add(absoluteSwSrc);\n\n    const swAsset = compilation.getAsset(config.swDest!);\n    const swAssetString = swAsset!.source.source().toString();\n\n    const globalRegexp = new RegExp(escapeRegExp(config.injectionPoint!), 'g');\n    const injectionResults = swAssetString.match(globalRegexp);\n\n    if (!injectionResults) {\n      throw new Error(\n        `Can't find ${config.injectionPoint ?? ''} in your SW source.`,\n      );\n    }\n    if (injectionResults.length !== 1) {\n      throw new Error(\n        `Multiple instances of ${config.injectionPoint ?? ''} were ` +\n          `found in your SW source. Include it only once. For more info, see ` +\n          `https://github.com/GoogleChrome/workbox/issues/2681`,\n      );\n    }\n\n    const {size, sortedEntries} = await getManifestEntriesFromCompilation(\n      compilation,\n      config,\n    );\n\n    let manifestString = stringify(sortedEntries);\n    if (\n      this.config.compileSrc &&\n      // See https://github.com/GoogleChrome/workbox/issues/2729\n      !(\n        compilation.options?.devtool === 'eval-cheap-source-map' &&\n        compilation.options.optimization?.minimize\n      )\n    ) {\n      // See https://github.com/GoogleChrome/workbox/issues/2263\n      manifestString = manifestString.replace(/\"/g, `'`);\n    }\n\n    const sourcemapAssetName = getSourcemapAssetName(\n      compilation,\n      swAssetString,\n      config.swDest!,\n    );\n\n    if (sourcemapAssetName) {\n      _generatedAssetNames.add(sourcemapAssetName);\n      const sourcemapAsset = compilation.getAsset(sourcemapAssetName);\n      const {source, map} = await replaceAndUpdateSourceMap({\n        jsFilename: config.swDest!,\n        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n        originalMap: JSON.parse(sourcemapAsset!.source.source().toString()),\n        originalSource: swAssetString,\n        replaceString: manifestString,\n        searchString: config.injectionPoint!,\n      });\n\n      compilation.updateAsset(sourcemapAssetName, new RawSource(map));\n      compilation.updateAsset(config.swDest!, new RawSource(source));\n    } else {\n      // If there's no sourcemap associated with swDest, a simple string\n      // replacement will suffice.\n      compilation.updateAsset(\n        config.swDest!,\n        new RawSource(\n          swAssetString.replace(config.injectionPoint!, manifestString),\n        ),\n      );\n    }\n\n    if (compilation.getLogger) {\n      const logger = compilation.getLogger(this.constructor.name);\n      logger.info(`The service worker at ${config.swDest ?? ''} will precache\n        ${sortedEntries.length} URLs, totaling ${prettyBytes(size)}.`);\n    }\n  }\n}\n\nexport {InjectManifest};\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/get-asset-hash.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport crypto from 'crypto';\nimport type {Asset} from 'webpack';\n\n/**\n * @param {Asset} asset\n * @return {string} The MD5 hash of the asset's source.\n *\n * @private\n */\nexport function getAssetHash(asset: Asset): string | null {\n  // If webpack has the asset marked as immutable, then we don't need to\n  // use an out-of-band revision for it.\n  // See https://github.com/webpack/webpack/issues/9038\n  if (asset.info && asset.info.immutable) {\n    return null;\n  }\n\n  return crypto\n    .createHash('md5')\n    .update(Buffer.from(asset.source.source() as Buffer))\n    .digest('hex');\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/get-manifest-entries-from-compilation.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {\n  Asset,\n  Chunk,\n  Compilation,\n  ModuleFilenameHelpers,\n  WebpackError,\n} from 'webpack';\nimport {transformManifest} from 'workbox-build/build/lib/transform-manifest';\n\nimport {\n  WebpackGenerateSWOptions,\n  WebpackInjectManifestOptions,\n  ManifestEntry,\n  FileDetails,\n} from 'workbox-build';\nimport {getAssetHash} from './get-asset-hash';\nimport {resolveWebpackURL} from './resolve-webpack-url';\n\n/**\n * For a given asset, checks whether at least one of the conditions matches.\n *\n * @param {Asset} asset The webpack asset in question. This will be passed\n * to any functions that are listed as conditions.\n * @param {Compilation} compilation The webpack compilation. This will be passed\n * to any functions that are listed as conditions.\n * @param {Array<string|RegExp|Function>} conditions\n * @return {boolean} Whether or not at least one condition matches.\n * @private\n */\nfunction checkConditions(\n  asset: Asset,\n  compilation: Compilation,\n\n  conditions: Array<\n    //eslint-disable-next-line @typescript-eslint/ban-types\n    string | RegExp | ((arg0: any) => boolean)\n  > = [],\n): boolean {\n  for (const condition of conditions) {\n    if (typeof condition === 'function') {\n      return condition({asset, compilation});\n      //return compilation !== null;\n    } else {\n      if (ModuleFilenameHelpers.matchPart(asset.name, condition)) {\n        return true;\n      }\n    }\n  }\n\n  // We'll only get here if none of the conditions applied.\n  return false;\n}\n\n/**\n * Returns the names of all the assets in all the chunks in a chunk group,\n * if provided a chunk group name.\n * Otherwise, if provided a chunk name, return all the assets in that chunk.\n * Otherwise, if there isn't a chunk group or chunk with that name, return null.\n *\n * @param {Compilation} compilation\n * @param {string} chunkOrGroup\n * @return {Array<string>|null}\n * @private\n */\nfunction getNamesOfAssetsInChunkOrGroup(\n  compilation: Compilation,\n  chunkOrGroup: string,\n): Array<string> | null {\n  const chunkGroup =\n    compilation.namedChunkGroups &&\n    compilation.namedChunkGroups.get(chunkOrGroup);\n  if (chunkGroup) {\n    const assetNames = [];\n    for (const chunk of chunkGroup.chunks) {\n      assetNames.push(...getNamesOfAssetsInChunk(chunk));\n    }\n    return assetNames;\n  } else {\n    const chunk =\n      compilation.namedChunks && compilation.namedChunks.get(chunkOrGroup);\n    if (chunk) {\n      return getNamesOfAssetsInChunk(chunk);\n    }\n  }\n\n  // If we get here, there's no chunkGroup or chunk with that name.\n  return null;\n}\n\n/**\n * Returns the names of all the assets in a chunk.\n *\n * @param {Chunk} chunk\n * @return {Array<string>}\n * @private\n */\nfunction getNamesOfAssetsInChunk(chunk: Chunk): Array<string> {\n  const assetNames: Array<string> = [];\n\n  assetNames.push(...chunk.files);\n\n  // This only appears to be set in webpack v5.\n  if (chunk.auxiliaryFiles) {\n    assetNames.push(...chunk.auxiliaryFiles);\n  }\n\n  return assetNames;\n}\n\n/**\n * Filters the set of assets out, based on the configuration options provided:\n * - chunks and excludeChunks, for chunkName-based criteria.\n * - include and exclude, for more general criteria.\n *\n * @param {Compilation} compilation The webpack compilation.\n * @param {Object} config The validated configuration, obtained from the plugin.\n * @return {Set<Asset>} The assets that should be included in the manifest,\n * based on the criteria provided.\n * @private\n */\nfunction filterAssets(\n  compilation: Compilation,\n  config: WebpackInjectManifestOptions | WebpackGenerateSWOptions,\n): Set<Asset> {\n  const filteredAssets = new Set<Asset>();\n  const assets = compilation.getAssets();\n\n  const allowedAssetNames = new Set<string>();\n  // See https://github.com/GoogleChrome/workbox/issues/1287\n  if (Array.isArray(config.chunks)) {\n    for (const name of config.chunks) {\n      // See https://github.com/GoogleChrome/workbox/issues/2717\n      const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(\n        compilation,\n        name,\n      );\n      if (assetsInChunkOrGroup) {\n        for (const assetName of assetsInChunkOrGroup) {\n          allowedAssetNames.add(assetName);\n        }\n      } else {\n        compilation.warnings.push(\n          new Error(\n            `The chunk '${name}' was ` +\n              `provided in your Workbox chunks config, but was not found in the ` +\n              `compilation.`,\n          ) as WebpackError,\n        );\n      }\n    }\n  }\n\n  const deniedAssetNames = new Set();\n  if (Array.isArray(config.excludeChunks)) {\n    for (const name of config.excludeChunks) {\n      // See https://github.com/GoogleChrome/workbox/issues/2717\n      const assetsInChunkOrGroup = getNamesOfAssetsInChunkOrGroup(\n        compilation,\n        name,\n      );\n      if (assetsInChunkOrGroup) {\n        for (const assetName of assetsInChunkOrGroup) {\n          deniedAssetNames.add(assetName);\n        }\n      } // Don't warn if the chunk group isn't found.\n    }\n  }\n\n  for (const asset of assets) {\n    // chunk based filtering is funky because:\n    // - Each asset might belong to one or more chunks.\n    // - If *any* of those chunk names match our config.excludeChunks,\n    //   then we skip that asset.\n    // - If the config.chunks is defined *and* there's no match\n    //   between at least one of the chunkNames and one entry, then\n    //   we skip that assets as well.\n\n    if (deniedAssetNames.has(asset.name)) {\n      continue;\n    }\n\n    if (Array.isArray(config.chunks) && !allowedAssetNames.has(asset.name)) {\n      continue;\n    }\n\n    // Next, check asset-level checks via includes/excludes:\n    const isExcluded = checkConditions(asset, compilation, config.exclude);\n    if (isExcluded) {\n      continue;\n    }\n\n    // Treat an empty config.includes as an implicit inclusion.\n    const isIncluded =\n      !Array.isArray(config.include) ||\n      checkConditions(asset, compilation, config.include);\n    if (!isIncluded) {\n      continue;\n    }\n\n    // If we've gotten this far, then add the asset.\n    filteredAssets.add(asset);\n  }\n\n  return filteredAssets;\n}\n\nexport async function getManifestEntriesFromCompilation(\n  compilation: Compilation,\n  config: WebpackGenerateSWOptions | WebpackInjectManifestOptions,\n): Promise<{size: number; sortedEntries: ManifestEntry[]}> {\n  const filteredAssets = filterAssets(compilation, config);\n\n  const {publicPath} = compilation.options.output;\n\n  const fileDetails = Array.from(filteredAssets).map((asset) => {\n    return {\n      file: resolveWebpackURL(publicPath as string, asset.name),\n      hash: getAssetHash(asset),\n      size: asset.source.size() || 0,\n    } as FileDetails;\n  });\n\n  const {manifestEntries, size, warnings} = await transformManifest({\n    fileDetails,\n    additionalManifestEntries: config.additionalManifestEntries,\n    dontCacheBustURLsMatching: config.dontCacheBustURLsMatching,\n    manifestTransforms: config.manifestTransforms,\n    maximumFileSizeToCacheInBytes: config.maximumFileSizeToCacheInBytes,\n    modifyURLPrefix: config.modifyURLPrefix,\n    transformParam: compilation,\n  });\n\n  // See https://github.com/GoogleChrome/workbox/issues/2790\n  for (const warning of warnings) {\n    compilation.warnings.push(new Error(warning) as WebpackError);\n  }\n\n  // Ensure that the entries are properly sorted by URL.\n  const sortedEntries = manifestEntries.sort((a, b) =>\n    a.url === b.url ? 0 : a.url > b.url ? 1 : -1,\n  );\n\n  return {size, sortedEntries};\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/get-script-files-for-chunks.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport upath from 'upath';\nimport {Compilation, WebpackError} from 'webpack';\n\nimport {resolveWebpackURL} from './resolve-webpack-url';\n\nexport function getScriptFilesForChunks(\n  compilation: Compilation,\n  chunkNames: Array<string>,\n): Array<string> {\n  const {chunks} = compilation.getStats().toJson({chunks: true});\n  const {publicPath} = compilation.options.output;\n  const scriptFiles = new Set<string>();\n\n  for (const chunkName of chunkNames) {\n    const chunk = chunks!.find((chunk) => chunk.names?.includes(chunkName));\n    if (chunk) {\n      for (const file of chunk?.files ?? []) {\n        // See https://github.com/GoogleChrome/workbox/issues/2161\n        if (upath.extname(file) === '.js') {\n          scriptFiles.add(resolveWebpackURL(publicPath as string, file));\n        }\n      }\n    } else {\n      compilation.warnings.push(\n        new Error(\n          `${chunkName} was provided to ` +\n            `importScriptsViaChunks, but didn't match any named chunks.`,\n        ) as WebpackError,\n      );\n    }\n  }\n\n  if (scriptFiles.size === 0) {\n    compilation.warnings.push(\n      new Error(\n        `There were no assets matching ` +\n          `importScriptsViaChunks: [${chunkNames.join(' ')}].`,\n      ) as WebpackError,\n    );\n  }\n\n  return Array.from(scriptFiles);\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/get-sourcemap-asset-name.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getSourceMapURL} from 'workbox-build/build/lib/get-source-map-url';\nimport upath from 'upath';\nimport type {Compilation} from 'webpack';\n\n/**\n * If our bundled swDest file contains a sourcemap, we would invalidate that\n * mapping if we just replaced injectionPoint with the stringified manifest.\n * Instead, we need to update the swDest contents as well as the sourcemap\n * at the same time.\n *\n * See https://github.com/GoogleChrome/workbox/issues/2235\n *\n * @param {Object} compilation The current webpack compilation.\n * @param {string} swContents The contents of the swSrc file, which may or\n * may not include a valid sourcemap comment.\n * @param {string} swDest The configured swDest value.\n * @return {string|undefined} If the swContents contains a valid sourcemap\n * comment pointing to an asset present in the compilation, this will return the\n * name of that asset. Otherwise, it will return undefined.\n *\n * @private\n */\nexport function getSourcemapAssetName(\n  compilation: Compilation,\n  swContents: string,\n  swDest: string,\n): string | undefined {\n  const url = getSourceMapURL(swContents);\n  if (url) {\n    // Translate the relative URL to what the presumed name for the webpack\n    // asset should be.\n    // This *might* not be a valid asset if the sourcemap URL that was found\n    // was added by another module incidentally.\n    // See https://github.com/GoogleChrome/workbox/issues/2250\n    const swAssetDirname = upath.dirname(swDest);\n    const sourcemapURLAssetName = upath.normalize(\n      upath.join(swAssetDirname, url),\n    );\n\n    // Not sure if there's a better way to check for asset existence?\n    if (compilation.getAsset(sourcemapURLAssetName)) {\n      return sourcemapURLAssetName;\n    }\n  }\n  return undefined;\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/relative-to-output-path.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport upath from 'upath';\nimport type {Compilation} from 'webpack';\n\n/**\n * @param {Object} compilation The webpack compilation.\n * @param {string} swDest The original swDest value.\n *\n * @return {string} If swDest was not absolute, the returns swDest as-is.\n * Otherwise, returns swDest relative to the compilation's output path.\n *\n * @private\n */\nexport function relativeToOutputPath(\n  compilation: Compilation,\n  swDest: string,\n): string {\n  // See https://github.com/jantimon/html-webpack-plugin/pull/266/files#diff-168726dbe96b3ce427e7fedce31bb0bcR38\n  if (upath.resolve(swDest) === upath.normalize(swDest)) {\n    return upath.relative(compilation.options.output.path!, swDest);\n  }\n\n  // Otherwise, return swDest as-is.\n  return swDest;\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/src/lib/resolve-webpack-url.ts",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/**\n * Resolves a url in the way that webpack would (with string concatenation)\n *\n * Use publicPath + filePath instead of url.resolve(publicPath, filePath) see:\n * https://webpack.js.org/configuration/output/#output-publicpath\n *\n * @function resolveWebpackURL\n * @param {string} publicPath The publicPath value from webpack's compilation.\n * @param {Array<string>} paths File paths to join\n * @return {string} Joined file path\n *\n * @private\n */\nexport function resolveWebpackURL(\n  publicPath: string,\n  ...paths: Array<string>\n): string {\n  // This is a change in webpack v5.\n  // See https://github.com/jantimon/html-webpack-plugin/pull/1516\n  if (publicPath === 'auto') {\n    return paths.join('');\n  } else {\n    return [publicPath, ...paths].join('');\n  }\n}\n"
  },
  {
    "path": "packages/workbox-webpack-plugin/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"esModuleInterop\": true,\n    \"module\": \"CommonJS\",\n    \"outDir\": \"./build\",\n    \"resolveJsonModule\": true,\n    \"rootDir\": \"./src\",\n    \"target\": \"ES2018\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [{\"path\": \"../workbox-build/\"}]\n}\n"
  },
  {
    "path": "packages/workbox-window/README.md",
    "content": "This module's documentation can be found at https://developers.google.com/web/tools/workbox/modules/workbox-window\n"
  },
  {
    "path": "packages/workbox-window/package.json",
    "content": "{\n  \"name\": \"workbox-window\",\n  \"version\": \"7.4.0\",\n  \"license\": \"MIT\",\n  \"author\": \"Google's Web DevRel Team and Google's Aurora Team\",\n  \"description\": \"Simplifies communications with Workbox packages running in the service worker\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/googlechrome/workbox.git\"\n  },\n  \"bugs\": \"https://github.com/googlechrome/workbox/issues\",\n  \"homepage\": \"https://github.com/GoogleChrome/workbox\",\n  \"keywords\": [\n    \"workbox\",\n    \"workboxjs\",\n    \"service worker\",\n    \"sw\",\n    \"window\",\n    \"message\",\n    \"postMessage\"\n  ],\n  \"workbox\": {\n    \"packageType\": \"window\",\n    \"primaryBuild\": \"build/workbox-window.prod.mjs\"\n  },\n  \"main\": \"build/workbox-window.prod.umd.js\",\n  \"module\": \"build/workbox-window.prod.es5.mjs\",\n  \"types\": \"index.d.ts\",\n  \"dependencies\": {\n    \"@types/trusted-types\": \"^2.0.2\",\n    \"workbox-core\": \"7.4.0\"\n  }\n}\n"
  },
  {
    "path": "packages/workbox-window/src/Workbox.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Deferred} from 'workbox-core/_private/Deferred.js';\nimport {dontWaitFor} from 'workbox-core/_private/dontWaitFor.js';\nimport {logger} from 'workbox-core/_private/logger.js';\nimport {TrustedScriptURL} from 'trusted-types/lib';\n\nimport {messageSW} from './messageSW.js';\nimport {WorkboxEventTarget} from './utils/WorkboxEventTarget.js';\nimport {urlsMatch} from './utils/urlsMatch.js';\nimport {WorkboxEvent, WorkboxLifecycleEventMap} from './utils/WorkboxEvent.js';\n\nimport './_version.js';\n\n// The time a SW must be in the waiting phase before we can conclude\n// `skipWaiting()` wasn't called. This 200 amount wasn't scientifically\n// chosen, but it seems to avoid false positives in my testing.\nconst WAITING_TIMEOUT_DURATION = 200;\n\n// The amount of time after a registration that we can reasonably conclude\n// that the registration didn't trigger an update.\nconst REGISTRATION_TIMEOUT_DURATION = 60000;\n\n// The de facto standard message that a service worker should be listening for\n// to trigger a call to skipWaiting().\nconst SKIP_WAITING_MESSAGE = {type: 'SKIP_WAITING'};\n\n/**\n * A class to aid in handling service worker registration, updates, and\n * reacting to service worker lifecycle events.\n *\n * @fires {@link workbox-window.Workbox#message}\n * @fires {@link workbox-window.Workbox#installed}\n * @fires {@link workbox-window.Workbox#waiting}\n * @fires {@link workbox-window.Workbox#controlling}\n * @fires {@link workbox-window.Workbox#activated}\n * @fires {@link workbox-window.Workbox#redundant}\n * @memberof workbox-window\n */\nclass Workbox extends WorkboxEventTarget {\n  private readonly _scriptURL: string | TrustedScriptURL;\n  private readonly _registerOptions: RegistrationOptions = {};\n  private _updateFoundCount = 0;\n\n  // Deferreds we can resolve later.\n  private readonly _swDeferred: Deferred<ServiceWorker> = new Deferred();\n  private readonly _activeDeferred: Deferred<ServiceWorker> = new Deferred();\n  private readonly _controllingDeferred: Deferred<ServiceWorker> =\n    new Deferred();\n\n  private _registrationTime: DOMHighResTimeStamp = 0;\n  private _isUpdate?: boolean;\n  private _compatibleControllingSW?: ServiceWorker;\n  private _registration?: ServiceWorkerRegistration;\n  private _sw?: ServiceWorker;\n  private readonly _ownSWs: Set<ServiceWorker> = new Set();\n  private _externalSW?: ServiceWorker;\n  private _waitingTimeout?: number;\n\n  /**\n   * Creates a new Workbox instance with a script URL and service worker\n   * options. The script URL and options are the same as those used when\n   * calling [navigator.serviceWorker.register(scriptURL, options)](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register).\n   *\n   * @param {string|TrustedScriptURL} scriptURL The service worker script\n   *     associated with this instance. Using a\n   *     [`TrustedScriptURL`](https://web.dev/trusted-types/) is supported.\n   * @param {Object} [registerOptions] The service worker options associated\n   *     with this instance.\n   */\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  constructor(scriptURL: string | TrustedScriptURL, registerOptions: {} = {}) {\n    super();\n\n    this._scriptURL = scriptURL;\n    this._registerOptions = registerOptions;\n\n    // Add a message listener immediately since messages received during\n    // page load are buffered only until the DOMContentLoaded event:\n    // https://github.com/GoogleChrome/workbox/issues/2202\n    navigator.serviceWorker.addEventListener('message', this._onMessage);\n  }\n\n  /**\n   * Registers a service worker for this instances script URL and service\n   * worker options. By default this method delays registration until after\n   * the window has loaded.\n   *\n   * @param {Object} [options]\n   * @param {Function} [options.immediate=false] Setting this to true will\n   *     register the service worker immediately, even if the window has\n   *     not loaded (not recommended).\n   */\n  async register({immediate = false} = {}): Promise<\n    ServiceWorkerRegistration | undefined\n  > {\n    if (process.env.NODE_ENV !== 'production') {\n      if (this._registrationTime) {\n        logger.error(\n          'Cannot re-register a Workbox instance after it has ' +\n            'been registered. Create a new instance instead.',\n        );\n        return;\n      }\n    }\n\n    if (!immediate && document.readyState !== 'complete') {\n      await new Promise((res) => window.addEventListener('load', res));\n    }\n\n    // Set this flag to true if any service worker was controlling the page\n    // at registration time.\n    this._isUpdate = Boolean(navigator.serviceWorker.controller);\n\n    // Before registering, attempt to determine if a SW is already controlling\n    // the page, and if that SW script (and version, if specified) matches this\n    // instance's script.\n    this._compatibleControllingSW = this._getControllingSWIfCompatible();\n\n    this._registration = await this._registerScript();\n\n    // If we have a compatible controller, store the controller as the \"own\"\n    // SW, resolve active/controlling deferreds and add necessary listeners.\n    if (this._compatibleControllingSW) {\n      this._sw = this._compatibleControllingSW;\n      this._activeDeferred.resolve(this._compatibleControllingSW);\n      this._controllingDeferred.resolve(this._compatibleControllingSW);\n\n      this._compatibleControllingSW.addEventListener(\n        'statechange',\n        this._onStateChange,\n        {once: true},\n      );\n    }\n\n    // If there's a waiting service worker with a matching URL before the\n    // `updatefound` event fires, it likely means that this site is open\n    // in another tab, or the user refreshed the page (and thus the previous\n    // page wasn't fully unloaded before this page started loading).\n    // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting\n    const waitingSW = this._registration.waiting;\n    if (\n      waitingSW &&\n      urlsMatch(waitingSW.scriptURL, this._scriptURL.toString())\n    ) {\n      // Store the waiting SW as the \"own\" Sw, even if it means overwriting\n      // a compatible controller.\n      this._sw = waitingSW;\n\n      // Run this in the next microtask, so any code that adds an event\n      // listener after awaiting `register()` will get this event.\n      dontWaitFor(\n        Promise.resolve().then(() => {\n          this.dispatchEvent(\n            new WorkboxEvent('waiting', {\n              sw: waitingSW,\n              wasWaitingBeforeRegister: true,\n            }),\n          );\n          if (process.env.NODE_ENV !== 'production') {\n            logger.warn(\n              'A service worker was already waiting to activate ' +\n                'before this script was registered...',\n            );\n          }\n        }),\n      );\n    }\n\n    // If an \"own\" SW is already set, resolve the deferred.\n    if (this._sw) {\n      this._swDeferred.resolve(this._sw);\n      this._ownSWs.add(this._sw);\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      logger.log(\n        'Successfully registered service worker.',\n        this._scriptURL.toString(),\n      );\n\n      if (navigator.serviceWorker.controller) {\n        if (this._compatibleControllingSW) {\n          logger.debug(\n            'A service worker with the same script URL ' +\n              'is already controlling this page.',\n          );\n        } else {\n          logger.debug(\n            'A service worker with a different script URL is ' +\n              'currently controlling the page. The browser is now fetching ' +\n              'the new script now...',\n          );\n        }\n      }\n\n      const currentPageIsOutOfScope = () => {\n        const scopeURL = new URL(\n          this._registerOptions.scope || this._scriptURL.toString(),\n          document.baseURI,\n        );\n        const scopeURLBasePath = new URL('./', scopeURL.href).pathname;\n        return !location.pathname.startsWith(scopeURLBasePath);\n      };\n      if (currentPageIsOutOfScope()) {\n        logger.warn(\n          'The current page is not in scope for the registered ' +\n            'service worker. Was this a mistake?',\n        );\n      }\n    }\n\n    this._registration.addEventListener('updatefound', this._onUpdateFound);\n    navigator.serviceWorker.addEventListener(\n      'controllerchange',\n      this._onControllerChange,\n    );\n\n    return this._registration;\n  }\n\n  /**\n   * Checks for updates of the registered service worker.\n   */\n  async update(): Promise<void> {\n    if (!this._registration) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.error(\n          'Cannot update a Workbox instance without ' +\n            'being registered. Register the Workbox instance first.',\n        );\n      }\n      return;\n    }\n\n    // Try to update registration\n    await this._registration.update();\n  }\n\n  /**\n   * Resolves to the service worker registered by this instance as soon as it\n   * is active. If a service worker was already controlling at registration\n   * time then it will resolve to that if the script URLs (and optionally\n   * script versions) match, otherwise it will wait until an update is found\n   * and activates.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n  get active(): Promise<ServiceWorker> {\n    return this._activeDeferred.promise;\n  }\n\n  /**\n   * Resolves to the service worker registered by this instance as soon as it\n   * is controlling the page. If a service worker was already controlling at\n   * registration time then it will resolve to that if the script URLs (and\n   * optionally script versions) match, otherwise it will wait until an update\n   * is found and starts controlling the page.\n   * Note: the first time a service worker is installed it will active but\n   * not start controlling the page unless `clients.claim()` is called in the\n   * service worker.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n  get controlling(): Promise<ServiceWorker> {\n    return this._controllingDeferred.promise;\n  }\n\n  /**\n   * Resolves with a reference to a service worker that matches the script URL\n   * of this instance, as soon as it's available.\n   *\n   * If, at registration time, there's already an active or waiting service\n   * worker with a matching script URL, it will be used (with the waiting\n   * service worker taking precedence over the active service worker if both\n   * match, since the waiting service worker would have been registered more\n   * recently).\n   * If there's no matching active or waiting service worker at registration\n   * time then the promise will not resolve until an update is found and starts\n   * installing, at which point the installing service worker is used.\n   *\n   * @return {Promise<ServiceWorker>}\n   */\n  getSW(): Promise<ServiceWorker> {\n    // If `this._sw` is set, resolve with that as we want `getSW()` to\n    // return the correct (new) service worker if an update is found.\n    return this._sw !== undefined\n      ? Promise.resolve(this._sw)\n      : this._swDeferred.promise;\n  }\n\n  /**\n   * Sends the passed data object to the service worker registered by this\n   * instance (via {@link workbox-window.Workbox#getSW}) and resolves\n   * with a response (if any).\n   *\n   * A response can be set in a message handler in the service worker by\n   * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n   * returned by `messageSW()`. If no response is set, the promise will never\n   * resolve.\n   *\n   * @param {Object} data An object to send to the service worker\n   * @return {Promise<Object>}\n   */\n  // We might be able to change the 'data' type to Record<string, unknown> in the future.\n  // eslint-disable-next-line @typescript-eslint/ban-types\n  async messageSW(data: object): Promise<any> {\n    const sw = await this.getSW();\n    return messageSW(sw, data);\n  }\n\n  /**\n   * Sends a `{type: 'SKIP_WAITING'}` message to the service worker that's\n   * currently in the `waiting` state associated with the current registration.\n   *\n   * If there is no current registration or no service worker is `waiting`,\n   * calling this will have no effect.\n   */\n  messageSkipWaiting(): void {\n    if (this._registration && this._registration.waiting) {\n      void messageSW(this._registration.waiting, SKIP_WAITING_MESSAGE);\n    }\n  }\n\n  /**\n   * Checks for a service worker already controlling the page and returns\n   * it if its script URL matches.\n   *\n   * @private\n   * @return {ServiceWorker|undefined}\n   */\n  private _getControllingSWIfCompatible() {\n    const controller = navigator.serviceWorker.controller;\n    if (\n      controller &&\n      urlsMatch(controller.scriptURL, this._scriptURL.toString())\n    ) {\n      return controller;\n    } else {\n      return undefined;\n    }\n  }\n\n  /**\n   * Registers a service worker for this instances script URL and register\n   * options and tracks the time registration was complete.\n   *\n   * @private\n   */\n  private async _registerScript() {\n    try {\n      // this._scriptURL may be a TrustedScriptURL, but there's no support for\n      // passing that to register() in lib.dom right now.\n      // https://github.com/GoogleChrome/workbox/issues/2855\n      const reg = await navigator.serviceWorker.register(\n        this._scriptURL as string,\n        this._registerOptions,\n      );\n\n      // Keep track of when registration happened, so it can be used in the\n      // `this._onUpdateFound` heuristic. Also use the presence of this\n      // property as a way to see if `.register()` has been called.\n      this._registrationTime = performance.now();\n\n      return reg;\n    } catch (error) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.error(error);\n      }\n      // Re-throw the error.\n      throw error;\n    }\n  }\n\n  /**\n   * @private\n   */\n  private readonly _onUpdateFound = () => {\n    // `this._registration` will never be `undefined` after an update is found.\n    const registration = this._registration!;\n    const installingSW = registration.installing as ServiceWorker;\n\n    // If the script URL passed to `navigator.serviceWorker.register()` is\n    // different from the current controlling SW's script URL, we know any\n    // successful registration calls will trigger an `updatefound` event.\n    // But if the registered script URL is the same as the current controlling\n    // SW's script URL, we'll only get an `updatefound` event if the file\n    // changed since it was last registered. This can be a problem if the user\n    // opens up the same page in a different tab, and that page registers\n    // a SW that triggers an update. It's a problem because this page has no\n    // good way of knowing whether the `updatefound` event came from the SW\n    // script it registered or from a registration attempt made by a newer\n    // version of the page running in another tab.\n    // To minimize the possibility of a false positive, we use the logic here:\n    const updateLikelyTriggeredExternally =\n      // Since we enforce only calling `register()` once, and since we don't\n      // add the `updatefound` event listener until the `register()` call, if\n      // `_updateFoundCount` is > 0 then it means this method has already\n      // been called, thus this SW must be external\n      this._updateFoundCount > 0 ||\n      // If the script URL of the installing SW is different from this\n      // instance's script URL, we know it's definitely not from our\n      // registration.\n      !urlsMatch(installingSW.scriptURL, this._scriptURL.toString()) ||\n      // If all of the above are false, then we use a time-based heuristic:\n      // Any `updatefound` event that occurs long after our registration is\n      // assumed to be external.\n      performance.now() > this._registrationTime + REGISTRATION_TIMEOUT_DURATION\n        ? // If any of the above are not true, we assume the update was\n          // triggered by this instance.\n          true\n        : false;\n\n    if (updateLikelyTriggeredExternally) {\n      this._externalSW = installingSW;\n      registration.removeEventListener('updatefound', this._onUpdateFound);\n    } else {\n      // If the update was not triggered externally we know the installing\n      // SW is the one we registered, so we set it.\n      this._sw = installingSW;\n      this._ownSWs.add(installingSW);\n      this._swDeferred.resolve(installingSW);\n\n      // The `installing` state isn't something we have a dedicated\n      // callback for, but we do log messages for it in development.\n      if (process.env.NODE_ENV !== 'production') {\n        if (navigator.serviceWorker.controller) {\n          logger.log('Updated service worker found. Installing now...');\n        } else {\n          logger.log('Service worker is installing...');\n        }\n      }\n    }\n\n    // Increment the `updatefound` count, so future invocations of this\n    // method can be sure they were triggered externally.\n    ++this._updateFoundCount;\n\n    // Add a `statechange` listener regardless of whether this update was\n    // triggered externally, since we have callbacks for both.\n    installingSW.addEventListener('statechange', this._onStateChange);\n  };\n\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n  private readonly _onStateChange = (originalEvent: Event) => {\n    // `this._registration` will never be `undefined` after an update is found.\n    const registration = this._registration!;\n    const sw = originalEvent.target as ServiceWorker;\n    const {state} = sw;\n    const isExternal = sw === this._externalSW;\n\n    const eventProps: {\n      sw: ServiceWorker;\n      originalEvent: Event;\n      isUpdate?: boolean;\n      isExternal: boolean;\n    } = {\n      sw,\n      isExternal,\n      originalEvent,\n    };\n    if (!isExternal && this._isUpdate) {\n      eventProps.isUpdate = true;\n    }\n\n    this.dispatchEvent(\n      new WorkboxEvent(state as keyof WorkboxLifecycleEventMap, eventProps),\n    );\n\n    if (state === 'installed') {\n      // This timeout is used to ignore cases where the service worker calls\n      // `skipWaiting()` in the install event, thus moving it directly in the\n      // activating state. (Since all service workers *must* go through the\n      // waiting phase, the only way to detect `skipWaiting()` called in the\n      // install event is to observe that the time spent in the waiting phase\n      // is very short.)\n      // NOTE: we don't need separate timeouts for the own and external SWs\n      // since they can't go through these phases at the same time.\n      this._waitingTimeout = self.setTimeout(() => {\n        // Ensure the SW is still waiting (it may now be redundant).\n        if (state === 'installed' && registration.waiting === sw) {\n          this.dispatchEvent(new WorkboxEvent('waiting', eventProps));\n\n          if (process.env.NODE_ENV !== 'production') {\n            if (isExternal) {\n              logger.warn(\n                'An external service worker has installed but is ' +\n                  'waiting for this client to close before activating...',\n              );\n            } else {\n              logger.warn(\n                'The service worker has installed but is waiting ' +\n                  'for existing clients to close before activating...',\n              );\n            }\n          }\n        }\n      }, WAITING_TIMEOUT_DURATION);\n    } else if (state === 'activating') {\n      clearTimeout(this._waitingTimeout);\n      if (!isExternal) {\n        this._activeDeferred.resolve(sw);\n      }\n    }\n\n    if (process.env.NODE_ENV !== 'production') {\n      switch (state) {\n        case 'installed':\n          if (isExternal) {\n            logger.warn(\n              'An external service worker has installed. ' +\n                'You may want to suggest users reload this page.',\n            );\n          } else {\n            logger.log('Registered service worker installed.');\n          }\n          break;\n        case 'activated':\n          if (isExternal) {\n            logger.warn('An external service worker has activated.');\n          } else {\n            logger.log('Registered service worker activated.');\n            if (sw !== navigator.serviceWorker.controller) {\n              logger.warn(\n                'The registered service worker is active but ' +\n                  'not yet controlling the page. Reload or run ' +\n                  '`clients.claim()` in the service worker.',\n              );\n            }\n          }\n          break;\n        case 'redundant':\n          if (sw === this._compatibleControllingSW) {\n            logger.log('Previously controlling service worker now redundant!');\n          } else if (!isExternal) {\n            logger.log('Registered service worker now redundant!');\n          }\n          break;\n      }\n    }\n  };\n\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n  private readonly _onControllerChange = (originalEvent: Event) => {\n    const sw = this._sw;\n    const isExternal = sw !== navigator.serviceWorker.controller;\n\n    // Unconditionally dispatch the controlling event, with isExternal set\n    // to distinguish between controller changes due to the initial registration\n    // vs. an update-check or other tab's registration.\n    // See https://github.com/GoogleChrome/workbox/issues/2786\n    this.dispatchEvent(\n      new WorkboxEvent('controlling', {\n        isExternal,\n        originalEvent,\n        sw,\n        isUpdate: this._isUpdate,\n      }),\n    );\n\n    if (!isExternal) {\n      if (process.env.NODE_ENV !== 'production') {\n        logger.log('Registered service worker now controlling this page.');\n      }\n      this._controllingDeferred.resolve(sw);\n    }\n  };\n\n  /**\n   * @private\n   * @param {Event} originalEvent\n   */\n  private readonly _onMessage = async (originalEvent: MessageEvent) => {\n    // Can't change type 'any' of data.\n    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n    const {data, ports, source} = originalEvent;\n\n    // Wait until there's an \"own\" service worker. This is used to buffer\n    // `message` events that may be received prior to calling `register()`.\n    await this.getSW();\n\n    // If the service worker that sent the message is in the list of own\n    // service workers for this instance, dispatch a `message` event.\n    // NOTE: we check for all previously owned service workers rather than\n    // just the current one because some messages (e.g. cache updates) use\n    // a timeout when sent and may be delayed long enough for a service worker\n    // update to be found.\n    if (this._ownSWs.has(source as ServiceWorker)) {\n      this.dispatchEvent(\n        new WorkboxEvent('message', {\n          // Can't change type 'any' of data.\n          // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n          data,\n          originalEvent,\n          ports,\n          sw: source as ServiceWorker,\n        }),\n      );\n    }\n  };\n}\n\nexport {Workbox};\n\n// The jsdoc comments below outline the events this instance may dispatch:\n// -----------------------------------------------------------------------\n\n/**\n * The `message` event is dispatched any time a `postMessage` is received.\n *\n * @event workbox-window.Workbox#message\n * @type {WorkboxEvent}\n * @property {*} data The `data` property from the original `message` event.\n * @property {Event} originalEvent The original [`message`]{@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent}\n *     event.\n * @property {string} type `message`.\n * @property {MessagePort[]} ports The `ports` value from `originalEvent`.\n * @property {Workbox} target The `Workbox` instance.\n */\n\n/**\n * The `installed` event is dispatched if the state of a\n * {@link workbox-window.Workbox} instance's\n * {@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw|registered service worker}\n * changes to `installed`.\n *\n * Then can happen either the very first time a service worker is installed,\n * or after an update to the current service worker is found. In the case\n * of an update being found, the event's `isUpdate` property will be `true`.\n *\n * @event workbox-window.Workbox#installed\n * @type {WorkboxEvent}\n * @property {ServiceWorker} sw The service worker instance.\n * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}\n *     event.\n * @property {boolean|undefined} isUpdate True if a service worker was already\n *     controlling when this `Workbox` instance called `register()`.\n * @property {boolean|undefined} isExternal True if this event is associated\n *     with an [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}.\n * @property {string} type `installed`.\n * @property {Workbox} target The `Workbox` instance.\n */\n\n/**\n * The `waiting` event is dispatched if the state of a\n * {@link workbox-window.Workbox} instance's\n * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}\n * changes to `installed` and then doesn't immediately change to `activating`.\n * It may also be dispatched if a service worker with the same\n * [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}\n * was already waiting when the {@link workbox-window.Workbox#register}\n * method was called.\n *\n * @event workbox-window.Workbox#waiting\n * @type {WorkboxEvent}\n * @property {ServiceWorker} sw The service worker instance.\n * @property {Event|undefined} originalEvent The original\n *    [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}\n *     event, or `undefined` in the case where the service worker was waiting\n *     to before `.register()` was called.\n * @property {boolean|undefined} isUpdate True if a service worker was already\n *     controlling when this `Workbox` instance called `register()`.\n * @property {boolean|undefined} isExternal True if this event is associated\n *     with an [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}.\n * @property {boolean|undefined} wasWaitingBeforeRegister True if a service worker with\n *     a matching `scriptURL` was already waiting when this `Workbox`\n *     instance called `register()`.\n * @property {string} type `waiting`.\n * @property {Workbox} target The `Workbox` instance.\n */\n\n/**\n * The `controlling` event is dispatched if a\n * [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}\n * fires on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer}\n * and the [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL}\n * of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller}\n * matches the `scriptURL` of the `Workbox` instance's\n * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}.\n *\n * @event workbox-window.Workbox#controlling\n * @type {WorkboxEvent}\n * @property {ServiceWorker} sw The service worker instance.\n * @property {Event} originalEvent The original [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange}\n *     event.\n * @property {boolean|undefined} isUpdate True if a service worker was already\n *     controlling when this service worker was registered.\n * @property {boolean|undefined} isExternal True if this event is associated\n *     with an [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}.\n * @property {string} type `controlling`.\n * @property {Workbox} target The `Workbox` instance.\n */\n\n/**\n * The `activated` event is dispatched if the state of a\n * {@link workbox-window.Workbox} instance's\n * {@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw|registered service worker}\n * changes to `activated`.\n *\n * @event workbox-window.Workbox#activated\n * @type {WorkboxEvent}\n * @property {ServiceWorker} sw The service worker instance.\n * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}\n *     event.\n * @property {boolean|undefined} isUpdate True if a service worker was already\n *     controlling when this `Workbox` instance called `register()`.\n * @property {boolean|undefined} isExternal True if this event is associated\n *     with an [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-window#when_an_unexpected_version_of_the_service_worker_is_found}.\n * @property {string} type `activated`.\n * @property {Workbox} target The `Workbox` instance.\n */\n\n/**\n * The `redundant` event is dispatched if the state of a\n * {@link workbox-window.Workbox} instance's\n * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}\n * changes to `redundant`.\n *\n * @event workbox-window.Workbox#redundant\n * @type {WorkboxEvent}\n * @property {ServiceWorker} sw The service worker instance.\n * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange}\n *     event.\n * @property {boolean|undefined} isUpdate True if a service worker was already\n *     controlling when this `Workbox` instance called `register()`.\n * @property {string} type `redundant`.\n * @property {Workbox} target The `Workbox` instance.\n */\n"
  },
  {
    "path": "packages/workbox-window/src/_version.ts",
    "content": "// @ts-ignore\ntry{self['workbox:window:7.4.0']&&_()}catch(e){}"
  },
  {
    "path": "packages/workbox-window/src/index.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {messageSW} from './messageSW.js';\nimport {Workbox} from './Workbox.js';\n\nimport './_version.js';\n\n/**\n * @module workbox-window\n */\nexport {messageSW, Workbox};\n\n// See https://github.com/GoogleChrome/workbox/issues/2770\nexport * from './utils/WorkboxEvent.js';\n"
  },
  {
    "path": "packages/workbox-window/src/messageSW.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport './_version.js';\n\n/**\n * Sends a data object to a service worker via `postMessage` and resolves with\n * a response (if any).\n *\n * A response can be set in a message handler in the service worker by\n * calling `event.ports[0].postMessage(...)`, which will resolve the promise\n * returned by `messageSW()`. If no response is set, the promise will not\n * resolve.\n *\n * @param {ServiceWorker} sw The service worker to send the message to.\n * @param {Object} data An object to send to the service worker.\n * @return {Promise<Object|undefined>}\n * @memberof workbox-window\n */\n// Better not change type of data.\n// eslint-disable-next-line @typescript-eslint/ban-types\nfunction messageSW(sw: ServiceWorker, data: {}): Promise<any> {\n  return new Promise((resolve) => {\n    const messageChannel = new MessageChannel();\n    messageChannel.port1.onmessage = (event: MessageEvent) => {\n      resolve(event.data);\n    };\n    sw.postMessage(data, [messageChannel.port2]);\n  });\n}\n\nexport {messageSW};\n"
  },
  {
    "path": "packages/workbox-window/src/utils/WorkboxEvent.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxEventTarget} from './WorkboxEventTarget.js';\nimport '../_version.js';\n\n/**\n * A minimal `Event` subclass shim.\n * This doesn't *actually* subclass `Event` because not all browsers support\n * constructable `EventTarget`, and using a real `Event` will error.\n * @private\n */\nexport class WorkboxEvent<K extends keyof WorkboxEventMap> {\n  target?: WorkboxEventTarget;\n  sw?: ServiceWorker;\n  originalEvent?: Event;\n  isExternal?: boolean;\n\n  constructor(\n    public type: K,\n    props: Omit<WorkboxEventMap[K], 'target' | 'type'>,\n  ) {\n    Object.assign(this, props);\n  }\n}\n\nexport interface WorkboxMessageEvent extends WorkboxEvent<'message'> {\n  data: any;\n  originalEvent: Event;\n  ports: readonly MessagePort[];\n}\n\nexport interface WorkboxLifecycleEvent\n  extends WorkboxEvent<keyof WorkboxLifecycleEventMap> {\n  isUpdate?: boolean;\n}\n\nexport interface WorkboxLifecycleWaitingEvent extends WorkboxLifecycleEvent {\n  wasWaitingBeforeRegister?: boolean;\n}\n\nexport interface WorkboxLifecycleEventMap {\n  installing: WorkboxLifecycleEvent;\n  installed: WorkboxLifecycleEvent;\n  waiting: WorkboxLifecycleWaitingEvent;\n  activating: WorkboxLifecycleEvent;\n  activated: WorkboxLifecycleEvent;\n  controlling: WorkboxLifecycleEvent;\n  redundant: WorkboxLifecycleEvent;\n}\n\nexport interface WorkboxEventMap extends WorkboxLifecycleEventMap {\n  message: WorkboxMessageEvent;\n}\n"
  },
  {
    "path": "packages/workbox-window/src/utils/WorkboxEventTarget.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxEvent, WorkboxEventMap} from './WorkboxEvent.js';\n\nexport type ListenerCallback = (event: WorkboxEvent<any>) => any;\n\n/**\n * A minimal `EventTarget` shim.\n * This is necessary because not all browsers support constructable\n * `EventTarget`, so using a real `EventTarget` will error.\n * @private\n */\nexport class WorkboxEventTarget {\n  private readonly _eventListenerRegistry: Map<\n    keyof WorkboxEventMap,\n    Set<ListenerCallback>\n  > = new Map();\n\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   * @private\n   */\n  addEventListener<K extends keyof WorkboxEventMap>(\n    type: K,\n    listener: (event: WorkboxEventMap[K]) => any,\n  ): void {\n    const foo = this._getEventListenersByType(type);\n    foo.add(listener as ListenerCallback);\n  }\n\n  /**\n   * @param {string} type\n   * @param {Function} listener\n   * @private\n   */\n  removeEventListener<K extends keyof WorkboxEventMap>(\n    type: K,\n    listener: (event: WorkboxEventMap[K]) => any,\n  ): void {\n    this._getEventListenersByType(type).delete(listener as ListenerCallback);\n  }\n\n  /**\n   * @param {Object} event\n   * @private\n   */\n  dispatchEvent(event: WorkboxEvent<any>): void {\n    event.target = this;\n\n    const listeners = this._getEventListenersByType(event.type);\n    for (const listener of listeners) {\n      listener(event);\n    }\n  }\n\n  /**\n   * Returns a Set of listeners associated with the passed event type.\n   * If no handlers have been registered, an empty Set is returned.\n   *\n   * @param {string} type The event type.\n   * @return {Set<ListenerCallback>} An array of handler functions.\n   * @private\n   */\n  private _getEventListenersByType(type: keyof WorkboxEventMap) {\n    if (!this._eventListenerRegistry.has(type)) {\n      this._eventListenerRegistry.set(type, new Set());\n    }\n    return this._eventListenerRegistry.get(type)!;\n  }\n}\n"
  },
  {
    "path": "packages/workbox-window/src/utils/urlsMatch.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport '../_version.js';\n\n/**\n * Returns true if two URLs have the same `.href` property. The URLS can be\n * relative, and if they are the current location href is used to resolve URLs.\n *\n * @private\n * @param {string} url1\n * @param {string} url2\n * @return {boolean}\n */\nexport function urlsMatch(url1: string, url2: string): boolean {\n  const {href} = location;\n  return new URL(url1, href).href === new URL(url2, href).href;\n}\n"
  },
  {
    "path": "packages/workbox-window/tsconfig.json",
    "content": "{\n  \"extends\": \"../../tsconfig\",\n  \"compilerOptions\": {\n    \"lib\": [\"es2017\", \"dom\"],\n    \"outDir\": \"./\",\n    \"rootDir\": \"./src\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"include\": [\"src/**/*.ts\"],\n  \"references\": [\n    {\n      \"path\": \"../workbox-core/\"\n    }\n  ]\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "module.exports = {\n  arrowParens: 'always',\n  bracketSpacing: false,\n  printWidth: 80,\n  quoteProps: 'consistent',\n  semi: true,\n  singleQuote: true,\n  tabWidth: 2,\n  trailingComma: 'all',\n};\n"
  },
  {
    "path": "test/all/node/test-exports.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst acorn = require('acorn');\nconst {expect} = require('chai');\nconst fs = require('fs-extra');\nconst path = require('path');\nconst {globSync} = require('glob');\nconst {getPackages} = require('../../../gulp-tasks/utils/get-packages');\n\ndescribe(`[all] Window and SW packages`, function () {\n  // Reading files can be slow.\n  this.timeout(5 * 1000);\n\n  const windowAndSWPackages = [\n    ...getPackages({type: 'sw'}),\n    ...getPackages({type: 'window'}),\n  ];\n\n  it(`should have top a level module for every export in index.mjs (and vise-versa)`, async function () {\n    for (const pkg of windowAndSWPackages) {\n      const packagePath = path.join(\n        __dirname,\n        '..',\n        '..',\n        '..',\n        'packages',\n        pkg.name,\n      );\n\n      // TODO(philipwalton): remove this once all packages are converted to\n      // typescript or typescript adds `.mjs` support.\n      const ext = 'types' in pkg ? 'js' : 'mjs';\n\n      const indexFile = path.join(packagePath, `index.${ext}`);\n      const indexContents = await fs.readFile(indexFile, 'utf-8');\n\n      // Use the acorn parser to generate a list of named exports.\n      const namedExports = [];\n      const indexAST = acorn.parse(indexContents, {\n        ecmaVersion: 6,\n        sourceType: 'module',\n      });\n      for (const node of indexAST.body) {\n        if (node.type === 'ExportDefaultDeclaration') {\n          throw new Error(\n            `'index.${ext}' files cannot contain default exports`,\n          );\n        }\n        if (node.type === 'ExportNamedDeclaration') {\n          for (const specifier of node.specifiers) {\n            namedExports.push(specifier.exported.name);\n          }\n        }\n      }\n\n      // Inspect the package directory to get a list of top-level, public\n      // module basenames.\n      const topLevelFiles = globSync(`*.${ext}`, {\n        ignore: ['index', 'types', '_types', '_version'].map(\n          (file) => `${file}.${ext}`,\n        ),\n        cwd: packagePath,\n      }).map((file) => path.basename(file, `.${ext}`));\n\n      // Assert there's a 1-to-1 mapping between exports and top-level files.\n      expect(namedExports.sort()).to.deep.equal(topLevelFiles.sort());\n    }\n  });\n\n  it(`should have top a level module for every export in _private.mjs (and vise-versa)`, async function () {\n    for (const pkg of windowAndSWPackages) {\n      // TODO(philipwalton): remove this once all packages are converted to\n      // typescript or typescript adds `.mjs` support.\n      const ext = 'types' in pkg ? 'js' : 'mjs';\n\n      const packagePath = path.join(\n        __dirname,\n        '..',\n        '..',\n        '..',\n        'packages',\n        pkg.name,\n      );\n      const privateFile = path.join(packagePath, `_private.${ext}`);\n\n      // Only some packages have a `_private.mjs` module.\n      if (!fs.existsSync(privateFile)) {\n        continue;\n      }\n\n      const privateContents = await fs.readFile(privateFile, 'utf-8');\n\n      // Use the acorn parser to generate a list of named exports.\n      const namedExports = [];\n      const indexAST = acorn.parse(privateContents, {\n        ecmaVersion: 6,\n        sourceType: 'module',\n      });\n      for (const node of indexAST.body) {\n        if (node.type === 'ExportDefaultDeclaration') {\n          throw new Error(\n            `'_private.${ext}' files cannot contain default exports`,\n          );\n        }\n        if (node.type === 'ExportNamedDeclaration') {\n          if (node.specifiers.length === 0) {\n            throw new Error(\n              `'_private.${ext}' files may only contain a single, named-export block`,\n            );\n          }\n          for (const specifier of node.specifiers) {\n            namedExports.push(specifier.exported.name);\n          }\n        }\n      }\n\n      // Inspect the package directory to get a list of top-level, public\n      // module basenames.\n      const privateDirectoryPath = path.join(packagePath, '_private');\n      const topLevelFiles = globSync(`*.${ext}`, {\n        cwd: privateDirectoryPath,\n      }).map((file) => path.basename(file, `.${ext}`));\n\n      // Assert there's a 1-to-1 mapping between exports and top-level files.\n      expect(namedExports.sort()).to.deep.equal(topLevelFiles.sort());\n    }\n  });\n});\n"
  },
  {
    "path": "test/all/node/test-jsdocs.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {expect} = require('chai');\nconst fse = require('fs-extra');\nconst {globSync} = require('glob');\nconst upath = require('path');\n\nconst {docs_build} = require('../../../gulp-tasks/docs.js');\n\ndescribe('[all] JSDocs', function () {\n  it('should run JSDocs and have no unexpected results', async function () {\n    // Windows is super unhappy with the JSDocs build pipeline.\n    // With gulp.cmd in spawn, the query string used by the baseline template\n    // causes issues.\n    if (process.platform === 'win32') {\n      this.skip();\n      return;\n    }\n\n    this.timeout(60 * 1000);\n\n    const projectRoot = upath.join(__dirname, '..', '..', '..');\n    const docsPath = upath.join(projectRoot, 'docs');\n    await docs_build();\n\n    const docs = globSync('*.html', {\n      cwd: docsPath,\n    });\n\n    // global.html is only added when the docs have stray global values.\n    expect(\n      docs.includes('global.html'),\n      `'global.html' should not be present in ${docsPath}`,\n    ).to.be.false;\n\n    // On some occasions, module.exports can leak into JSDocs, and breaks\n    // into the final template.\n    const indexAllHTML = await fse.readFile(\n      upath.join(docsPath, 'index-all.html'),\n      'utf8',\n    );\n    expect(\n      indexAllHTML.includes(\n        '<a href=\"module.html#.exports\">module.exports</a>',\n      ),\n      `'module.exports' was found in index-all.html`,\n    ).to.be.false;\n\n    // We document this private method because we expect developers to\n    // override it in their extending classes.\n    const privateMethodAllowlist = ['_handle'];\n\n    // string.matchAll() isn't supported before node v12...\n    const regexp = /<a href=\"([^\"]+)\">/g;\n    let match;\n    while ((match = regexp.exec(indexAllHTML)) !== null) {\n      const href = match[1];\n      if (\n        href.includes('#_') &&\n        !privateMethodAllowlist.some((allow) => href.endsWith(allow))\n      ) {\n        throw new Error(`Private method found in JSDocs: ${href}`);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "test/all/node/test-package.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {expect} = require('chai');\nconst camelCase = require('camelcase');\nconst fs = require('fs-extra');\nconst {globSync} = require('glob');\nconst ol = require('common-tags').oneLine;\nconst upath = require('path');\n\nconst {getPackages} = require('../../../gulp-tasks/utils/get-packages');\nconst constants = require('../../../gulp-tasks/utils/constants');\nconst pkgPathToName = require('../../../gulp-tasks/utils/pkg-path-to-name');\n\ndescribe(`[all] Test package.json`, function () {\n  it(`should expose correct main, browser and module fields`, function () {\n    const packageFiles = globSync('packages/**/package.json', {\n      ignore: ['packages/*/node_modules/**/*'],\n      cwd: upath.join(__dirname, '..', '..', '..'),\n      absolute: true,\n    });\n    packageFiles.forEach((packagePath) => {\n      const pkg = require(packagePath);\n      switch (pkg.workbox.packageType) {\n        case 'sw': {\n          const propertiesToCheck = ['main', 'module'];\n\n          propertiesToCheck.forEach((propertyName) => {\n            if (!pkg[propertyName]) {\n              throw new Error(\n                `The package.json at '${upath.relative(\n                  process.cwd(),\n                  packagePath,\n                )}' isn't exposing a '${propertyName}' property`,\n              );\n            }\n\n            const fullPath = upath.join(\n              upath.dirname(packagePath),\n              pkg[propertyName],\n            );\n            if (!fs.existsSync(fullPath)) {\n              throw new Error(\n                `${upath.relative(\n                  process.cwd(),\n                  packagePath,\n                )} has an invalid '${propertyName}' property: '${\n                  pkg[propertyName]\n                }'`,\n              );\n            }\n          });\n          break;\n        }\n        case 'window': {\n          break;\n        }\n        case 'node': {\n          break;\n        }\n        case 'node_ts': {\n          break;\n        }\n        default:\n          throw new Error(\n            `Unknown package.json workbox.packageType: '${\n              pkg.workbox.packageType\n            }' in ${upath.relative(process.cwd(), packagePath)}`,\n          );\n      }\n    });\n  });\n\n  it(`should import _version.mjs in each .mjs file`, function () {\n    // Find directories with package.json file\n    const packageFiles = globSync('packages/*/package.json', {\n      ignore: ['packages/*/node_modules/**/*'],\n      cwd: upath.join(__dirname, '..', '..', '..'),\n      absolute: true,\n    });\n    packageFiles.forEach((packagePath) => {\n      // skip non-sw modules\n      const pkg = require(packagePath);\n      if (pkg.workbox.packageType !== 'sw') {\n        return;\n      }\n\n      // TODO(philipwalton): remove this once all packages are converted to\n      // typescript or typescript adds `.mjs` support.\n      const ext = 'types' in pkg ? 'js' : 'mjs';\n\n      // Glob for all js and mjs files in the package\n      const packageName = pkgPathToName(upath.dirname(packagePath));\n      const packageFiles = globSync(`packages/${packageName}/**/*.${ext}`, {\n        ignore: [\n          'packages/*/node_modules/**/*',\n          `packages/*/_version.${ext}`,\n          `packages/*/${constants.PACKAGE_BUILD_DIRNAME}/**/*`,\n        ],\n        cwd: upath.join(__dirname, '..', '..', '..'),\n        absolute: true,\n      });\n\n      const importRegex = new RegExp(`import\\\\s+'[./]+_version\\\\.${ext}';`);\n\n      // Find the version in each file.\n      packageFiles.forEach((filePath) => {\n        const fileContents = fs.readFileSync(filePath).toString();\n        const results = importRegex.exec(fileContents);\n        if (!results) {\n          throw new Error(\n            `Unable to find the workbox version in '${upath.relative(\n              process.cwd(),\n              filePath,\n            )}'`,\n          );\n        }\n      });\n    });\n  });\n\n  it(`should contain the file version`, function () {\n    const versionRegex = /['|\"]workbox:((?:[^:'\"]*|:)*)['|\"]/;\n\n    // Find directories with package.json file\n    const packageFiles = globSync('packages/*/package.json', {\n      ignore: ['packages/*/node_modules/**/*'],\n      cwd: upath.join(__dirname, '..', '..', '..'),\n      absolute: true,\n    });\n    packageFiles.forEach((packagePath) => {\n      // skip non-browser modules\n      const pkg = require(packagePath);\n      if (pkg.workbox.packageType !== 'sw') {\n        return;\n      }\n\n      // Glob for all js and mjs files in the package\n      const packageName = pkgPathToName(upath.dirname(packagePath));\n      const packageFiles = globSync(\n        `packages/${packageName}/${constants.PACKAGE_BUILD_DIRNAME}/**/*.{js,mjs}`,\n        {\n          ignore: ['packages/*/node_modules/**/*'],\n          cwd: upath.join(__dirname, '..', '..', '..'),\n          absolute: true,\n        },\n      );\n\n      // Find the version in each file.\n      packageFiles.forEach((filePath) => {\n        const fileContents = fs.readFileSync(filePath).toString();\n        const results = versionRegex.exec(fileContents);\n        if (!results) {\n          throw new Error(\n            `Unable to find the workbox version in '${upath.relative(\n              process.cwd(),\n              filePath,\n            )}'`,\n          );\n        }\n\n        const metadata = results[1].split(':');\n        try {\n          expect(metadata[0]).to.equal(pkg.name.replace('workbox-', ''));\n          expect(metadata[1]).to.equal(pkg.version);\n        } catch (err) {\n          throw new Error(`Invalid file version ${filePath}: ${metadata}`);\n        }\n      });\n    });\n  });\n\n  it(`should have correct details in _version.mjs`, function () {\n    const versionRegex = /['|\"]workbox:((?:[^:'\"]*|:)*)['|\"]/;\n\n    // Find directories with package.json file\n    const packageFiles = globSync('packages/*/package.json', {\n      ignore: ['packages/*/node_modules/**/*'],\n      cwd: upath.join(__dirname, '..', '..', '..'),\n      absolute: true,\n    });\n    packageFiles.forEach((packagePath) => {\n      // skip non-browser modules\n      const pkg = require(packagePath);\n      if (pkg.workbox.packageType !== 'sw') {\n        return;\n      }\n\n      // TODO(philipwalton): remove this once all packages are converted to\n      // typescript or typescript adds `.mjs` support.\n      const ext = 'types' in pkg ? 'js' : 'mjs';\n\n      const packageName = pkgPathToName(upath.dirname(packagePath));\n      const versionFiles = globSync(`packages/${packageName}/_version.${ext}`, {\n        ignore: ['packages/*/node_modules/**/*'],\n        cwd: upath.join(__dirname, '..', '..', '..'),\n        absolute: true,\n      });\n\n      // Find the version in each file.\n      versionFiles.forEach((filePath) => {\n        const fileContents = fs.readFileSync(filePath).toString();\n        const results = versionRegex.exec(fileContents);\n        if (!results) {\n          throw new Error(\n            `Unable to find the workbox version in '${upath.relative(\n              process.cwd(),\n              filePath,\n            )}'`,\n          );\n        }\n\n        const metadata = results[1].split(':');\n        try {\n          expect(metadata[0]).to.equal(pkg.name.replace('workbox-', ''));\n          expect(metadata[1]).to.equal(pkg.version);\n        } catch (err) {\n          throw new Error(`Invalid file version ${filePath}: ${metadata}`);\n        }\n      });\n    });\n  });\n\n  it(`should only use a namespace that matches its package name`, function () {\n    const pkgs = getPackages({type: 'sw'});\n\n    for (const pkg of pkgs) {\n      // These rules don't apply to workbox-sw\n      if (pkg.name === 'workbox-sw') continue;\n\n      // Remove the `workbox-` prefix.\n      const pkgNameSuffix = pkg.name.replace(/^workbox-/, '');\n\n      // Remvoe the `workbox.` prefix.\n      const pkgNamespaceSuffix = pkg.workbox.browserNamespace.replace(\n        /^workbox\\./,\n        '',\n      );\n\n      if (camelCase(pkgNameSuffix) !== pkgNamespaceSuffix) {\n        throw new Error(ol`Invalid browser namespace:\n            ${pkg.workbox.browserNamespace}. The browser namespace must include\n            the package name camelCased (${camelCase(pkgNameSuffix)}).`);\n      }\n    }\n  });\n});\n"
  },
  {
    "path": "test/all/node/test-prod-builds.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {oneLine} = require('common-tags');\nconst constants = require('../../../gulp-tasks/utils/constants');\nconst fse = require('fs-extra');\nconst {globSync} = require('glob');\nconst logHelper = require('../../../infra/utils/log-helper');\nconst path = require('path');\n\ndescribe(`[all] prod builds`, function () {\n  const buildFiles = globSync(\n    `packages/*/${constants.PACKAGE_BUILD_DIRNAME}/*.prod.js`,\n    {\n      ignore: ['packages/*/node_modules/**/*'],\n      cwd: path.join(__dirname, '..', '..', '..'),\n      absolute: true,\n    },\n  );\n\n  it(`should not have files with \"console\" or \"%cworwbox\"`, function () {\n    const invalidFiles = [];\n    buildFiles.forEach((filePath) => {\n      const fileContents = fse.readFileSync(filePath).toString();\n      if (\n        (fileContents.indexOf(`console`) > -1 &&\n          // See https://github.com/GoogleChrome/workbox/issues/2259\n          !filePath.includes('workbox-precaching')) ||\n        fileContents.indexOf(`%cworkbox`) > -1\n      ) {\n        invalidFiles.push(filePath);\n      }\n    });\n\n    if (invalidFiles.length > 0) {\n      logHelper.error(\n        `Files with 'console' in them\\n`,\n        JSON.stringify(invalidFiles, null, 2),\n      );\n      throw new Error(oneLine`\n        Found ${invalidFiles.length} files with \"console\" or \"%cworkbox\" in\n        the final build. Please ensure all 'logger' calls are wrapped in a\n        \"if (process.env.NODE_ENV !== 'production') {...}\" conditional.\n      `);\n    }\n  });\n\n  it(`should not have files with hasOwnProperty`, function () {\n    const invalidFiles = [];\n    buildFiles.forEach((filePath) => {\n      const fileContents = fse.readFileSync(filePath).toString();\n      if (fileContents.indexOf(`.hasOwnProperty('default')`) !== -1) {\n        invalidFiles.push(filePath);\n      }\n    });\n\n    if (invalidFiles.length > 0) {\n      logHelper.error(\n        `Files with 'hasOwnProperty('default')' in them\\n`,\n        JSON.stringify(invalidFiles, null, 2),\n      );\n      throw new Error(oneLine`\n        Found ${invalidFiles.length} files with \"hasOwnProperty('default')\"\n        in the final build. Please convert these to named exports to be friendly\n        to Rollup.\n      `);\n    }\n  });\n});\n"
  },
  {
    "path": "test/all/node/test-yarn-installation.js",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst execa = require('execa');\nconst tempy = require('tempy');\nconst upath = require('upath');\n\nconst packagesToInstall = [\n  'workbox-build',\n  'workbox-cli',\n  'workbox-webpack-plugin',\n];\n\nlet temporaryDirectory;\n\ndescribe('[all] Yarn Installation', function () {\n  before(async function () {\n    temporaryDirectory = await tempy.directory();\n  });\n\n  for (const packageToInstall of packagesToInstall) {\n    it(`should install ${packageToInstall} using yarn`, async function () {\n      this.timeout(5 * 60 * 1000);\n\n      try {\n        const packagePath = upath.resolve('packages', packageToInstall);\n        await execa('yarn', ['add', packagePath], {cwd: temporaryDirectory});\n      } catch (error) {\n        if (error.code === 'ENOENT') {\n          // Skip the test if yarn isn't installed.\n          // (It will always be installed on GitHub Actions.)\n          this.skip();\n        } else {\n          throw error;\n        }\n      }\n    });\n  }\n});\n"
  },
  {
    "path": "test/workbox-background-sync/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst waitUntil = require('../../../infra/testing/wait-until');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-background-sync]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-background-sync/sw/');\n  });\n});\n\ndescribe(`[workbox-background-sync] Load and use Background Sync`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-background-sync/static/basic-example/`;\n  const swURL = `${testingURL}sw.js`;\n\n  let requestCounter;\n  beforeEach(function () {\n    requestCounter = server.startCountingRequests();\n  });\n  afterEach(function () {\n    server.stopCountingRequests(requestCounter);\n  });\n\n  it(`should load a page with service worker`, async function () {\n    // Load the page and wait for the first service worker to register and activate.\n    await webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n\n    const url = `/test/workbox-background-sync/static/basic-example/example.txt`;\n    const err = await webdriver.executeAsyncScript((url, cb) => {\n      return fetch(url)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    }, url);\n\n    expect(err).to.not.exist;\n\n    await waitUntil(\n      () => {\n        const count = requestCounter.getURLCount(url);\n        return count > 0;\n      },\n      20,\n      500,\n    );\n  });\n});\n"
  },
  {
    "path": "test/workbox-background-sync/static/basic-example/example.txt",
    "content": "example.txt"
  },
  {
    "path": "test/workbox-background-sync/static/basic-example/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-background-sync/static/basic-example/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nconst queue = new workbox.backgroundSync.Queue('myQueueName');\n\nself.addEventListener('fetch', (event) => {\n  const pathname = new URL(event.request.url).pathname;\n  if (\n    pathname ===\n    '/test/workbox-background-sync/static/basic-example/example.txt'\n  ) {\n    const queuePromise = (async () => {\n      await queue.pushRequest({request: event.request});\n      // This is a horrible hack :(\n      // In non-sync supporting browsers we only replay requests when the SW starts up\n      // but there is no API to force close a service worker, so just force a replay in\n      // this situation to \"fake\" a sw starting up......\n      if (!('sync' in registration)) {\n        await queue.replayRequests();\n      }\n    })();\n\n    event.respondWith(Promise.resolve(new Response(`Added to BG Sync`)));\n    event.waitUntil(queuePromise);\n  }\n});\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-background-sync/sw/lib/test-QueueDb.mjs",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {QueueDb} from 'workbox-background-sync/lib/QueueDb.mjs';\nimport {openDB, deleteDB} from 'idb';\n\ndescribe(`QueueDb`, () => {\n  let db = null;\n\n  const requestData1 = {\n    url: `${location.origin}/one`,\n    requestInit: {\n      mode: 'cors',\n    },\n  };\n\n  const requestData2 = {\n    url: `${location.origin}/two`,\n    requestInit: {\n      mode: 'cors',\n    },\n  };\n\n  const requestData3 = {\n    url: `${location.origin}/three`,\n    requestInit: {\n      mode: 'cors',\n    },\n  };\n\n  const requestData4 = {\n    url: `${location.origin}/four`,\n    requestInit: {\n      mode: 'cors',\n    },\n  };\n\n  const requestData5 = {\n    url: `${location.origin}/five`,\n    requestInit: {\n      mode: 'cors',\n    },\n  };\n\n  const entry1 = {\n    queueName: 'a',\n    requestData: requestData1,\n    timestamp: 1000,\n    metadata: {name: 'meta1'},\n  };\n\n  const entry2 = {\n    queueName: 'a',\n    requestData: requestData2,\n    timestamp: 2000,\n    metadata: {name: 'meta2'},\n  };\n\n  const entry3 = {\n    queueName: 'a',\n    requestData: requestData3,\n    timestamp: 3000,\n    metadata: {name: 'meta3'},\n  };\n\n  const entry4 = {\n    queueName: 'b',\n    requestData: requestData4,\n    timestamp: 4000,\n    metadata: {name: 'meta4'},\n  };\n\n  const entry5 = {\n    queueName: 'b',\n    requestData: requestData5,\n    timestamp: 5000,\n    metadata: {name: 'meta5'},\n  };\n\n  beforeEach(async () => {\n    db = await openDB('workbox-background-sync', 3, {\n      upgrade: QueueDb.prototype._upgradeDb,\n    });\n    await db.clear('requests');\n  });\n\n  describe(`_upgradeDb`, () => {\n    const v3Entry = {\n      queueName: 'a',\n      metadata: {\n        one: '1',\n        two: '2',\n      },\n      timestamp: 123,\n      requestData: {\n        url: `${location.origin}/one`,\n        requestInit: {\n          mode: 'cors',\n        },\n      },\n    };\n\n    it(`should handle upgrading from no previous version`, async () => {\n      const dbv3 = await openDB('workbox-background-sync-from-v3', 3, {\n        upgrade: QueueDb.prototype._upgradeDb,\n      });\n\n      let entries = await dbv3.getAll('requests');\n      expect(entries.length).to.equal(0);\n\n      await dbv3.add('requests', v3Entry);\n\n      entries = await dbv3.getAll('requests');\n      expect(entries[0].id).to.equal(1);\n      expect(entries[0].timestamp).to.equal(v3Entry.timestamp);\n      expect(entries[0].metadata).to.deep.equal(v3Entry.metadata);\n      expect(entries[0].queueName).to.equal(v3Entry.queueName);\n      expect(entries[0].requestData).to.deep.equal(v3Entry.requestData);\n\n      await deleteDB('workbox-background-sync-from-v3', {\n        blocked() {\n          dbv3.close();\n        },\n      });\n    });\n\n    it(`should handle upgrading from version 1`, async () => {\n      await deleteDB('workbox-background-sync-from-v1');\n\n      const dbv1 = await openDB('workbox-background-sync-from-v1', 1, {\n        upgrade(db) {\n          db.createObjectStore('requests', {\n            autoIncrement: true,\n          }).createIndex('queueName', 'queueName', {unique: false});\n        },\n      });\n\n      // Add entries in v1 format.\n      await dbv1.add('requests', {\n        queueName: 'a',\n        storableRequest: {\n          url: `${location.origin}/one`,\n          timestamp: 123,\n          requestInit: {\n            method: 'POST',\n            mode: 'cors',\n            headers: {\n              'x-foo': 'bar',\n              'x-qux': 'baz',\n            },\n          },\n        },\n      });\n      await dbv1.add('requests', {\n        queueName: 'b',\n        storableRequest: {\n          url: `${location.origin}/two`,\n          timestamp: 234,\n          requestInit: {\n            mode: 'cors',\n          },\n        },\n      });\n      await dbv1.add('requests', {\n        queueName: 'a',\n        storableRequest: {\n          url: `${location.origin}/three`,\n          timestamp: 345,\n          requestInit: {},\n        },\n      });\n\n      const dbv3 = await openDB('workbox-background-sync-from-v1', 3, {\n        upgrade: QueueDb.prototype._upgradeDb,\n        blocked() {\n          dbv1.close();\n        },\n      });\n\n      let entries = await dbv3.getAll('requests');\n      expect(entries.length).to.equal(0);\n\n      await dbv3.add('requests', v3Entry);\n\n      entries = await dbv3.getAll('requests');\n      expect(entries[0].id).to.equal(1);\n      expect(entries[0].timestamp).to.equal(v3Entry.timestamp);\n      expect(entries[0].metadata).to.deep.equal(v3Entry.metadata);\n      expect(entries[0].queueName).to.equal(v3Entry.queueName);\n      expect(entries[0].requestData).to.deep.equal(v3Entry.requestData);\n\n      await deleteDB('workbox-background-sync-from-v1', {\n        blocked() {\n          dbv3.close();\n        },\n      });\n    });\n\n    it(`should handle upgrading from version 2`, async () => {\n      await deleteDB('workbox-background-sync-from-v2');\n\n      const dbv2 = await openDB('workbox-background-sync-from-v2', 2, {\n        upgrade(db) {\n          db.createObjectStore('requests', {\n            autoIncrement: true,\n            keyPath: 'id',\n          }).createIndex('queueName', 'queueName', {unique: false});\n        },\n      });\n\n      // Add entries in v2 format.\n      await dbv2.add('requests', {\n        queueName: 'a',\n        metadata: {one: '1', two: '2'},\n        storableRequest: {\n          url: `${location.origin}/one`,\n          timestamp: 123,\n          requestInit: {\n            method: 'POST',\n            mode: 'cors',\n            headers: {\n              'x-foo': 'bar',\n              'x-qux': 'baz',\n            },\n          },\n        },\n      });\n      await dbv2.add('requests', {\n        queueName: 'b',\n        metadata: {three: '3', four: '4'},\n        storableRequest: {\n          url: `${location.origin}/two`,\n          timestamp: 234,\n          requestInit: {\n            mode: 'cors',\n          },\n        },\n      });\n\n      const dbv3 = await openDB('workbox-background-sync-from-v2', 3, {\n        upgrade: QueueDb.prototype._upgradeDb,\n        blocked() {\n          dbv2.close();\n        },\n      });\n\n      let entries = await dbv3.getAll('requests');\n      expect(entries.length).to.equal(0);\n\n      await dbv3.add('requests', v3Entry);\n\n      entries = await dbv3.getAll('requests');\n      expect(entries[0].id).to.equal(1);\n      expect(entries[0].timestamp).to.equal(v3Entry.timestamp);\n      expect(entries[0].metadata).to.deep.equal(v3Entry.metadata);\n      expect(entries[0].queueName).to.equal(v3Entry.queueName);\n      expect(entries[0].requestData).to.deep.equal(v3Entry.requestData);\n\n      await deleteDB('workbox-background-sync-from-v2', {\n        blocked() {\n          dbv3.close();\n        },\n      });\n    });\n  });\n\n  describe('getFirstEntryId', () => {\n    it(`should return the first entry id in the IBDObjectStore`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n\n      const result = await queueDb.getFirstEntryId();\n      expect(result).to.equal(firstId);\n    });\n  });\n\n  describe('getAllEntriesByQueueName', () => {\n    it(`should return all entries in IDB filtered by index`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n      await queueDb.addEntry(entry4);\n      await queueDb.addEntry(entry5);\n\n      let results = await queueDb.getAllEntriesByQueueName('b');\n      expect(results).to.deep.equal([\n        {\n          id: firstId + 3,\n          queueName: 'b',\n          requestData: requestData4,\n          timestamp: 4000,\n          metadata: {name: 'meta4'},\n        },\n        {\n          id: firstId + 4,\n          queueName: 'b',\n          requestData: requestData5,\n          timestamp: 5000,\n          metadata: {name: 'meta5'},\n        },\n      ]);\n\n      results = await queueDb.getAllEntriesByQueueName('a');\n      expect(results).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: requestData1,\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 1,\n          queueName: 'a',\n          requestData: requestData2,\n          timestamp: 2000,\n          metadata: {name: 'meta2'},\n        },\n        {\n          id: firstId + 2,\n          queueName: 'a',\n          requestData: requestData3,\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n      ]);\n\n      await db.clear('requests');\n\n      expect(await queueDb.getAllEntriesByQueueName('a')).to.deep.equal([]);\n      expect(await queueDb.getAllEntriesByQueueName('b')).to.deep.equal([]);\n    });\n  });\n\n  describe('getEntryCountByQueueName', () => {\n    it(`should return the number of entries in IDB filtered by index`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n      await queueDb.addEntry(entry4);\n      await queueDb.addEntry(entry5);\n\n      expect(await queueDb.getEntryCountByQueueName('a')).to.equal(3);\n      expect(await queueDb.getEntryCountByQueueName('b')).to.equal(2);\n\n      await db.clear('requests');\n\n      expect(await queueDb.getEntryCountByQueueName('a')).to.equal(0);\n      expect(await queueDb.getEntryCountByQueueName('b')).to.equal(0);\n    });\n  });\n\n  describe('deleteEntry', () => {\n    it(`should delete an entry for the given ID`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n\n      await queueDb.deleteEntry(firstId + 1);\n\n      expect(await db.getAll('requests')).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: requestData1,\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 2,\n          queueName: 'a',\n          requestData: requestData3,\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n      ]);\n    });\n  });\n\n  describe('deleteEntry', () => {\n    it(`should delete an entry for the given ID`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n\n      await queueDb.deleteEntry(firstId + 1);\n\n      expect(await db.getAll('requests')).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: requestData1,\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 2,\n          queueName: 'a',\n          requestData: requestData3,\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n      ]);\n    });\n  });\n\n  describe('addEntry', () => {\n    it(`Should add new entries`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry3);\n\n      expect(await db.getAll('requests')).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: requestData1,\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 1,\n          queueName: 'a',\n          requestData: requestData3,\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n      ]);\n    });\n  });\n\n  describe('getFirstEntryByQueueName', () => {\n    it(`should return first entry in IDB filtered by index`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n      await queueDb.addEntry(entry4);\n      await queueDb.addEntry(entry5);\n\n      let result = await queueDb.getFirstEntryByQueueName('b');\n      expect(result).to.deep.equal({\n        id: firstId + 3,\n        queueName: 'b',\n        requestData: requestData4,\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n\n      result = await queueDb.getFirstEntryByQueueName('a');\n      expect(result).to.deep.equal({\n        id: firstId,\n        queueName: 'a',\n        requestData: requestData1,\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n    });\n  });\n\n  describe('getLastEntryByQueueName', () => {\n    it(`should return last entry in IDB filtered by index`, async () => {\n      const queueDb = new QueueDb();\n\n      await queueDb.addEntry(entry1);\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueDb.addEntry(entry2);\n      await queueDb.addEntry(entry3);\n      await queueDb.addEntry(entry4);\n      await queueDb.addEntry(entry5);\n\n      let result = await queueDb.getLastEntryByQueueName('b');\n      expect(result).to.deep.equal({\n        id: firstId + 4,\n        queueName: 'b',\n        requestData: requestData5,\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      result = await queueDb.getLastEntryByQueueName('a');\n      expect(result).to.deep.equal({\n        id: firstId + 2,\n        queueName: 'a',\n        requestData: requestData3,\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-background-sync/sw/lib/test-QueueStore.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {QueueStore} from 'workbox-background-sync/lib/QueueStore.mjs';\nimport {StorableRequest} from 'workbox-background-sync/lib/StorableRequest.mjs';\nimport {QueueDb} from 'workbox-background-sync/lib/QueueDb.mjs';\nimport {openDB} from 'idb';\n\ndescribe(`QueueStore`, function () {\n  let db = null;\n\n  beforeEach(async function () {\n    db = await openDB('workbox-background-sync', 3, {\n      upgrade: QueueDb.prototype._upgradeDb,\n    });\n    await db.clear('requests');\n  });\n\n  describe(`constructor`, function () {\n    it(`should associate the queue name with a Queue instance`, function () {\n      const queueStore = new QueueStore('foo');\n      expect(queueStore._queueName).to.equal('foo');\n    });\n  });\n\n  describe(`pushEntry`, function () {\n    it(`should append an entry to IDB with the right queue name`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      let entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore2.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.pushEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(5);\n      expect(entries[0].id).to.equal(firstId);\n      expect(entries[0].queueName).to.equal('a');\n      expect(entries[0].requestData.url).to.equal(`${location.origin}/one`);\n      expect(entries[0].timestamp).to.equal(1000);\n      expect(entries[0].metadata).to.deep.equal({name: 'meta1'});\n      expect(entries[1].id).to.equal(firstId + 1);\n      expect(entries[1].queueName).to.equal('b');\n      expect(entries[1].requestData.url).to.equal(`${location.origin}/two`);\n      expect(entries[1].timestamp).to.equal(2000);\n      expect(entries[1].metadata).to.deep.equal({name: 'meta2'});\n      expect(entries[2].id).to.equal(firstId + 2);\n      expect(entries[2].queueName).to.equal('b');\n      expect(entries[2].requestData.url).to.equal(`${location.origin}/three`);\n      expect(entries[2].timestamp).to.equal(3000);\n      expect(entries[2].metadata).to.deep.equal({name: 'meta3'});\n      expect(entries[3].id).to.equal(firstId + 3);\n      expect(entries[3].queueName).to.equal('b');\n      expect(entries[3].requestData.url).to.equal(`${location.origin}/four`);\n      expect(entries[3].timestamp).to.equal(4000);\n      expect(entries[3].metadata).to.deep.equal({name: 'meta4'});\n      expect(entries[4].id).to.equal(firstId + 4);\n      expect(entries[4].queueName).to.equal('a');\n      expect(entries[4].requestData.url).to.equal(`${location.origin}/five`);\n      expect(entries[4].timestamp).to.equal(5000);\n      expect(entries[4].metadata).to.deep.equal({name: 'meta5'});\n    });\n\n    it(`throws if not given an entry object`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const queueStore = new QueueStore('a');\n        await queueStore.pushEntry();\n      }, 'incorrect-type');\n    });\n\n    it(`throws if not given an entry object with requestData`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const queueStore = new QueueStore('a');\n        await queueStore.pushEntry({});\n      }, 'incorrect-type');\n    });\n  });\n\n  describe(`unshiftEntry`, function () {\n    it(`should prepend an entry to IDB with the right queue name and ID`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      let entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore2.unshiftEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.unshiftEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.unshiftEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.unshiftEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(5);\n      expect(entries[0].id).to.equal(firstId - 4);\n      expect(entries[0].timestamp).to.equal(5000);\n      expect(entries[0].metadata).to.deep.equal({name: 'meta5'});\n      expect(entries[0].queueName).to.equal('a');\n      expect(entries[0].requestData.url).to.equal(`${location.origin}/five`);\n      expect(entries[1].id).to.equal(firstId - 3);\n      expect(entries[1].timestamp).to.equal(4000);\n      expect(entries[1].metadata).to.deep.equal({name: 'meta4'});\n      expect(entries[1].queueName).to.equal('b');\n      expect(entries[1].requestData.url).to.equal(`${location.origin}/four`);\n      expect(entries[2].id).to.equal(firstId - 2);\n      expect(entries[2].timestamp).to.equal(3000);\n      expect(entries[2].metadata).to.deep.equal({name: 'meta3'});\n      expect(entries[2].queueName).to.equal('b');\n      expect(entries[2].requestData.url).to.equal(`${location.origin}/three`);\n      expect(entries[3].id).to.equal(firstId - 1);\n      expect(entries[3].timestamp).to.equal(2000);\n      expect(entries[3].metadata).to.deep.equal({name: 'meta2'});\n      expect(entries[3].queueName).to.equal('b');\n      expect(entries[3].requestData.url).to.equal(`${location.origin}/two`);\n      expect(entries[4].id).to.equal(firstId);\n      expect(entries[4].timestamp).to.equal(1000);\n      expect(entries[4].metadata).to.deep.equal({name: 'meta1'});\n      expect(entries[4].queueName).to.equal('a');\n      expect(entries[4].requestData.url).to.equal(`${location.origin}/one`);\n    });\n\n    it(`throws if not given an entry object`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const queueStore = new QueueStore('a');\n        await queueStore.unshiftEntry();\n      }, 'incorrect-type');\n    });\n\n    it(`throws if not given an entry object with requestData`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const queueStore = new QueueStore('a');\n        await queueStore.unshiftEntry({});\n      }, 'incorrect-type');\n    });\n  });\n\n  describe(`shiftEntry`, function () {\n    it(`should remove and return the first entry in IDB with the matching queue name`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      let entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore2.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.pushEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(5);\n\n      const sr2a = await queueStore2.shiftEntry();\n      expect(sr2a.requestData).to.deep.equal(sr2.toObject());\n      expect(sr2a.timestamp).to.equal(2000);\n      expect(sr2a.metadata).to.deep.equal({name: 'meta2'});\n      expect(sr2a.id).to.equal(firstId + 1);\n      expect(sr2a.queueName).to.equal('b');\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(4);\n      expect(entries[0].id).to.equal(firstId);\n      expect(entries[1].id).to.equal(firstId + 2);\n      expect(entries[2].id).to.equal(firstId + 3);\n      expect(entries[3].id).to.equal(firstId + 4);\n\n      const sr1a = await queueStore1.shiftEntry();\n      expect(sr1a.requestData).to.deep.equal(sr1.toObject());\n      expect(sr1a.timestamp).to.equal(1000);\n      expect(sr1a.metadata).to.deep.equal({name: 'meta1'});\n      expect(sr1a.id).to.equal(firstId);\n      expect(sr1a.queueName).to.equal('a');\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(3);\n      expect(entries[0].id).to.equal(firstId + 2);\n      expect(entries[1].id).to.equal(firstId + 3);\n      expect(entries[2].id).to.equal(firstId + 4);\n    });\n  });\n\n  describe(`popEntry`, function () {\n    it(`should remove and return the last entry in IDB with the matching queue name`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      let entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore2.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.pushEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(5);\n\n      const sr4a = await queueStore2.popEntry();\n      expect(sr4a.requestData).to.deep.equal(sr4.toObject());\n      expect(sr4a.timestamp).to.equal(4000);\n      expect(sr4a.metadata).to.deep.equal({name: 'meta4'});\n      expect(sr4a.id).to.equal(firstId + 3);\n      expect(sr4a.queueName).to.equal('b');\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(4);\n      expect(entries[0].id).to.equal(firstId);\n      expect(entries[0].queueName).to.equal('a');\n      expect(entries[0].requestData.url).to.equal(`${location.origin}/one`);\n      expect(entries[1].id).to.equal(firstId + 1);\n      expect(entries[1].queueName).to.equal('b');\n      expect(entries[1].requestData.url).to.equal(`${location.origin}/two`);\n      expect(entries[2].id).to.equal(firstId + 2);\n      expect(entries[2].queueName).to.equal('b');\n      expect(entries[2].requestData.url).to.equal(`${location.origin}/three`);\n      expect(entries[3].id).to.equal(firstId + 4);\n      expect(entries[3].queueName).to.equal('a');\n      expect(entries[3].requestData.url).to.equal(`${location.origin}/five`);\n\n      const sr5a = await queueStore1.popEntry();\n      expect(sr5a.requestData).to.deep.equal(sr5.toObject());\n      expect(sr5a.timestamp).to.equal(5000);\n      expect(sr5a.metadata).to.deep.equal({name: 'meta5'});\n      expect(sr5a.id).to.equal(firstId + 4);\n      expect(sr5a.queueName).to.equal('a');\n\n      entries = await db.getAll('requests');\n      expect(entries).to.have.lengthOf(3);\n      expect(entries[0].id).to.equal(firstId);\n      expect(entries[1].id).to.equal(firstId + 1);\n      expect(entries[2].id).to.equal(firstId + 2);\n    });\n  });\n\n  describe(`getAll`, function () {\n    it(`should return all entries in IDB with the right queue name`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore2.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.pushEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      expect(await queueStore1.getAll()).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: sr1.toObject(),\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 4,\n          queueName: 'a',\n          requestData: sr5.toObject(),\n          timestamp: 5000,\n          metadata: {name: 'meta5'},\n        },\n      ]);\n\n      expect(await queueStore2.getAll()).to.deep.equal([\n        {\n          id: firstId + 1,\n          queueName: 'b',\n          requestData: sr2.toObject(),\n          timestamp: 2000,\n          metadata: {name: 'meta2'},\n        },\n        {\n          id: firstId + 2,\n          queueName: 'b',\n          requestData: sr3.toObject(),\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n        {\n          id: firstId + 3,\n          queueName: 'b',\n          requestData: sr4.toObject(),\n          timestamp: 4000,\n          metadata: {name: 'meta4'},\n        },\n      ]);\n\n      await db.clear('requests');\n\n      expect(await queueStore1.getAll()).to.deep.equal([]);\n      expect(await queueStore2.getAll()).to.deep.equal([]);\n    });\n  });\n\n  describe(`size`, function () {\n    it(`should return the number of entries in IDB with the right queue name`, async function () {\n      const queueStore1 = new QueueStore('a');\n      const queueStore2 = new QueueStore('b');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n      const sr4 = await StorableRequest.fromRequest(new Request('/four'));\n      const sr5 = await StorableRequest.fromRequest(new Request('/five'));\n\n      await queueStore1.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n      await queueStore2.pushEntry({\n        requestData: sr4.toObject(),\n        timestamp: 4000,\n        metadata: {name: 'meta4'},\n      });\n      await queueStore1.pushEntry({\n        requestData: sr5.toObject(),\n        timestamp: 5000,\n        metadata: {name: 'meta5'},\n      });\n\n      expect(await queueStore1.size()).to.equal(2);\n      expect(await queueStore2.size()).to.equal(3);\n\n      await db.clear('requests');\n\n      expect(await queueStore1.size()).to.deep.equal(0);\n      expect(await queueStore2.size()).to.deep.equal(0);\n    });\n  });\n\n  describe(`delete`, function () {\n    it(`should delete an entry for the given ID`, async function () {\n      const queueStore = new QueueStore('a');\n\n      const sr1 = await StorableRequest.fromRequest(new Request('/one'));\n      const sr2 = await StorableRequest.fromRequest(new Request('/two'));\n      const sr3 = await StorableRequest.fromRequest(new Request('/three'));\n\n      await queueStore.pushEntry({\n        requestData: sr1.toObject(),\n        timestamp: 1000,\n        metadata: {name: 'meta1'},\n      });\n\n      const entries = await db.getAll('requests');\n      const firstId = entries[0].id;\n\n      await queueStore.pushEntry({\n        requestData: sr2.toObject(),\n        timestamp: 2000,\n        metadata: {name: 'meta2'},\n      });\n      await queueStore.pushEntry({\n        requestData: sr3.toObject(),\n        timestamp: 3000,\n        metadata: {name: 'meta3'},\n      });\n\n      await queueStore.deleteEntry(firstId + 1);\n\n      expect(await db.getAll('requests')).to.deep.equal([\n        {\n          id: firstId,\n          queueName: 'a',\n          requestData: sr1.toObject(),\n          timestamp: 1000,\n          metadata: {name: 'meta1'},\n        },\n        {\n          id: firstId + 2,\n          queueName: 'a',\n          requestData: sr3.toObject(),\n          timestamp: 3000,\n          metadata: {name: 'meta3'},\n        },\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-background-sync/sw/lib/test-StorableRequest.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {StorableRequest} from 'workbox-background-sync/lib/StorableRequest.mjs';\n\ndescribe(`StorableRequest`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  describe(`static fromRequest`, function () {\n    it(`should convert a Request to a StorableRequest instance`, async function () {\n      const request = new Request('/foo', {\n        method: 'POST',\n        body: 'it worked!',\n        mode: 'cors',\n        headers: {\n          'x-foo': 'bar',\n          'x-qux': 'baz',\n        },\n      });\n\n      const storableRequest = await StorableRequest.fromRequest(request);\n      expect(storableRequest._requestData.url).to.equal(\n        `${location.origin}/foo`,\n      );\n      expect(storableRequest._requestData.method).to.equal('POST');\n      expect(storableRequest._requestData.body).to.be.instanceOf(ArrayBuffer);\n      expect(storableRequest._requestData.body.byteLength).to.equal(10);\n\n      expect(storableRequest._requestData.mode).to.equal('cors');\n      expect(storableRequest._requestData.headers['x-foo']).to.equal('bar');\n      expect(storableRequest._requestData.headers['x-qux']).to.equal('baz');\n    });\n  });\n\n  describe(`constructor`, function () {\n    it(`sets the passed properties on the instance`, function () {\n      const requestData = {\n        url: '/foo',\n        method: 'POST',\n        body: 'it worked!',\n        mode: 'cors',\n        headers: {\n          'x-foo': 'bar',\n          'x-qux': 'baz',\n        },\n      };\n\n      const storableRequest = new StorableRequest(requestData);\n      expect(storableRequest._requestData).to.deep.equal(requestData);\n    });\n\n    it(`handles navigation requests by converting them to same-origin`, async function () {\n      const requestData = {\n        url: '/api',\n        method: 'POST',\n        body: '{\"json\":\"data\"}',\n        mode: 'navigate',\n      };\n\n      const storableRequest = new StorableRequest(requestData);\n      expect(storableRequest._requestData.mode).to.equal('same-origin');\n    });\n\n    it(`throws if not given a requestData object`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new StorableRequest();\n      }, 'incorrect-type');\n    });\n\n    it(`throws if not given a URL in the requestData object`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new StorableRequest({});\n      }, 'incorrect-type');\n    });\n  });\n\n  describe(`toObject`, function () {\n    it(`converts the instance to a plain object`, async function () {\n      const storableRequest = await StorableRequest.fromRequest(\n        new Request('/foo', {\n          method: 'POST',\n          body: 'it worked!',\n          mode: 'cors',\n          headers: {\n            'x-foo': 'bar',\n            'x-qux': 'baz',\n          },\n        }),\n      );\n\n      const requestData = storableRequest.toObject();\n\n      expect(Object.getPrototypeOf(requestData)).to.equal(Object.prototype);\n      expect(requestData.url).to.equal(`${location.origin}/foo`);\n      expect(requestData.method).to.equal('POST');\n      expect(requestData.body).to.be.instanceOf(ArrayBuffer);\n      expect(requestData.body.byteLength).to.equal(10);\n      expect(requestData.mode).to.equal('cors');\n      expect(requestData.headers['x-foo']).to.equal('bar');\n      expect(requestData.headers['x-qux']).to.equal('baz');\n    });\n  });\n\n  describe(`toRequest`, function () {\n    it(`converts the instance to a Request object`, async function () {\n      const storableRequest = await StorableRequest.fromRequest(\n        new Request('/foo', {\n          method: 'POST',\n          body: 'it worked!',\n          mode: 'cors',\n          headers: {\n            'x-foo': 'bar',\n            'x-qux': 'baz',\n          },\n        }),\n      );\n\n      const request = storableRequest.toRequest();\n\n      expect(Object.getPrototypeOf(request)).to.equal(Request.prototype);\n      expect(request.url).to.equal(`${location.origin}/foo`);\n      expect(request.method).to.equal('POST');\n      expect(await request.clone().text()).to.equal('it worked!');\n      expect(request.mode).to.equal('cors');\n      expect(request.headers.get('x-foo')).to.equal('bar');\n      expect(request.headers.get('x-qux')).to.equal('baz');\n    });\n  });\n\n  describe(`clone`, function () {\n    it(`creates a new instance with the same values`, async function () {\n      const original = new StorableRequest({\n        url: '/foo',\n        body: new Blob(['it worked!']),\n        method: 'POST',\n        mode: 'cors',\n        headers: {\n          'x-foo': 'bar',\n          'x-qux': 'baz',\n        },\n      });\n      const clone = original.clone();\n\n      expect(original._requestData).to.deep.equal(clone._requestData);\n\n      // Ensure clone was not shallow.\n      expect(original._requestData.body).to.not.equal(clone._requestData.body);\n      expect(original._requestData.headers).to.not.equal(\n        clone._requestData.headers,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-background-sync/sw/test-BackgroundSyncPlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Queue} from 'workbox-background-sync/Queue.mjs';\nimport {BackgroundSyncPlugin} from 'workbox-background-sync/BackgroundSyncPlugin.mjs';\n\nlet count = 0;\nfunction getUniqueQueueName() {\n  return `queue-${count++}`;\n}\n\ndescribe(`BackgroundSyncPlugin`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    // Don't actually register for a sync event in any test, as it could\n    // make the tests non-deterministic.\n    if ('sync' in registration) {\n      sandbox.stub(registration.sync, 'register');\n    }\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should implement fetchDidFail and add requests to the queue`, async function () {\n      const stub = sandbox.stub(Queue.prototype, 'pushRequest');\n      const queuePlugin = new BackgroundSyncPlugin(getUniqueQueueName());\n\n      queuePlugin.fetchDidFail({request: new Request('/one')});\n      expect(stub.callCount).to.equal(1);\n      expect(stub.args[0][0].request.url).to.equal(`${location.origin}/one`);\n      expect(stub.args[0][0].request).to.be.instanceOf(Request);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-background-sync/sw/test-Queue.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Queue} from 'workbox-background-sync/Queue.mjs';\nimport {QueueDb} from 'workbox-background-sync/lib/QueueDb.mjs';\nimport {openDB} from 'idb';\nimport {QueueStore} from 'workbox-background-sync/lib/QueueStore.mjs';\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nconst MINUTES = 60 * 1000;\n\ndescribe(`Queue`, function () {\n  const sandbox = sinon.createSandbox();\n  let db = null;\n\n  beforeEach(async function () {\n    Queue._queueNames.clear();\n    db = await openDB('workbox-background-sync', 3, {\n      upgrade: QueueDb.prototype._upgradeDb,\n    });\n    await db.clear('requests');\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    if (process.env.NODE_ENV !== 'production') {\n      sandbox.stub(logger);\n    }\n\n    // Don't actually register for a sync event in any test, as it could\n    // make the tests non-deterministic.\n    if ('sync' in registration) {\n      sandbox.stub(registration.sync, 'register');\n    }\n\n    sandbox.stub(Queue.prototype, 'replayRequests');\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`throws if two queues are created with the same name`, async function () {\n      expect(() => {\n        new Queue('foo');\n        new Queue('bar');\n      }).not.to.throw();\n\n      await expectError(() => {\n        new Queue('foo');\n      }, 'duplicate-queue-name');\n\n      expect(() => {\n        new Queue('baz');\n      }).not.to.throw();\n    });\n\n    it(`adds a sync event listener (if supported) that runs the onSync function when a sync event is dispatched`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const onSync = sandbox.spy();\n\n      const queue = new Queue('foo', {onSync});\n\n      // `addEventListener` is spied on in the beforeEach hook.\n      expect(self.addEventListener.calledOnce).to.be.true;\n      expect(self.addEventListener.calledWith('sync')).to.be.true;\n\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:foo',\n        }),\n      );\n\n      // `onSync` should not be called because the tag won't match.\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:bar',\n        }),\n      );\n\n      expect(onSync.callCount).to.equal(1);\n      expect(onSync.firstCall.args[0].queue).to.equal(queue);\n    });\n\n    it(`defaults to calling replayRequests (if supported) when no onSync function is passed`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const queue = new Queue('foo');\n\n      // `addEventListener` is spied on in the beforeEach hook.\n      expect(self.addEventListener.calledOnce).to.be.true;\n      expect(self.addEventListener.calledWith('sync')).to.be.true;\n\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:foo',\n        }),\n      );\n\n      // `replayRequests` should not be called because the tag won't match.\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:bar',\n        }),\n      );\n\n      // `replayRequsets` is stubbed in beforeEach, so we don't have to\n      // re-stub in this test, and we can just assert it was called.\n      expect(Queue.prototype.replayRequests.callCount).to.equal(1);\n      expect(Queue.prototype.replayRequests.firstCall.args[0].queue).to.equal(\n        queue,\n      );\n    });\n\n    it(`registers a tag (if supported) if entries were added to the queue during a successful sync`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const onSync = sandbox.stub().callsFake(async ({queue}) => {\n        await queue.pushRequest({\n          request: new Request('/one', {method: 'POST', body: '...'}),\n        });\n        await queue.pushRequest({\n          request: new Request('/two', {method: 'POST', body: '...'}),\n        });\n        await queue.pushRequest({\n          request: new Request('/three', {method: 'POST', body: '...'}),\n        });\n      });\n\n      const queue = new Queue('foo', {onSync});\n      sandbox.spy(queue, 'registerSync');\n\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:foo',\n        }),\n      );\n\n      expect(queue.registerSync.callCount).to.equal(1);\n    });\n\n    it(`doesn't re-register after a sync event fails`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const onSync = async ({queue}) => {\n        await queue.pushRequest({\n          request: new Request('/one', {method: 'POST', body: '...'}),\n        });\n        throw new Error('sync failed');\n      };\n\n      const queue = new Queue('foo', {onSync});\n      sandbox.spy(queue, 'registerSync');\n\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:foo',\n        }),\n      );\n\n      expect(queue.registerSync.callCount).to.equal(0);\n    });\n\n    it(`re-registers a tag after a sync event fails if event.lastChance is true`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const onSync = async ({queue}) => {\n        await queue.pushRequest({\n          request: new Request('/one', {method: 'POST', body: '...'}),\n        });\n        throw new Error('sync failed');\n      };\n\n      const queue = new Queue('foo', {onSync});\n      sandbox.spy(queue, 'registerSync');\n\n      const syncEvent = new SyncEvent('sync', {\n        tag: 'workbox-background-sync:foo',\n      });\n      sandbox.stub(syncEvent, 'lastChance').value(true);\n      await dispatchAndWaitUntilDone(syncEvent);\n\n      expect(queue.registerSync.callCount).to.equal(1);\n    });\n\n    it(`tries to run the sync logic on instantiation iff the browser doesn't support Background Sync`, async function () {\n      const onSync = sandbox.spy();\n      new Queue('foo', {onSync});\n\n      if ('sync' in registration) {\n        expect(onSync.calledOnce).to.be.false;\n      } else {\n        expect(onSync.calledOnce).to.be.true;\n      }\n    });\n\n    it(`should run 'onSync' on instantiation when forceSyncFallback is set`, async function () {\n      const onSync = sandbox.spy();\n      new Queue('foo', {onSync, forceSyncFallback: true});\n\n      expect(onSync.calledOnce).to.be.true;\n    });\n  });\n\n  describe(`pushRequest`, function () {\n    it(`should add the request to the end QueueStore instance`, async function () {\n      sandbox.spy(QueueStore.prototype, 'pushEntry');\n\n      const queue = new Queue('a');\n      const requestURL = 'https://example.com/';\n      const requestInit = {\n        method: 'POST',\n        body: 'testing...',\n        headers: {'x-foo': 'bar'},\n        mode: 'cors',\n      };\n      const request = new Request(requestURL, requestInit);\n      const timestamp = 1234;\n      const metadata = {meta: 'data'};\n\n      await queue.pushRequest({request, timestamp, metadata});\n\n      expect(QueueStore.prototype.pushEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.pushEntry.firstCall.args;\n      expect(args[0].requestData.url).to.equal(requestURL);\n      expect(args[0].requestData.method).to.equal(requestInit.method);\n      expect(args[0].requestData.headers['x-foo']).to.equal(\n        requestInit.headers['x-foo'],\n      );\n      expect(args[0].requestData.mode).to.deep.equal(requestInit.mode);\n      expect(args[0].requestData.body).to.be.instanceOf(ArrayBuffer);\n      expect(args[0].timestamp).to.equal(timestamp);\n      expect(args[0].metadata).to.deep.equal(metadata);\n    });\n\n    it(`should not require metadata`, async function () {\n      sandbox.spy(QueueStore.prototype, 'pushEntry');\n\n      const queue = new Queue('a');\n      const request = new Request('https://example.com/');\n\n      await queue.pushRequest({request});\n\n      expect(QueueStore.prototype.pushEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.pushEntry.firstCall.args;\n      expect(args[0].metadata).to.be.undefined;\n    });\n\n    it(`should use the current time as the timestamp when not specified`, async function () {\n      sandbox.spy(QueueStore.prototype, 'pushEntry');\n\n      sandbox.useFakeTimers({\n        toFake: ['Date'],\n        now: 1234,\n      });\n\n      const queue = new Queue('a');\n      const request = new Request('https://example.com/');\n\n      await queue.pushRequest({request});\n\n      expect(QueueStore.prototype.pushEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.pushEntry.firstCall.args;\n      expect(args[0].timestamp).to.equal(1234);\n    });\n\n    it(`should register to receive sync events for a unique tag`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const queue = new Queue('foo');\n\n      await queue.pushRequest({request: new Request('/')});\n\n      // self.registration.sync.register is stubbed in `beforeEach()`.\n      expect(self.registration.sync.register.calledOnce).to.be.true;\n      expect(\n        self.registration.sync.register.calledWith(\n          'workbox-background-sync:foo',\n        ),\n      ).to.be.true;\n    });\n  });\n\n  describe(`unshiftRequest`, function () {\n    it(`should add the request to the beginning of the QueueStore`, async function () {\n      sandbox.spy(QueueStore.prototype, 'unshiftEntry');\n\n      const queue = new Queue('a');\n      const requestURL = 'https://example.com/';\n      const requestInit = {\n        method: 'POST',\n        body: 'testing...',\n        headers: {'x-foo': 'bar'},\n        mode: 'cors',\n      };\n      const request = new Request(requestURL, requestInit);\n      const timestamp = 1234;\n      const metadata = {meta: 'data'};\n\n      await queue.unshiftRequest({request, timestamp, metadata});\n\n      expect(QueueStore.prototype.unshiftEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.unshiftEntry.firstCall.args;\n      expect(args[0].requestData.url).to.equal(requestURL);\n      expect(args[0].requestData.method).to.equal(requestInit.method);\n      expect(args[0].requestData.headers['x-foo']).to.equal(\n        requestInit.headers['x-foo'],\n      );\n      expect(args[0].requestData.mode).to.deep.equal(requestInit.mode);\n      expect(args[0].requestData.body).to.be.instanceOf(ArrayBuffer);\n      expect(args[0].timestamp).to.equal(timestamp);\n      expect(args[0].metadata).to.deep.equal(metadata);\n    });\n\n    it(`should not require metadata`, async function () {\n      sandbox.spy(QueueStore.prototype, 'unshiftEntry');\n\n      const queue = new Queue('a');\n      const request = new Request('https://example.com/');\n\n      await queue.unshiftRequest({request});\n\n      expect(QueueStore.prototype.unshiftEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.unshiftEntry.firstCall.args;\n      expect(args[0].metadata).to.be.undefined;\n    });\n\n    it(`should use the current time as the timestamp when not specified`, async function () {\n      sandbox.spy(QueueStore.prototype, 'unshiftEntry');\n\n      const queue = new Queue('a');\n      const request = new Request('https://example.com/');\n\n      const startTime = Date.now();\n      await queue.unshiftRequest({request});\n      const endTime = Date.now();\n\n      expect(QueueStore.prototype.unshiftEntry.callCount).to.equal(1);\n\n      const args = QueueStore.prototype.unshiftEntry.firstCall.args;\n      expect(args[0].timestamp >= startTime).to.be.ok;\n      expect(args[0].timestamp <= endTime).to.be.ok;\n    });\n\n    it(`should register to receive sync events for a unique tag`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      const queue = new Queue('foo');\n\n      await queue.unshiftRequest({\n        request: new Request('/', {method: 'POST', body: '...'}),\n      });\n\n      // self.registration.sync.register is stubbed in `beforeEach()`.\n      expect(self.registration.sync.register.calledOnce).to.be.true;\n      expect(\n        self.registration.sync.register.calledWith(\n          'workbox-background-sync:foo',\n        ),\n      ).to.be.true;\n    });\n  });\n\n  describe(`shiftRequest`, function () {\n    it(`gets and removes the first request in the QueueStore instance`, async function () {\n      sandbox.spy(QueueStore.prototype, 'shiftEntry');\n\n      const queue = new Queue('a');\n      const requestURL = 'https://example.com/';\n      const requestInit = {\n        method: 'POST',\n        body: 'testing...',\n        headers: {'x-foo': 'bar'},\n        mode: 'cors',\n      };\n\n      await queue.pushRequest({request: new Request(requestURL, requestInit)});\n\n      // Add a second request to ensure the first one is returned.\n      await queue.pushRequest({request: new Request('/two')});\n\n      const {request} = await queue.shiftRequest();\n\n      expect(QueueStore.prototype.shiftEntry.callCount).to.equal(1);\n      expect(request.url).to.equal(requestURL);\n      expect(request.method).to.equal(requestInit.method);\n      expect(request.mode).to.deep.equal(requestInit.mode);\n      expect(await request.text()).to.equal(requestInit.body);\n      expect(request.headers.get('x-foo')).to.equal(\n        requestInit.headers['x-foo'],\n      );\n    });\n\n    it(`returns the timestamp and any passed metadata along with the request`, async function () {\n      const queue = new Queue('a');\n\n      await queue.pushRequest({\n        metadata: {meta: 'data'},\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n\n      const {request, metadata} = await queue.shiftRequest();\n\n      expect(request.url).to.equal(`${location.origin}/one`);\n      expect(metadata).to.deep.equal({meta: 'data'});\n    });\n\n    it(`does not return requests that have expired`, async function () {\n      const queue = new Queue('a');\n\n      await queue.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n        timestamp: 12,\n      });\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/three', {method: 'POST', body: '...'}),\n        timestamp: 34,\n      });\n      await queue.pushRequest({\n        request: new Request('/four', {method: 'POST', body: '...'}),\n      });\n\n      const entry1 = await queue.shiftRequest();\n      const entry2 = await queue.shiftRequest();\n      const entry3 = await queue.shiftRequest();\n\n      expect(entry1.request.url).to.equal(`${location.origin}/two`);\n      expect(entry2.request.url).to.equal(`${location.origin}/four`);\n      expect(entry3).to.be.undefined;\n    });\n  });\n\n  describe(`popRequest`, function () {\n    it(`gets and removes the last request in the QueueStore instance`, async function () {\n      sandbox.spy(QueueStore.prototype, 'popEntry');\n\n      const queue = new Queue('a');\n      const requestURL = 'https://example.com/';\n      const requestInit = {\n        method: 'POST',\n        body: 'testing...',\n        headers: {'x-foo': 'bar'},\n        mode: 'cors',\n      };\n\n      // Add a second request to ensure the last one is returned.\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request(requestURL, requestInit),\n      });\n\n      const {request} = await queue.popRequest();\n\n      expect(QueueStore.prototype.popEntry.callCount).to.equal(1);\n      expect(request.url).to.equal(requestURL);\n      expect(request.method).to.equal(requestInit.method);\n      expect(request.mode).to.deep.equal(requestInit.mode);\n      expect(await request.text()).to.equal(requestInit.body);\n      expect(request.headers.get('x-foo')).to.equal(\n        requestInit.headers['x-foo'],\n      );\n    });\n\n    it(`returns the timestamp and any passed metadata along with the request`, async function () {\n      const queue = new Queue('a');\n\n      await queue.pushRequest({\n        metadata: {meta: 'data'},\n        request: new Request('/one'),\n      });\n\n      const {request, metadata} = await queue.popRequest();\n\n      expect(request.url).to.equal(`${location.origin}/one`);\n      expect(metadata).to.deep.equal({meta: 'data'});\n    });\n\n    it(`does not return requests that have expired`, async function () {\n      const queue = new Queue('a');\n\n      await queue.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n        timestamp: 12,\n      });\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/three', {method: 'POST', body: '...'}),\n        timestamp: 34,\n      });\n      await queue.pushRequest({\n        request: new Request('/four', {method: 'POST', body: '...'}),\n      });\n\n      const entry1 = await queue.popRequest();\n      const entry2 = await queue.popRequest();\n      const entry3 = await queue.popRequest();\n\n      expect(entry1.request.url).to.equal(`${location.origin}/four`);\n      expect(entry2.request.url).to.equal(`${location.origin}/two`);\n      expect(entry3).to.be.undefined;\n    });\n  });\n\n  describe(`replayRequests`, function () {\n    beforeEach(function () {\n      // Unstub replayRequests for all tests in this group.\n      Queue.prototype.replayRequests.restore();\n    });\n\n    it(`should try to re-fetch all requests in the queue`, async function () {\n      sandbox.stub(self, 'fetch');\n\n      const queue1 = new Queue('foo');\n      const queue2 = new Queue('bar');\n\n      // Add requests for both queues to ensure only the requests from\n      // the matching queue are replayed.\n      await queue1.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n      await queue2.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue1.pushRequest({\n        request: new Request('/three', {method: 'POST', body: '...'}),\n      });\n      await queue2.pushRequest({\n        request: new Request('/four', {method: 'POST', body: '...'}),\n      });\n      await queue1.pushRequest({\n        request: new Request('/five', {method: 'POST', body: '...'}),\n      });\n\n      await queue1.replayRequests();\n\n      expect(self.fetch.callCount).to.equal(3);\n\n      expect(\n        self.fetch.getCall(0).calledWith(\n          sinon.match({\n            url: `${location.origin}/one`,\n          }),\n        ),\n      ).to.be.true;\n\n      expect(\n        self.fetch.getCall(1).calledWith(\n          sinon.match({\n            url: `${location.origin}/three`,\n          }),\n        ),\n      ).to.be.true;\n\n      expect(\n        self.fetch.getCall(2).calledWith(\n          sinon.match({\n            url: `${location.origin}/five`,\n          }),\n        ),\n      ).to.be.true;\n\n      await queue2.replayRequests();\n      expect(self.fetch.callCount).to.equal(5);\n\n      expect(\n        self.fetch.getCall(3).calledWith(\n          sinon.match({\n            url: `${location.origin}/two`,\n          }),\n        ),\n      ).to.be.true;\n\n      expect(\n        self.fetch.getCall(4).calledWith(\n          sinon.match({\n            url: `${location.origin}/four`,\n          }),\n        ),\n      ).to.be.true;\n    });\n\n    it(`should remove requests after a successful retry`, async function () {\n      sandbox.spy(self, 'fetch');\n\n      const queue1 = new Queue('foo');\n      const queue2 = new Queue('bar');\n\n      // Add requests for both queues to ensure only the requests from\n      // the matching queue are replayed.\n      await queue1.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n      await queue2.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue1.pushRequest({\n        request: new Request('/three', {method: 'POST', body: '...'}),\n      });\n      await queue2.pushRequest({\n        request: new Request('/four', {method: 'POST', body: '...'}),\n      });\n      await queue1.pushRequest({\n        request: new Request('/five', {method: 'POST', body: '...'}),\n      });\n\n      await queue1.replayRequests();\n      expect(self.fetch.callCount).to.equal(3);\n\n      const entries = await db.getAll('requests');\n      expect(entries.length).to.equal(2);\n      expect(entries[0].requestData.url).to.equal(`${location.origin}/two`);\n      expect(entries[1].requestData.url).to.equal(`${location.origin}/four`);\n    });\n\n    it(`should ignore (and remove) requests if maxRetentionTime has passed`, async function () {\n      sandbox.spy(self, 'fetch');\n      const clock = sandbox.useFakeTimers({\n        now: Date.now(),\n        toFake: ['Date'],\n      });\n\n      const queue = new Queue('foo', {\n        maxRetentionTime: 1,\n      });\n\n      await queue.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n\n      clock.tick(1 * MINUTES + 1); // One minute and 1ms.\n\n      await queue.pushRequest({\n        request: new Request('/three'),\n      });\n      await queue.replayRequests();\n\n      expect(self.fetch.calledOnce).to.be.true;\n      expect(\n        self.fetch.calledWith(\n          sinon.match({\n            url: `${location.origin}/three`,\n          }),\n        ),\n      ).to.be.true;\n\n      const entries = await db.getAll('requests');\n      // Assert that the two requests not replayed were deleted.\n      expect(entries.length).to.equal(0);\n    });\n\n    it(`should stop replaying if a request fails`, async function () {\n      sandbox\n        .stub(self, 'fetch')\n        .onCall(3)\n        .callsFake(async (request) => {\n          // Use the body to ensure everything is cloned beforehand.\n          await request.text();\n          throw new Error('network error');\n        })\n        .callThrough();\n\n      const queue = new Queue('foo');\n\n      await queue.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/three', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/four', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/five', {method: 'POST', body: '...'}),\n      });\n\n      await expectError(() => {\n        return queue.replayRequests(); // The 4th requests should fail.\n      }, 'queue-replay-failed');\n\n      const entries = await db.getAll('requests');\n      expect(entries.length).to.equal(2);\n      expect(entries[0].requestData.url).to.equal(`${location.origin}/four`);\n      expect(entries[1].requestData.url).to.equal(`${location.origin}/five`);\n    });\n\n    it(`should throw WorkboxError if re-fetching fails`, async function () {\n      sandbox\n        .stub(self, 'fetch')\n        .onCall(1)\n        .callsFake(async (request) => {\n          // Use the body to ensure everything is cloned beforehand.\n          await request.text();\n          throw new Error('network error');\n        })\n        .callThrough();\n\n      const queue = new Queue('foo');\n\n      // Add requests for both queues to ensure only the requests from\n      // the matching queue are replayed.\n      await queue.pushRequest({\n        request: new Request('/one', {method: 'POST', body: '...'}),\n      });\n      await queue.pushRequest({\n        request: new Request('/two', {method: 'POST', body: '...'}),\n      });\n\n      await expectError(() => {\n        return queue.replayRequests();\n      }, 'queue-replay-failed');\n    });\n  });\n\n  describe(`registerSync()`, function () {\n    it(`should succeed regardless of browser support for sync`, async function () {\n      const queue = new Queue('a');\n      await queue.registerSync();\n    });\n\n    it(`should handle thrown errors in sync registration`, async function () {\n      if (!('sync' in registration)) this.skip();\n\n      registration.sync.register.restore();\n\n      sandbox.stub(registration.sync, 'register').callsFake(() => {\n        return Promise.reject(new Error('Injected Error'));\n      });\n\n      const queue = new Queue('a');\n      await queue.registerSync();\n    });\n  });\n\n  describe(`getAll()`, function () {\n    it(`returns all requests in the QueueStore instance`, async function () {\n      const queue = new Queue('a');\n\n      const request1 = new Request('/one', {method: 'POST', body: '...'});\n      const request2 = new Request('/two', {method: 'POST', body: '...'});\n      const request3 = new Request('/three', {method: 'POST', body: '...'});\n\n      await queue.pushRequest({request: request1});\n      await queue.pushRequest({request: request2});\n      await queue.pushRequest({\n        request: request3,\n        metadata: {meta: 'data'},\n      });\n\n      const entries = await queue.getAll();\n      expect(entries.length).to.equal(3);\n      expect(entries[0].request).to.deep.equal(request1);\n      expect(entries[0].metadata).to.equal(undefined);\n      expect(entries[1].request).to.deep.equal(request2);\n      expect(entries[1].metadata).to.equal(undefined);\n      expect(entries[2].request).to.deep.equal(request3);\n      expect(entries[2].metadata).to.deep.equal({meta: 'data'});\n\n      // Ensure the entries aren't deleted.\n      expect(await db.getAll('requests')).to.have.lengthOf(3);\n    });\n\n    it(`doesn't return expired entries (and it deletes them)`, async function () {\n      const queue = new Queue('a');\n\n      const request1 = new Request('/one', {method: 'POST', body: '...'});\n      const request2 = new Request('/two', {method: 'POST', body: '...'});\n      const request3 = new Request('/three', {method: 'POST', body: '...'});\n\n      await queue.pushRequest({request: request1});\n      await queue.pushRequest({\n        request: request2,\n        timestamp: Date.now() - 1e12,\n      });\n      await queue.pushRequest({\n        request: request3,\n        metadata: {meta: 'data'},\n      });\n\n      const entries = await queue.getAll();\n      expect(entries.length).to.equal(2);\n      expect(entries[0].request).to.deep.equal(request1);\n      expect(entries[0].metadata).to.equal(undefined);\n      expect(entries[1].request).to.deep.equal(request3);\n      expect(entries[1].metadata).to.deep.equal({meta: 'data'});\n\n      // Ensure the expired entry was deleted.\n      expect(await db.getAll('requests')).to.have.lengthOf(2);\n    });\n  });\n\n  describe(`size()`, function () {\n    it(`returns the number of requests in the QueueStore instance`, async function () {\n      const queue = new Queue('a');\n\n      const request1 = new Request('/one', {method: 'POST', body: '...'});\n      const request2 = new Request('/two', {method: 'POST', body: '...'});\n      const request3 = new Request('/three', {method: 'POST', body: '...'});\n\n      await queue.pushRequest({request: request1});\n      await queue.pushRequest({request: request2});\n      await queue.pushRequest({\n        request: request3,\n        metadata: {meta: 'data'},\n      });\n\n      expect(await queue.size()).to.equal(3);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-broadcast-update/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\nconst {\n  IframeManager,\n} = require('../../../infra/testing/webdriver/IframeManager');\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst templateData = require('../../../infra/testing/server/template-data');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-broadcast-update]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-broadcast-update/sw/');\n  });\n});\n\ndescribe(`[workbox-broadcast-update] Plugin`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-broadcast-update/static/`;\n  const swURL = `${testingURL}sw.js`;\n  const apiURL = `${testServerAddress}/__WORKBOX/uniqueETag`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, testingURL);\n    await activateAndControlSW(swURL);\n  });\n\n  it(`should broadcast a message when there's a cache update to a regular request`, async function () {\n    // Fetch `apiURL`, which should put it in the cache (but not trigger an update)\n    const err1 = await webdriver.executeAsyncScript((apiURL, cb) => {\n      fetch(apiURL)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    }, apiURL);\n    expect(err1).to.not.exist;\n\n    // Fetch `apiURL` again, which should trigger an update message.\n    const err2 = await webdriver.executeAsyncScript((apiURL, cb) => {\n      fetch(apiURL)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    }, apiURL);\n    expect(err2).to.not.exist;\n\n    await webdriver.wait(() => {\n      return webdriver.executeScript(() => {\n        return window.__messages.length > 0;\n      });\n    });\n\n    const messages = await webdriver.executeScript(() => {\n      return window.__messages;\n    });\n\n    expect(messages.length).to.equal(1);\n    expect(messages[0]).to.deep.equal({\n      type: 'CACHE_UPDATED',\n      meta: 'workbox-broadcast-update',\n      payload: {\n        cacheName: 'bcu-integration-test',\n        updatedURL: apiURL,\n      },\n    });\n  });\n\n  it(`should broadcast a message when there's a cache update to a navigation request`, async function () {\n    templateData.assign({\n      title: 'Broadcast Cache Update Test',\n      body: 'Second test, initial body.',\n      script: `\n        window.__messages = [];\n        navigator.serviceWorker.addEventListener('message', (event) => {\n          window.__messages.push(event.data);\n        });\n      `,\n    });\n\n    const dynamicPageURL = testingURL + 'integration.html.njk?second';\n\n    // Navigate to a dynamic page whose content can be updated from with this\n    // test, and wait until the cache is populated.\n    await webdriver.get(dynamicPageURL);\n    await webdriver.wait(async () => {\n      return webdriver.executeAsyncScript(async (url, cb) => {\n        cb(await caches.match(url));\n      }, dynamicPageURL);\n    });\n\n    // Update the template data with new content,\n    // then refresh and wait until the update message is received.\n    templateData.assign({\n      body: 'Second test, with and updated body.',\n    });\n\n    await webdriver.get(webdriver.getCurrentUrl());\n\n    await webdriver.wait(() => {\n      return webdriver.executeScript(() => {\n        return window.__messages.length > 0;\n      });\n    });\n\n    const messages = await webdriver.executeScript(() => {\n      return window.__messages;\n    });\n\n    expect(messages.length).to.equal(1);\n    expect(messages[0]).to.deep.equal({\n      type: 'CACHE_UPDATED',\n      meta: 'workbox-broadcast-update',\n      payload: {\n        cacheName: 'bcu-integration-test',\n        updatedURL: dynamicPageURL,\n      },\n    });\n  });\n\n  it(`should broadcast a message to all open window clients by default`, async function () {\n    const iframeManager = new IframeManager(webdriver);\n\n    templateData.assign({\n      title: 'Broadcast Cache Update Test',\n      body: 'Third test, initial body.',\n      script: `\n        window.__messages = [];\n        navigator.serviceWorker.addEventListener('message', (event) => {\n          window.__messages.push(event.data);\n        });\n      `,\n    });\n\n    const dynamicPageURL = testingURL + 'integration.html.njk?third';\n\n    // Navigate to a dynamic page whose content can be updated from with this\n    // test, and wait until the cache is populated.\n    await webdriver.get(dynamicPageURL);\n    await webdriver.wait(async () => {\n      return webdriver.executeAsyncScript(async (url, cb) => {\n        cb(await caches.match(url));\n      }, dynamicPageURL);\n    });\n\n    // Update the template data and open an iframe to trigger the cache update.\n    templateData.assign({\n      body: 'Third test, with an updated body.',\n    });\n    await iframeManager.createIframeClient(dynamicPageURL);\n\n    await webdriver.wait(() => {\n      return webdriver.executeScript(() => {\n        return window.__messages.length > 0;\n      });\n    });\n\n    const tab1Messages = await webdriver.executeScript(() => {\n      return window.__messages;\n    });\n\n    expect(tab1Messages).to.eql([\n      {\n        type: 'CACHE_UPDATED',\n        meta: 'workbox-broadcast-update',\n        payload: {\n          cacheName: 'bcu-integration-test',\n          updatedURL: dynamicPageURL,\n        },\n      },\n    ]);\n  });\n\n  it(`should only broadcast a message to the client that made the request when notifyAllClients is false`, async function () {\n    const url = `${apiURL}?notifyAllClientsTest`;\n    const iframeManager = new IframeManager(webdriver);\n\n    await webdriver.get(testingURL);\n    await webdriver.executeAsyncScript((url, cb) => {\n      fetch(url)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    }, url);\n\n    const iframeClient = await iframeManager.createIframeClient(testingURL);\n    await iframeClient.executeAsyncScript(`fetch(${JSON.stringify(url)})`);\n    await iframeClient.wait('window.__messages.length > 0');\n\n    const populatedMessages = await iframeClient.executeAsyncScript(\n      'window.__messages',\n    );\n    expect(populatedMessages).to.eql([\n      {\n        type: 'CACHE_UPDATED',\n        meta: 'workbox-broadcast-update',\n        payload: {\n          cacheName: 'bcu-integration-test',\n          updatedURL: url,\n        },\n      },\n    ]);\n\n    const unpopulatedMessages = await webdriver.executeScript(() => {\n      return window.__messages;\n    });\n    expect(unpopulatedMessages).to.be.empty;\n  });\n});\n"
  },
  {
    "path": "test/workbox-broadcast-update/static/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n    <script>\n      window.__messages = [];\n      navigator.serviceWorker.addEventListener('message', (event) => {\n        window.__messages.push(event.data);\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-broadcast-update/static/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-broadcast-update');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\n\nconst cacheName = 'bcu-integration-test';\n\nworkbox.routing.registerRoute(\n  ({url}) => url.searchParams.has('notifyAllClientsTest'),\n  new workbox.strategies.NetworkFirst({\n    cacheName,\n    plugins: [\n      new workbox.broadcastUpdate.BroadcastUpdatePlugin({\n        notifyAllClients: false,\n      }),\n    ],\n  }),\n);\n\nworkbox.routing.registerRoute(\n  new RegExp('/__WORKBOX/uniqueETag$'),\n  new workbox.strategies.StaleWhileRevalidate({\n    cacheName,\n    plugins: [new workbox.broadcastUpdate.BroadcastUpdatePlugin()],\n  }),\n);\n\nworkbox.routing.registerRoute(\n  ({request}) => request.mode === 'navigate',\n  new workbox.strategies.StaleWhileRevalidate({\n    cacheName,\n    plugins: [new workbox.broadcastUpdate.BroadcastUpdatePlugin()],\n  }),\n);\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-broadcast-update/sw/test-BroadcastCacheUpdate.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {BroadcastCacheUpdate} from 'workbox-broadcast-update/BroadcastCacheUpdate.mjs';\nimport {\n  CACHE_UPDATED_MESSAGE_META,\n  CACHE_UPDATED_MESSAGE_TYPE,\n  DEFAULT_HEADERS_TO_CHECK,\n} from 'workbox-broadcast-update/utils/constants.mjs';\n\ndescribe(`BroadcastCacheUpdate`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor()`, function () {\n    it(`should use the passed headersToCheck`, function () {\n      const headersToCheck = ['hello-1', 'hello-2'];\n      const bcu = new BroadcastCacheUpdate({headersToCheck});\n      expect(bcu._headersToCheck).to.equal(headersToCheck);\n    });\n\n    it(`should use the default headersToCheck when not passed`, function () {\n      const bcu = new BroadcastCacheUpdate();\n      expect(bcu._headersToCheck).to.deep.equal(DEFAULT_HEADERS_TO_CHECK);\n    });\n\n    it(`should use the passed generatePayload`, function () {\n      const generatePayload = () => {};\n      const bcu = new BroadcastCacheUpdate({generatePayload});\n      expect(bcu._generatePayload).to.equal(generatePayload);\n    });\n\n    it(`should use the default generatePayload when not passed`, function () {\n      const bcu = new BroadcastCacheUpdate();\n      expect(bcu._generatePayload).to.be.a('function');\n    });\n  });\n\n  describe(`notifyIfUpdated()`, function () {\n    it(`should broadcast update if responses are different`, async function () {\n      const bcu = new BroadcastCacheUpdate();\n\n      const pm1Spy = sandbox.spy();\n      const pm2Spy = sandbox.spy();\n      sandbox\n        .stub(self.clients, 'matchAll')\n        .withArgs(sinon.match.has('type', 'window'))\n        .resolves([{postMessage: pm1Spy}, {postMessage: pm2Spy}]);\n\n      await bcu.notifyIfUpdated({\n        oldResponse: new Response('', {\n          headers: {'content-length': 0},\n        }),\n        newResponse: new Response('', {\n          headers: {'content-length': 1},\n        }),\n        request: new Request('/'),\n        cacheName: 'cache-name',\n      });\n\n      const expectData = {\n        type: CACHE_UPDATED_MESSAGE_TYPE,\n        meta: CACHE_UPDATED_MESSAGE_META,\n        payload: {\n          updatedURL: new URL('/', location).href,\n          cacheName: 'cache-name',\n        },\n      };\n\n      expect(pm1Spy.callCount).to.equal(1);\n      expect(pm1Spy.args[0][0]).to.deep.equal(expectData);\n      expect(pm2Spy.callCount).to.equal(1);\n      expect(pm2Spy.args[0][0]).to.deep.equal(expectData);\n    });\n\n    it(`should not broadcast update if responses are the same`, async function () {\n      const bcu = new BroadcastCacheUpdate();\n\n      const pm1Spy = sandbox.spy();\n      const pm2Spy = sandbox.spy();\n      sandbox\n        .stub(self.clients, 'matchAll')\n        .withArgs(sinon.match.has('type', 'window'))\n        .resolves([{postMessage: pm1Spy}, {postMessage: pm2Spy}]);\n\n      await bcu.notifyIfUpdated({\n        oldResponse: new Response('', {\n          headers: {'content-length': 0},\n        }),\n        newResponse: new Response('', {\n          headers: {'content-length': 0},\n        }),\n        request: new Request('/'),\n        cacheName: 'cache-name',\n      });\n\n      expect(pm1Spy.callCount).to.equal(0);\n      expect(pm2Spy.callCount).to.equal(0);\n    });\n\n    it(`should throw when called and cacheName is missing`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const bcu = new BroadcastCacheUpdate();\n        const oldResponse = new Response();\n        const newResponse = new Response();\n        const request = new Request('/');\n        await bcu.notifyIfUpdated({oldResponse, newResponse, request});\n      }, 'incorrect-type');\n    });\n\n    it(`should throw when called and newResponse is missing`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const bcu = new BroadcastCacheUpdate();\n        const cacheName = 'cache-name';\n        const oldResponse = new Response();\n        const request = new Request('/');\n        await bcu.notifyIfUpdated({cacheName, oldResponse, request});\n      }, 'incorrect-class');\n    });\n\n    it(`should throw when called and request is missing`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(async () => {\n        const bcu = new BroadcastCacheUpdate();\n        const cacheName = 'cache-name';\n        const oldResponse = new Response();\n        const newResponse = new Response();\n        await bcu.notifyIfUpdated({cacheName, oldResponse, newResponse});\n      }, 'incorrect-class');\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-broadcast-update/sw/test-BroadcastUpdatePlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {BroadcastUpdatePlugin} from '../../../packages/workbox-broadcast-update/BroadcastUpdatePlugin.mjs';\n\ndescribe(`BroadcastUpdatePlugin`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  describe(`cacheDidUpdate`, function () {\n    it(`should call notifyIfUpdated and pass all options`, async function () {\n      const bcuPlugin = new BroadcastUpdatePlugin();\n      sandbox.stub(bcuPlugin._broadcastUpdate, 'notifyIfUpdated').resolves();\n\n      const opts = {};\n      await bcuPlugin.cacheDidUpdate(opts);\n\n      expect(bcuPlugin._broadcastUpdate.notifyIfUpdated.callCount).to.equal(1);\n      expect(bcuPlugin._broadcastUpdate.notifyIfUpdated.args[0][0]).to.equal(\n        opts,\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-broadcast-update/sw/test-responsesAreSame.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {responsesAreSame} from '../../../packages/workbox-broadcast-update/responsesAreSame.mjs';\n\ndescribe(`responsesAreSame`, function () {\n  const firstHeaderName = 'x-first-header';\n  const secondHeaderName = 'x-second-header';\n  const headersToCheck = [firstHeaderName, secondHeaderName];\n\n  it(`should throw when responsesAreSame() is called without any parameters`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    return expectError(() => {\n      responsesAreSame();\n    }, 'invalid-responses-are-same-args');\n  });\n\n  it(`should return true when all the headers match`, function () {\n    const first = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n        [secondHeaderName]: 'same',\n      },\n    });\n    const second = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n        [secondHeaderName]: 'same',\n      },\n    });\n    expect(responsesAreSame(first, second, headersToCheck)).to.be.true;\n  });\n\n  it(`should return true when only a subset of headers exist, but the existing ones match`, function () {\n    const first = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n      },\n    });\n    const second = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n      },\n    });\n    expect(responsesAreSame(first, second, headersToCheck)).to.be.true;\n  });\n\n  it(`should return true when no headers exist`, function () {\n    const first = new Response('');\n    const second = new Response('');\n    expect(responsesAreSame(first, second, headersToCheck)).to.be.true;\n  });\n\n  it(`should return false when one header matches and the other doesn't`, function () {\n    const first = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n        [secondHeaderName]: 'same',\n      },\n    });\n    const second = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n        [secondHeaderName]: 'different',\n      },\n    });\n    expect(responsesAreSame(first, second, headersToCheck)).to.be.false;\n  });\n\n  it(`should return false when none of the headers match`, function () {\n    const first = new Response('', {\n      headers: {\n        [firstHeaderName]: 'same',\n        [secondHeaderName]: 'same',\n      },\n    });\n    const second = new Response('', {\n      headers: {\n        [firstHeaderName]: 'different',\n        [secondHeaderName]: 'different',\n      },\n    });\n    expect(responsesAreSame(first, second, headersToCheck)).to.be.false;\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/dependency-check.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst upath = require('upath');\nconst depcheck = require('depcheck');\n\ndescribe(`[workbox-build] Test Dependencies`, function () {\n  it(`should have required dependencies`, function () {\n    return new Promise((resolve, reject) => {\n      depcheck(\n        upath.join(__dirname, '..', '..', '..', 'packages', 'workbox-build'),\n        {\n          ignoreDirs: ['test', 'build', 'demo'],\n          ignoreMatches: [\n            '@babel/preset-env',\n            '@babel/runtime',\n            'type-fest',\n            'workbox-background-sync',\n            'workbox-broadcast-update',\n            'workbox-cacheable-response',\n            'workbox-core',\n            'workbox-expiration',\n            'workbox-google-analytics',\n            'workbox-navigation-preload',\n            'workbox-precaching',\n            'workbox-range-requests',\n            'workbox-recipes',\n            'workbox-routing',\n            'workbox-strategies',\n            'workbox-streams',\n            'workbox-sw',\n            'workbox-window',\n          ],\n        },\n        (unusedDeps) => {\n          if (unusedDeps.dependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.dependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (unusedDeps.devDependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.devDependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (Object.keys(unusedDeps.missing).length > 0) {\n            return reject(\n              new Error(\n                `Dependencies missing from package.json: ${JSON.stringify(\n                  unusedDeps.missing,\n                )}`,\n              ),\n            );\n          }\n\n          resolve();\n        },\n      );\n    });\n  });\n\n  it(`should have no devDependencies`, function () {\n    // This test exists because there have been a number of situations where\n    // dependencies have been used from the top level project and NOT from\n    // this module itself. So dependencies are checked above and devDependencies\n    // can be put in top level.\n    const pkg = require('../../../packages/workbox-build/package.json');\n    if (pkg.devDependencies && Object.keys(pkg.devDependencies) > 0) {\n      throw new Error('No devDependencies in this module.');\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/generate-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst chai = require('chai');\nconst chaiAsPromised = require('chai-as-promised');\nconst fse = require('fs-extra');\nconst upath = require('upath');\nconst tempy = require('tempy');\n\nconst {errors} = require('../../../packages/workbox-build/build/lib/errors');\nconst {\n  generateSW,\n} = require('../../../packages/workbox-build/build/generate-sw');\nconst {\n  WorkboxConfigError,\n} = require('../../../packages/workbox-build/build/lib/validate-options');\nconst confirmDirectoryContains = require('../../../infra/testing/confirm-directory-contains');\nconst validateServiceWorkerRuntime = require('../../../infra/testing/validator/service-worker-runtime');\n\nchai.use(chaiAsPromised);\nconst {expect} = chai;\n\ndescribe(`[workbox-build] generate-sw.js (End to End)`, function () {\n  const GLOB_DIR = upath.join(__dirname, '..', 'static', 'example-project-1');\n  const BASE_OPTIONS = {\n    globDirectory: GLOB_DIR,\n    inlineWorkboxRuntime: false,\n    mode: 'development',\n    swDest: tempy.file({extension: 'js'}),\n  };\n  const REQUIRED_PARAMS = ['swDest'];\n  const SUPPORTED_PARAMS = [\n    'additionalManifestEntries',\n    'babelPresetEnvTargets',\n    'cacheId',\n    'clientsClaim',\n    'directoryIndex',\n    'disableDevLogs',\n    'dontCacheBustURLsMatching',\n    'globDirectory',\n    'globFollow',\n    'globIgnores',\n    'globPatterns',\n    'globStrict',\n    'ignoreURLParametersMatching',\n    'importScripts',\n    'inlineWorkboxRuntime',\n    'manifestTransforms',\n    'maximumFileSizeToCacheInBytes',\n    'mode',\n    'modifyURLPrefix',\n    'navigateFallback',\n    'navigateFallbackDenylist',\n    'navigateFallbackAllowlist',\n    'navigationPreload',\n    'offlineGoogleAnalytics',\n    'runtimeCaching',\n    'skipWaiting',\n    'sourcemap',\n    'templatedURLs',\n  ].concat(REQUIRED_PARAMS);\n  const UNSUPPORTED_PARAMS = ['injectionPoint', 'swSrc'];\n  const PRECACHE_ORDER = [\n    {\n      url: 'webpackEntry.js',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'page-2.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'page-1.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'index.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'styles/stylesheet-2.css',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'styles/stylesheet-1.css',\n      revision: /^[0-9a-f]{32}$/,\n    },\n  ];\n\n  describe('[workbox-build] required parameters', function () {\n    for (const requiredParam of REQUIRED_PARAMS) {\n      it(`should fail validation when '${requiredParam}' is missing`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        delete options[requiredParam];\n\n        await expect(generateSW(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          requiredParam,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] unsupported parameters', function () {\n    for (const unsupportedParam of UNSUPPORTED_PARAMS) {\n      it(`should fail validation when '${unsupportedParam}' is present`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[unsupportedParam] = unsupportedParam;\n\n        await expect(generateSW(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          unsupportedParam,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] invalid parameter values', function () {\n    for (const param of SUPPORTED_PARAMS) {\n      it(`should fail validation when '${param}' is an unexpected value`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[param] = () => {};\n\n        await expect(generateSW(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          param,\n        );\n      });\n    }\n\n    it(`should reject when there are no manifest entries or runtimeCaching`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS);\n      // This temporary directory will be empty.\n      options.globDirectory = tempy.directory();\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        errors['no-manifest-entries-or-runtime-caching'],\n      );\n    });\n  });\n\n  describe(`[workbox-build] writing a service worker file`, function () {\n    it(`should use defaults when all the required parameters are present`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {swDest});\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          __WB_DISABLE_DEV_LOGS: undefined,\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n        },\n      });\n    });\n\n    it(`should include the versioning strings in the generated bundle`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        inlineWorkboxRuntime: true,\n      });\n\n      const {count, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n\n      const contents = await fse.readFile(swDest, 'utf8');\n      // This isn't the exact string, but it's close enough.\n      expect(contents).to.include(`workbox:core:`);\n    });\n\n    it(`should disable logging when disableDevLogs is set to true`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        disableDevLogs: true,\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          __WB_DISABLE_DEV_LOGS: true,\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with additional importScripts`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const importScripts = ['manifest.js'];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        importScripts,\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/], [...importScripts]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with additional configuration`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const directoryIndex = 'test.html';\n      const ignoreURLParametersMatching = [/test1/, /test2/];\n      const cacheId = 'test';\n      const additionalOptions = {\n        cacheId,\n        directoryIndex,\n        ignoreURLParametersMatching,\n        clientsClaim: true,\n        skipWaiting: true,\n      };\n      const options = Object.assign({}, BASE_OPTIONS, additionalOptions, {\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          clientsClaim: [[]],\n          skipWaiting: [[]],\n          setCacheNameDetails: [[{prefix: cacheId}]],\n          precacheAndRoute: [\n            [PRECACHE_ORDER, {directoryIndex, ignoreURLParametersMatching}],\n          ],\n        },\n        addEventListenerValidation: (addEventListenerStub) => {\n          // When skipWaiting is true, the 'message' addEventListener shouldn't be called.\n          expect(addEventListenerStub.called).to.be.false;\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with additionalManifestEntries`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        additionalManifestEntries: [\n          '/one',\n          {url: '/two', revision: null},\n          {url: '/three', revision: '333'},\n          // See https://github.com/GoogleChrome/workbox/issues/2558\n          {url: '/four', revision: '123', integrity: '456'},\n        ],\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      // The string additionalManifestEntries entry should lead to one warning.\n      expect(warnings).to.have.length(1);\n      expect(count).to.eql(10);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [\n            [\n              [\n                ...PRECACHE_ORDER,\n                {url: '/one', revision: null},\n                {url: '/two', revision: null},\n                {url: '/three', revision: '333'},\n                {url: '/four', revision: '123', integrity: '456'},\n              ],\n              {},\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should add a 'message' event listener when 'skipWaiting: false'`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const additionalOptions = {\n        skipWaiting: false,\n      };\n      const options = Object.assign({}, BASE_OPTIONS, additionalOptions, {\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n        },\n        addEventListenerValidation: (addEventListenerStub) => {\n          expect(addEventListenerStub.calledOnce).to.be.true;\n          expect(addEventListenerStub.firstCall.args[0]).to.eql('message');\n          // This isn't the *cleanest* possible way of testing the message event\n          // handler, but given the constraints of this node-based environment,\n          // it seems the most effective way to ensure the right code gets run.\n          expect(addEventListenerStub.firstCall.args[1].toString()).to.eql(\n            `event => {\\n    if (event.data && event.data.type === 'SKIP_WAITING') {\\n      self.skipWaiting();\\n    }\\n  }`,\n          );\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with 'navigateFallback'`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const navigateFallback = 'test.html';\n      const navigateFallbackDenylist = [/test1/, /test2/];\n      const navigateFallbackAllowlist = [/test3/, /test4/];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        navigateFallback,\n        navigateFallbackDenylist,\n        navigateFallbackAllowlist,\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          createHandlerBoundToURL: [[navigateFallback]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          registerRoute: [[{name: 'NavigationRoute'}]],\n          NavigationRoute: [\n            [\n              '/urlWithCacheKey',\n              {\n                denylist: navigateFallbackDenylist,\n                allowlist: navigateFallbackAllowlist,\n              },\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with symlinks`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const globDirectory = tempy.directory();\n\n      await fse.ensureSymlink(GLOB_DIR, upath.join(globDirectory, 'link'));\n\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globDirectory,\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [\n            [\n              [\n                {\n                  url: 'link/webpackEntry.js',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/page-2.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/page-1.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/index.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/styles/stylesheet-2.css',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/styles/stylesheet-1.css',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n              ],\n              {},\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with 'globFollow:true' and  symlinks`, async function () {\n      // https://github.com/isaacs/node-glob/blob/main/changelog.md#90\n      // 10.3 exclude symbolic links to directories when follow and nodir are both set\n\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const globDirectory = tempy.directory();\n\n      await fse.ensureSymlink(GLOB_DIR, upath.join(globDirectory, 'link'));\n\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globDirectory,\n        globFollow: true,\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [\n            [\n              [\n                {\n                  url: 'link/webpackEntry.js',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/page-2.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/page-1.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/index.html',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/styles/stylesheet-2.css',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n                {\n                  url: 'link/styles/stylesheet-1.css',\n                  revision: /^[0-9a-f]{32}$/,\n                },\n              ],\n              {},\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with 'offlineGoogleAnalytics' set to true`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        offlineGoogleAnalytics: true,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          initialize: [[{}]],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, with 'offlineGoogleAnalytics' set to a config`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        offlineGoogleAnalytics: {\n          parameterOverrides: {\n            cd1: 'offline',\n          },\n        },\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          initialize: [\n            [\n              {\n                parameterOverrides: {\n                  cd1: 'offline',\n                },\n              },\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should support using a swDest that includes a subdirectory`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sub', 'directory', 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n    });\n\n    it(`should inline the Workbox runtime when 'inlineWorkboxRuntime' is true`, async function () {\n      const outputDir = tempy.directory();\n      const swDest = upath.join(outputDir, 'sw.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        inlineWorkboxRuntime: true,\n      });\n\n      const {count, filePaths, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await confirmDirectoryContains(outputDir, filePaths);\n      // We can't validate the generated sw.js file, unfortunately.\n    });\n  });\n\n  describe(`[workbox-build] behavior with 'runtimeCaching'`, function () {\n    const DEFAULT_METHOD = 'GET';\n    const REGEXP_URL_PATTERN = /test/;\n    const STRING_URL_PATTERN = '/test';\n    const STRING_HANDLER = 'CacheFirst';\n    const FUNCTION_URL_PATTERN = (params) => true;\n\n    it(`should reject when 'urlPattern' is missing from 'runtimeCaching'`, async function () {\n      const handler = STRING_HANDLER;\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching: [{handler}],\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        'urlPattern',\n      );\n    });\n\n    it(`should reject when 'handler' is missing from 'runtimeCaching'`, async function () {\n      const urlPattern = REGEXP_URL_PATTERN;\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching: [{urlPattern}],\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        'handler',\n      );\n    });\n\n    it(`should reject when 'handler' is not a valid strategy name`, async function () {\n      const urlPattern = REGEXP_URL_PATTERN;\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching: [\n          {\n            urlPattern,\n            handler: 'invalid',\n          },\n        ],\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        'handler',\n      );\n    });\n\n    // See https://github.com/GoogleChrome/workbox/issues/2078\n    it(`should not require using precaching`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const runtimeCaching = [\n        {\n          urlPattern: STRING_URL_PATTERN,\n          handler: STRING_HANDLER,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n      delete options.globDirectory;\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(0);\n      expect(size).to.eql(0);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [STRING_HANDLER]: [[]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          registerRoute: [\n            [STRING_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should support a single string 'urlPattern' and a string 'handler'`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const runtimeCaching = [\n        {\n          urlPattern: STRING_URL_PATTERN,\n          handler: STRING_HANDLER,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [STRING_HANDLER]: [[]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          registerRoute: [\n            [STRING_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should support a single function 'urlPattern' and a string 'handler'`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const runtimeCaching = [\n        {\n          urlPattern: FUNCTION_URL_PATTERN,\n          handler: STRING_HANDLER,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [STRING_HANDLER]: [[]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          // See https://github.com/chaijs/chai/issues/697\n          registerRoute: [\n            ['params => true', {name: STRING_HANDLER}, DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should support setting individual 'options' each, for multiple 'runtimeCaching' entries`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const firstRuntimeCachingOptions = {\n        cacheName: 'first-cache-name',\n        expiration: {\n          maxEntries: 1,\n          maxAgeSeconds: 1,\n          purgeOnQuotaError: false,\n        },\n      };\n      const secondRuntimeCachingOptions = {\n        cacheName: 'second-cache-name',\n        cacheableResponse: {\n          headers: {\n            'X-Test': 'test',\n          },\n          statuses: [0, 200],\n        },\n        precacheFallback: {\n          fallbackURL: '/test',\n        },\n      };\n      const runtimeCaching = [\n        {\n          urlPattern: REGEXP_URL_PATTERN,\n          handler: STRING_HANDLER,\n          options: firstRuntimeCachingOptions,\n        },\n        {\n          urlPattern: REGEXP_URL_PATTERN,\n          handler: STRING_HANDLER,\n          options: secondRuntimeCachingOptions,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [STRING_HANDLER]: [\n            [\n              {\n                cacheName: firstRuntimeCachingOptions.cacheName,\n                plugins: [{}],\n              },\n            ],\n            [\n              {\n                cacheName: secondRuntimeCachingOptions.cacheName,\n                plugins: [{}, {}],\n              },\n            ],\n          ],\n          ExpirationPlugin: [[firstRuntimeCachingOptions.expiration]],\n          CacheableResponsePlugin: [\n            [secondRuntimeCachingOptions.cacheableResponse],\n          ],\n          PrecacheFallbackPlugin: [\n            [secondRuntimeCachingOptions.precacheFallback],\n          ],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          registerRoute: [\n            [REGEXP_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],\n            [REGEXP_URL_PATTERN, {name: STRING_HANDLER}, DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should reject with a ValidationError when 'networkTimeoutSeconds' is used and handler is not 'NetworkFirst'`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const runtimeCachingOptions = {\n        networkTimeoutSeconds: 1,\n      };\n      const runtimeCaching = [\n        {\n          urlPattern: REGEXP_URL_PATTERN,\n          handler: 'NetworkOnly',\n          options: runtimeCachingOptions,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        errors['invalid-network-timeout-seconds'],\n      );\n    });\n\n    it(`should support passing in a function when allowed`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const handler = () => {};\n      const urlPattern = () => {};\n\n      const runtimeCachingOptions = {\n        backgroundSync: {\n          name: 'test',\n          options: {\n            onSync: () => {},\n          },\n        },\n        plugins: [\n          {\n            cachedResponseWillBeUsed: () => {},\n          },\n        ],\n      };\n      const runtimeCaching = [\n        {\n          handler,\n          urlPattern,\n          options: runtimeCachingOptions,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [handler]: [[runtimeCachingOptions]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          registerRoute: [\n            [urlPattern.toString(), handler.toString(), DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should support 'networkTimeoutSeconds' when handler is 'NetworkFirst'`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const networkTimeoutSeconds = 1;\n      const handler = 'NetworkFirst';\n\n      const runtimeCachingOptions = {\n        networkTimeoutSeconds,\n        plugins: [],\n      };\n      const runtimeCaching = [\n        {\n          urlPattern: REGEXP_URL_PATTERN,\n          handler,\n          options: runtimeCachingOptions,\n        },\n      ];\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [handler]: [[runtimeCachingOptions]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          registerRoute: [\n            [REGEXP_URL_PATTERN, {name: handler}, DEFAULT_METHOD],\n          ],\n        },\n      });\n    });\n\n    it(`should reject when 'options.expiration' is used without 'options.cacheName'`, async function () {\n      const urlPattern = REGEXP_URL_PATTERN;\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching: [\n          {\n            urlPattern,\n            handler: 'NetworkFirst',\n            options: {\n              expiration: {\n                maxEntries: 5,\n              },\n            },\n          },\n        ],\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        errors['cache-name-required'],\n      );\n    });\n\n    it(`should ignore swDest and workbox-*.js when generating manifest entries`, async function () {\n      const tempDirectory = tempy.directory();\n      await fse.copy(BASE_OPTIONS.globDirectory, tempDirectory);\n      const swDest = upath.join(tempDirectory, 'service-worker.js');\n      await fse.createFile(swDest);\n      // See https://rollupjs.org/guide/en/#outputchunkfilenames\n      await fse.createFile(upath.join(tempDirectory, 'workbox-abcd1234.js'));\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globDirectory: tempDirectory,\n        swDest,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n        },\n      });\n    });\n  });\n\n  describe(`[workbox-build] behavior with 'navigationPreload'`, function () {\n    it(`should reject when 'navigationPreload' is true and 'runtimeCaching' is undefined`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        navigationPreload: true,\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        errors['nav-preload-runtime-caching'],\n      );\n    });\n\n    it(`should reject when 'navigationPreload' is true and 'runtimeCaching' is undefined`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        runtimeCaching: undefined,\n        navigationPreload: true,\n      });\n\n      await expect(generateSW(options)).to.eventually.be.rejectedWith(\n        WorkboxConfigError,\n        errors['nav-preload-runtime-caching'],\n      );\n    });\n\n    it(`should generate when 'navigationPreload' is true and 'runtimeCaching' is valid`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const urlPattern = /test/;\n      const handler = 'CacheFirst';\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        runtimeCaching: [{urlPattern, handler}],\n        navigationPreload: true,\n      });\n\n      const {count, size, warnings} = await generateSW(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        swFile: swDest,\n        expectedMethodCalls: {\n          [handler]: [[]],\n          importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n          precacheAndRoute: [[PRECACHE_ORDER, {}]],\n          enable: [[]],\n          registerRoute: [[urlPattern, {name: handler}, 'GET']],\n        },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/get-manifest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst chai = require('chai');\nconst chaiAsPromised = require('chai-as-promised');\nconst chaiMatchPattern = require('chai-match-pattern');\nconst fse = require('fs-extra');\nconst upath = require('upath');\nconst tempy = require('tempy');\n\nchai.use(chaiAsPromised);\nchai.use(chaiMatchPattern);\nconst {expect} = chai;\n\nconst {\n  getManifest,\n} = require('../../../packages/workbox-build/build/get-manifest');\nconst {\n  WorkboxConfigError,\n} = require('../../../packages/workbox-build/build/lib/validate-options');\n\ndescribe(`[workbox-build] get-manifest.js (End to End)`, function () {\n  const SRC_DIR = upath.join(__dirname, '..', 'static', 'example-project-1');\n  const BASE_OPTIONS = {\n    globDirectory: SRC_DIR,\n  };\n  const SUPPORTED_PARAMS = [\n    'dontCacheBustURLsMatching',\n    'globDirectory',\n    'globFollow',\n    'globIgnores',\n    'globPatterns',\n    'globStrict',\n    'manifestTransforms',\n    'maximumFileSizeToCacheInBytes',\n    'modifyURLPrefix',\n    'templatedURLs',\n  ];\n  const UNSUPPORTED_PARAMS = [\n    'cacheId',\n    'clientsClaim',\n    'directoryIndex',\n    'ignoreURLParametersMatching',\n    'importScripts',\n    'importWorkboxFrom',\n    'injectionPointRegexp',\n    'mode',\n    'navigateFallback',\n    'navigateFallbackAllowlist',\n    'runtimeCaching',\n    'skipWaiting',\n    'swSrc',\n    'swDest',\n  ];\n\n  describe('[workbox-build] unsupported parameters', function () {\n    for (const unsupportedParam of UNSUPPORTED_PARAMS) {\n      it(`should fail validation when '${unsupportedParam}' is present`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[unsupportedParam] = unsupportedParam;\n\n        await expect(getManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          unsupportedParam,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] invalid parameter values', function () {\n    for (const param of SUPPORTED_PARAMS) {\n      it(`should fail validation when '${param}' is an unexpected value`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[param] = () => {};\n\n        await expect(getManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          param,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] should generate a valid manifest when properly configured', function () {\n    it(`should use defaults when all the required parameters are present`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS);\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: 'webpackEntry.js',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'page-2.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'page-1.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'index.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'styles/stylesheet-2.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'styles/stylesheet-1.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n    });\n\n    it(`should use defaults when all the required parameters, and 'globPatterns' are present`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globPatterns: ['**/*.html', '**/*.js'],\n      });\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: 'page-2.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'page-1.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'index.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'webpackEntry.js',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(4);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2707, 2629]);\n    });\n\n    it(`should use defaults when all the required parameters, and 'globIgnores' are present`, async function () {\n      const options = Object.assign(\n        {\n          globIgnores: ['**/*.html', '**/*.js'],\n        },\n        BASE_OPTIONS,\n      );\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern(\n        [\n          {\n            url: 'styles/stylesheet-1.css',\n            revision: /^[0-9a-f]{32}$/,\n          },\n          {\n            url: 'styles/stylesheet-2.css',\n            revision: /^[0-9a-f]{32}$/,\n          },\n        ].reverse(),\n      );\n      expect(count).to.eql(2);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([69, 75]);\n    });\n\n    it(`should use defaults when all the required parameters, 'globIgnores', and 'globPatterns' are present`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globPatterns: ['**/*.css', '**/*.js'],\n        globIgnores: ['node_modules/**/*', '**/*2*'],\n      });\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: 'styles/stylesheet-1.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'webpackEntry.js',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(2);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([216, 219]);\n    });\n\n    it(`should use defaults when all the required parameters, and 'maximumFileSizeToCacheInBytes' are present`, async function () {\n      const options = Object.assign(\n        {\n          maximumFileSizeToCacheInBytes: 50,\n        },\n        BASE_OPTIONS,\n      );\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.have.lengthOf(2);\n      expect(manifestEntries).to.matchPattern([\n        {\n          revision: /^[0-9a-f]{32}$/,\n          url: 'page-2.html',\n        },\n        {\n          revision: /^[0-9a-f]{32}$/,\n          url: 'page-1.html',\n        },\n        {\n          revision: /^[0-9a-f]{32}$/,\n          url: 'styles/stylesheet-2.css',\n        },\n        {\n          revision: /^[0-9a-f]{32}$/,\n          url: 'styles/stylesheet-1.css',\n        },\n      ]);\n      expect(count).to.eql(4);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([101, 109]);\n    });\n\n    it(`should use defaults when all the required parameters, and 'templatedURLs' are present`, async function () {\n      const url1 = 'url1';\n      const url2 = 'url2';\n\n      const options = Object.assign(\n        {\n          templatedURLs: {\n            [url1]: ['**/*.html'],\n            [url2]: 'string dependency',\n          },\n        },\n        BASE_OPTIONS,\n      );\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: 'webpackEntry.js',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'page-2.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'page-1.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'index.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'styles/stylesheet-2.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'styles/stylesheet-1.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'url1',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'url2',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(8);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([5162, 5324]);\n    });\n\n    it(`should use defaults when all the required parameters, and 'manifestTransforms' are present`, async function () {\n      // This filters out all entries unless the url property includes the string '1'.\n      const transform1 = (entries) => {\n        const manifest = entries.filter((entry) => {\n          return entry.url.includes('1');\n        });\n        return {manifest};\n      };\n      // This modifies all entries to prefix the url property with the string '/prefix/'.\n      const transform2 = (entries) => {\n        const manifest = entries.filter((entry) => {\n          entry.url = `/prefix/${entry.url}`;\n          return entry;\n        });\n        return {manifest};\n      };\n\n      const options = Object.assign(\n        {\n          manifestTransforms: [transform1, transform2],\n        },\n        BASE_OPTIONS,\n      );\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: '/prefix/page-1.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: '/prefix/styles/stylesheet-1.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(2);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([50, 54]);\n    });\n\n    it(`should use defaults when all the required parameters are present, with 'globFollow:true' and symlinks`, async function () {\n      const globDirectory = tempy.directory();\n\n      await fse.ensureSymlink(SRC_DIR, upath.join(globDirectory, 'link'));\n\n      const options = Object.assign({}, BASE_OPTIONS, {\n        globDirectory,\n        globFollow: true,\n      });\n\n      const {count, size, manifestEntries, warnings} = await getManifest(\n        options,\n      );\n      expect(warnings).to.be.empty;\n      expect(manifestEntries).to.matchPattern([\n        {\n          url: 'link/webpackEntry.js',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'link/page-2.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'link/page-1.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'link/index.html',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'link/styles/stylesheet-2.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n        {\n          url: 'link/styles/stylesheet-1.css',\n          revision: /^[0-9a-f]{32}$/,\n        },\n      ]);\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n    });\n  });\n\n  describe(`[workbox-build] removed options`, function () {\n    // These were deprecated in v4, and formally removed in v5.\n    const oldOptionsToValue = {\n      dontCacheBustUrlsMatching: /ignored/,\n      ignoreUrlParametersMatching: [/ignored/],\n      modifyUrlPrefix: {\n        ignored: 'ignored',\n      },\n      templatedUrls: {},\n    };\n\n    for (const [option, value] of Object.entries(oldOptionsToValue)) {\n      it(`should fail validation when ${option} is used`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS, {\n          [option]: value,\n        });\n\n        await expect(getManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          option,\n        );\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/inject-manifest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst chai = require('chai');\nconst chaiAsPromised = require('chai-as-promised');\nconst fse = require('fs-extra');\nconst upath = require('upath');\nconst tempy = require('tempy');\n\nconst {errors} = require('../../../packages/workbox-build/build/lib/errors');\nconst {\n  injectManifest,\n} = require('../../../packages/workbox-build/build/inject-manifest');\nconst {\n  WorkboxConfigError,\n} = require('../../../packages/workbox-build/build/lib/validate-options');\nconst validateServiceWorkerRuntime = require('../../../infra/testing/validator/service-worker-runtime');\n\nchai.use(chaiAsPromised);\nconst {expect} = chai;\n\ndescribe(`[workbox-build] inject-manifest.js (End to End)`, function () {\n  const GLOB_DIR = upath.join(__dirname, '..', 'static', 'example-project-1');\n  const SW_SRC_DIR = upath.join(__dirname, '..', 'static', 'sw-injections');\n  const BASE_OPTIONS = {\n    globDirectory: GLOB_DIR,\n    swDest: tempy.file({extension: 'js'}),\n    swSrc: upath.join(SW_SRC_DIR, 'basic.js'),\n  };\n  const REQUIRED_PARAMS = ['swDest', 'swSrc'];\n  const SUPPORTED_PARAMS = [\n    'additionalManifestEntries',\n    'dontCacheBustURLsMatching',\n    'globDirectory',\n    'globFollow',\n    'globIgnores',\n    'globPatterns',\n    'injectionPoint',\n    'manifestTransforms',\n    'maximumFileSizeToCacheInBytes',\n    'modifyURLPrefix',\n    'templatedURLs',\n  ].concat(REQUIRED_PARAMS);\n  const UNSUPPORTED_PARAMS = [\n    'cacheId',\n    'clientsClaim',\n    'directoryIndex',\n    'ignoreURLParametersMatching',\n    'importScripts',\n    'importWorkboxFrom',\n    'mode',\n    'navigateFallback',\n    'navigateFallbackAllowlist',\n    'runtimeCaching',\n    'skipWaiting',\n  ];\n  const PRECACHE_ORDER = [\n    {\n      url: 'webpackEntry.js',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'page-2.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'page-1.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'index.html',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'styles/stylesheet-2.css',\n      revision: /^[0-9a-f]{32}$/,\n    },\n    {\n      url: 'styles/stylesheet-1.css',\n      revision: /^[0-9a-f]{32}$/,\n    },\n  ];\n  describe('[workbox-build] required parameters', function () {\n    for (const requiredParam of REQUIRED_PARAMS) {\n      it(`should reject when '${requiredParam}' is missing`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        delete options[requiredParam];\n\n        await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          requiredParam,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] unsupported parameters', function () {\n    for (const unsupportedParam of UNSUPPORTED_PARAMS) {\n      it(`should reject when '${unsupportedParam}' is present`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[unsupportedParam] = unsupportedParam;\n\n        await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          unsupportedParam,\n        );\n      });\n    }\n  });\n\n  describe('[workbox-build] invalid parameter values', function () {\n    for (const param of SUPPORTED_PARAMS) {\n      it(`should reject when '${param}' is null`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS);\n        options[param] = null;\n\n        await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          param,\n        );\n      });\n    }\n  });\n\n  describe(`[workbox-build] runtime errors`, function () {\n    it(`should throw the expected error when 'swSrc' is invalid`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swSrc: 'DOES_NOT_EXIST',\n      });\n\n      await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n        errors['invalid-sw-src'],\n      );\n    });\n\n    it(`should throw the expected error when there is no match for 'injectionPoint'`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swSrc: upath.join(SW_SRC_DIR, 'bad-no-injection.js'),\n      });\n\n      await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n        errors['injection-point-not-found'],\n      );\n    });\n\n    it(`should throw the expected error when there is no match for 'injectionPoint' and 'swSrc' and 'swDest' are the same`, async function () {\n      const swFile = upath.join(SW_SRC_DIR, 'bad-no-injection.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swSrc: swFile,\n        swDest: swFile,\n      });\n\n      await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n        errors['same-src-and-dest'],\n      );\n    });\n\n    it(`should throw the expected error when there are multiple matches for 'injectionPoint'`, async function () {\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swSrc: upath.join(SW_SRC_DIR, 'bad-multiple-injection.js'),\n      });\n\n      await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n        errors['multiple-injection-points'],\n      );\n    });\n  });\n\n  describe(`[workbox-build] writing a service worker file`, function () {\n    it(`should use defaults when all the required parameters are present`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const options = Object.assign({}, BASE_OPTIONS, {swDest});\n\n      const {count, filePaths, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      expect(filePaths).to.have.members([upath.resolve(swDest)]);\n\n      await validateServiceWorkerRuntime({\n        entryPoint: 'injectManifest',\n        swFile: swDest,\n        expectedMethodCalls: {\n          precacheAndRoute: [[PRECACHE_ORDER]],\n        },\n      });\n    });\n\n    it(`should use absolute paths in the filePaths return value`, async function () {\n      // Deliberately use a relative path for swDest.\n      const swDest = upath.relative('.', tempy.file({extension: 'js'}));\n      const options = Object.assign({}, BASE_OPTIONS, {swDest});\n\n      const {filePaths, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      // Use upath.resolve() to confirm that we get back an absolute path.\n      expect(filePaths).to.have.members([upath.resolve(swDest)]);\n    });\n\n    it(`should use defaults when all the required parameters are present, when workboxSW.precache() is called twice`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        swSrc: upath.join(SW_SRC_DIR, 'multiple-calls.js'),\n      });\n\n      const {count, filePaths, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      expect(filePaths).to.have.members([upath.resolve(swDest)]);\n\n      await validateServiceWorkerRuntime({\n        entryPoint: 'injectManifest',\n        swFile: swDest,\n        expectedMethodCalls: {\n          importScripts: [['./sample-import.js']],\n          precacheAndRoute: [\n            [PRECACHE_ORDER],\n            [\n              [\n                '/extra-assets/example.1234.css',\n                '/extra-assets/example-2.1234.js',\n              ],\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should use defaults when all the required parameters are present, when a custom 'injectionPoint' is used`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        injectionPoint: 'self.__custom_injection_point',\n        swSrc: upath.join(SW_SRC_DIR, 'custom-injection-point.js'),\n      });\n\n      const {count, filePaths, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      expect(filePaths).to.have.members([upath.resolve(swDest)]);\n\n      await validateServiceWorkerRuntime({\n        entryPoint: 'injectManifest',\n        swFile: swDest,\n        expectedMethodCalls: {\n          precacheAndRoute: [[PRECACHE_ORDER]],\n        },\n      });\n    });\n\n    it(`should support using the default 'injectionPoint' when precacheAndRoute() is called with options`, async function () {\n      const swDest = tempy.file({extension: 'js'});\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        swSrc: upath.join(SW_SRC_DIR, 'precache-and-route-options.js'),\n      });\n\n      const {count, filePaths, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      expect(filePaths).to.have.members([upath.resolve(swDest)]);\n\n      await validateServiceWorkerRuntime({\n        entryPoint: 'injectManifest',\n        swFile: swDest,\n        expectedMethodCalls: {\n          precacheAndRoute: [\n            [\n              PRECACHE_ORDER,\n              {\n                cleanURLs: true,\n              },\n            ],\n          ],\n        },\n      });\n    });\n\n    it(`should ignore swSrc and swDest when generating manifest entries`, async function () {\n      const tempDirectory = tempy.directory();\n      await fse.copy(BASE_OPTIONS.globDirectory, tempDirectory);\n      const swSrc = upath.join(tempDirectory, 'sw-src-service-worker.js');\n      await fse.copyFile(upath.join(SW_SRC_DIR, 'basic.js'), swSrc);\n      const swDest = upath.join(tempDirectory, 'sw-dest-service-worker.js');\n      await fse.createFile(swDest);\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swSrc,\n        swDest,\n        globDirectory: tempDirectory,\n      });\n\n      const {count, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      await validateServiceWorkerRuntime({\n        entryPoint: 'injectManifest',\n        swFile: swDest,\n        expectedMethodCalls: {\n          precacheAndRoute: [[PRECACHE_ORDER]],\n        },\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Sourcemap manipulation`, function () {\n    it(`should update the sourcemap to account for manifest injection`, async function () {\n      const outputDir = tempy.directory();\n      const swSrc = upath.join(SW_SRC_DIR, 'basic-with-sourcemap.js.nolint');\n      const swDest = upath.join(outputDir, 'basic-with-sourcemap.js');\n      const sourcemapDest = upath.join(\n        outputDir,\n        'basic-with-sourcemap.js.map',\n      );\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        swSrc,\n      });\n\n      const {count, filePaths, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n      expect(filePaths).to.have.members([upath.resolve(swDest), sourcemapDest]);\n\n      const actualSourcemap = await fse.readJSON(sourcemapDest);\n      const expectedSourcemap = await fse.readJSON(\n        upath.join(SW_SRC_DIR, '..', 'expected-source-map.js.map'),\n      );\n      expect(actualSourcemap).to.eql(expectedSourcemap);\n\n      // We can't validate the SW file contents.\n    });\n\n    it(`should not update the sourcemap if it uses a data: URL`, async function () {\n      const outputDir = tempy.directory();\n      const swSrc = upath.join(\n        SW_SRC_DIR,\n        'basic-with-sourcemap-data-url.js.nolint',\n      );\n      const swDest = upath.join(outputDir, 'basic-with-sourcemap.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        swSrc,\n      });\n\n      const {count, size, warnings} = await injectManifest(options);\n      expect(warnings).to.be.empty;\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      // We can't validate the SW file contents.\n    });\n\n    it(`should perform injection, but report a warning if the sourcemap file can't be found`, async function () {\n      const outputDir = tempy.directory();\n      const swSrc = upath.join(\n        SW_SRC_DIR,\n        'basic-with-invalid-sourcemap.js.nolint',\n      );\n      const swDest = upath.join(outputDir, 'basic-with-sourcemap.js');\n      const options = Object.assign({}, BASE_OPTIONS, {\n        swDest,\n        swSrc,\n      });\n\n      const {count, size, warnings} = await injectManifest(options);\n      expect(warnings.length).to.eql(1);\n      expect(warnings[0]).to.include(errors['cant-find-sourcemap']);\n      expect(count).to.eql(6);\n      // Line ending differences lead to different sizes on Windows.\n      expect(size).to.be.oneOf([2782, 2698]);\n\n      // We can't validate the SW file contents.\n    });\n  });\n\n  describe(`[workbox-build] removed options`, function () {\n    // These were deprecated in v4, and formally removed in v5.\n    const oldOptionsToValue = {\n      dontCacheBustUrlsMatching: /ignored/,\n      ignoreUrlParametersMatching: [/ignored/],\n      modifyUrlPrefix: {\n        ignored: 'ignored',\n      },\n      templatedUrls: {},\n    };\n\n    for (const [option, value] of Object.entries(oldOptionsToValue)) {\n      it(`should fail validation when ${option} is used`, async function () {\n        const options = Object.assign({}, BASE_OPTIONS, {\n          [option]: value,\n        });\n\n        await expect(injectManifest(options)).to.eventually.be.rejectedWith(\n          WorkboxConfigError,\n          option,\n        );\n      });\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/additional-manifest-entries-transform.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\nconst {\n  additionalManifestEntriesTransform,\n} = require('../../../../packages/workbox-build/build/lib/additional-manifest-entries-transform');\n\ndescribe(`[workbox-build] lib/additional-manifest-entries-transform`, function () {\n  function getManifest() {\n    return [\n      {\n        url: '/first',\n        revision: null,\n      },\n    ];\n  }\n\n  it(`should not make any changes when additionalManifestEntries is empty`, function () {\n    const transform = additionalManifestEntriesTransform([]);\n    expect(transform(getManifest())).to.eql({\n      manifest: [{url: '/first', revision: null}],\n      warnings: [],\n    });\n  });\n\n  it(`should add the additionalManifestEntries to the end of the existing manifest`, function () {\n    const transform = additionalManifestEntriesTransform([\n      {url: '/second', revision: null},\n      {url: '/third', revision: null},\n    ]);\n\n    expect(transform(getManifest())).to.eql({\n      manifest: [\n        {url: '/first', revision: null},\n        {url: '/second', size: 0, revision: null},\n        {url: '/third', size: 0, revision: null},\n      ],\n      warnings: [],\n    });\n  });\n\n  it(`should return a warning, along with the modified manifest, when additionalManifestEntries contains a string or an entry without revision`, function () {\n    const transform = additionalManifestEntriesTransform([\n      '/second',\n      {url: '/third'},\n    ]);\n\n    expect(transform(getManifest())).to.eql({\n      manifest: [\n        {url: '/first', revision: null},\n        {url: '/second', size: 0, revision: null},\n        {url: '/third', size: 0},\n      ],\n      warnings: [\n        errors['string-entry-warning'] + '\\n  - /second\\n  - /third\\n',\n      ],\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/bundle.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\ndescribe(`[workbox-build] lib/bundle`, function () {\n  const MODULE_PATH = '../../../../packages/workbox-build/build/lib/bundle';\n  let bundle;\n  let stubs;\n\n  beforeEach(function () {\n    const rollupStub = {\n      generate: sinon.stub().resolves({\n        output: [\n          {\n            fileName: 'asset-filename',\n            type: 'asset',\n            source: 'asset-source',\n          },\n          {\n            code: 'chunk1-code',\n            fileName: 'chunk1-filename',\n            type: 'chunk',\n          },\n          {\n            code: 'chunk2-code',\n            fileName: 'chunk2-filename',\n            type: 'chunk',\n            map: 'sourcemap-contents',\n          },\n        ],\n      }),\n    };\n\n    stubs = {\n      rollupStub,\n      '@babel/preset-env': sinon.stub(),\n      'fs-extra': {\n        writeFile: sinon.stub().resolves(),\n      },\n      'upath': {\n        format: sinon.stub().callsFake((args) => `${args.dir}${args.base}`),\n        parse: sinon.stub().returns({base: 'sw.js', dir: ''}),\n      },\n      'tempy': {\n        file: sinon.stub().returns('sw.js'),\n      },\n      '@rollup/plugin-node-resolve': sinon.stub(),\n      '@rollup/plugin-replace': sinon.stub(),\n      '@rollup/plugin-babel': {\n        babel: sinon.stub(),\n      },\n      '@rollup/plugin-terser': sinon.stub(),\n      '@trickfilm400/rollup-plugin-off-main-thread': sinon.stub(),\n      'rollup': {\n        rollup: sinon.stub().resolves(rollupStub),\n      },\n    };\n\n    bundle = proxyquire(MODULE_PATH, stubs).bundle;\n  });\n\n  it(`should pass 'babelPresetEnvTargets' to @babel/preset-env`, async function () {\n    const babelPresetEnvTargets = ['target1', 'target2'];\n\n    await bundle({\n      babelPresetEnvTargets,\n    });\n\n    // This is ugly, but necessary due to the way babel() is configured.\n    const babelParams = stubs['@rollup/plugin-babel'].babel.args[0][0];\n    expect(babelParams.presets[0][1].targets.browsers).to.eql(\n      babelPresetEnvTargets,\n    );\n  });\n\n  it(`should use loadz0r when 'inlineWorkboxRuntime' is false`, async function () {\n    await bundle({\n      inlineWorkboxRuntime: false,\n    });\n\n    expect(stubs['@trickfilm400/rollup-plugin-off-main-thread'].calledOnce).to\n      .be.true;\n  });\n\n  it(`should not use loadz0r when 'inlineWorkboxRuntime' is true`, async function () {\n    await bundle({\n      inlineWorkboxRuntime: true,\n    });\n\n    expect(stubs['@trickfilm400/rollup-plugin-off-main-thread'].notCalled).to.be\n      .true;\n  });\n\n  it(`should replace NODE_ENV with the 'mode' value`, async function () {\n    const mode = 'mode-value';\n    await bundle({\n      mode,\n    });\n\n    expect(stubs['@rollup/plugin-replace'].args).to.eql([\n      [\n        {\n          'preventAssignment': true,\n          'process.env.NODE_ENV': `\"${mode}\"`,\n        },\n      ],\n    ]);\n  });\n\n  it(`should use terser when 'mode' is 'production'`, async function () {\n    const mode = 'production';\n    await bundle({\n      mode,\n    });\n\n    expect(stubs['@rollup/plugin-terser'].calledOnce).to.be.true;\n  });\n\n  it(`should not use terser when 'mode' is not 'production'`, async function () {\n    const mode = 'something-else';\n    await bundle({\n      mode,\n    });\n\n    expect(stubs['@rollup/plugin-terser'].notCalled).to.be.true;\n  });\n\n  it(`should pass the 'sourcemap' parameter value through to Rollup`, async function () {\n    const sourcemap = true;\n    await bundle({\n      sourcemap,\n    });\n\n    expect(stubs.rollupStub.generate.args[0][0].sourcemap).to.eql(sourcemap);\n  });\n\n  it(`should process the generated Rollup bundle into the expected return value`, async function () {\n    const files = await bundle({});\n\n    expect(files).to.eql([\n      {\n        contents: 'asset-source',\n        name: 'asset-filename',\n      },\n      {\n        contents: 'chunk1-code',\n        name: 'chunk1-filename',\n      },\n      {\n        contents: 'sourcemap-contents',\n        name: 'chunk2-filename.map',\n      },\n      {\n        contents: 'chunk2-code//# sourceMappingURL=chunk2-filename.map\\n',\n        name: 'chunk2-filename',\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/cdn-utils.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst cdnUtils = require('../../../../packages/workbox-build/build/lib/cdn-utils');\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/cdn-utils.js`, function () {\n  const CDN_ORIGIN = 'https://storage.googleapis.com/workbox-cdn/releases';\n\n  it(`getModuleURL() should throw when moduleName is undefined`, function () {\n    expect(() => cdnUtils.getModuleURL()).to.throw(errors['no-module-name']);\n  });\n\n  it(`getModuleURL('workbox-sw', 'dev') should throw`, function () {\n    expect(() => cdnUtils.getModuleURL('workbox-sw', 'dev')).to.throw(\n      'workbox-sw',\n    );\n  });\n\n  it(`getModuleURL(moduleName) should return the expected URL`, function () {\n    const moduleName = 'workbox-sw';\n    const url = cdnUtils.getModuleURL(moduleName);\n\n    expect(url.startsWith(CDN_ORIGIN)).to.be.true;\n    expect(url.includes(moduleName)).to.be.true;\n  });\n\n  it(`getModuleURL('workbox-routing', buildType) should return the expected URL`, function () {\n    const moduleName = 'workbox-routing';\n    const buildType = 'prod';\n\n    const url = cdnUtils.getModuleURL(moduleName, buildType);\n\n    expect(url.startsWith(CDN_ORIGIN)).to.be.true;\n    expect(url.includes(moduleName)).to.be.true;\n    expect(url.includes(buildType)).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/copy-workbox-libraries.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/copy-workbox-libraries.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/copy-workbox-libraries';\n  const ABSOLUTE_DEST_DIRECTORY = upath.join('/', 'test-dir');\n  const RELATIVE_DEST_DIRECTORY = upath.join('.', 'test-dir');\n\n  it(`should reject with an error when the copy fails`, async function () {\n    const {copyWorkboxLibraries} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        ensureDir: sinon.stub().resolves(),\n        copy: sinon.stub().rejects('INJECTED_ERROR'),\n      },\n    });\n\n    try {\n      await copyWorkboxLibraries(ABSOLUTE_DEST_DIRECTORY);\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(\n        errors['unable-to-copy-workbox-libraries'],\n      );\n    }\n  });\n\n  for (const destDir of [ABSOLUTE_DEST_DIRECTORY, RELATIVE_DEST_DIRECTORY]) {\n    it(`should resolve with the new directory name, using a destDir of ${destDir}`, async function () {\n      const copyStub = sinon.stub().resolves();\n      const ensureDirStub = sinon.stub().resolves();\n\n      const {copyWorkboxLibraries} = proxyquire(MODULE_PATH, {\n        'fs-extra': {\n          copy: copyStub,\n          ensureDir: ensureDirStub,\n        },\n      });\n\n      const workboxDirectory = await copyWorkboxLibraries(destDir);\n      // The workboxDirectory value is a relative path from destDir to the\n      // new directory. We check if ensureDir was called with the combined upath.\n      const expectedPath = upath.join(destDir, workboxDirectory);\n      expect(expectedPath).to.eql(ensureDirStub.args[0][0]);\n\n      // The total number of package build directories that were copied:\n      expect(copyStub.callCount).to.eql(15);\n    });\n  }\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/escape-regexp.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst {\n  escapeRegExp,\n} = require('../../../../packages/workbox-build/build/lib/escape-regexp');\n\ndescribe(`[workbox-build] lib/copy-workbox-libraries.js`, function () {\n  const expectedValues = new Map([\n    ['abcd', 'abcd'],\n    ['\\\\abc()d', '\\\\\\\\abc\\\\(\\\\)d'],\n    ['$?.js', '\\\\$\\\\?\\\\.js'],\n    ['.*+?^${}()|[]\\\\', '\\\\.\\\\*\\\\+\\\\?\\\\^\\\\$\\\\{\\\\}\\\\(\\\\)\\\\|\\\\[\\\\]\\\\\\\\'],\n  ]);\n\n  for (const [original, escaped] of expectedValues) {\n    it(`should perform the expected escaping: ${original} => ${escaped}`, async function () {\n      expect(escapeRegExp(original)).to.eql(escaped);\n    });\n  }\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-composite-details.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {\n  getCompositeDetails,\n} = require('../../../../packages/workbox-build/build/lib/get-composite-details');\n\ndescribe(`[workbox-build] lib/get-composite-details.js`, function () {\n  const URL = '/test';\n\n  const ENTRY1 = {\n    file: 'file1.txt',\n    size: 1234,\n    hash: 'hash1',\n  };\n  const ENTRY2 = {\n    file: 'file2.txt',\n    size: 5678,\n    hash: 'hash2',\n  };\n\n  it(`should return the expected composite details for a single file`, function () {\n    const details = getCompositeDetails(URL, [ENTRY1]);\n    expect(details).to.eql({\n      file: URL,\n      hash: '00c6ee2e21a7548de6260cf72c4f4b5b',\n      size: ENTRY1.size,\n    });\n  });\n\n  it(`should return the expected composite details for multiple files`, function () {\n    const details = getCompositeDetails(URL, [ENTRY1, ENTRY2]);\n    expect(details).to.eql({\n      file: URL,\n      hash: '3dcd1f089c4a94cbbedb7a00d7ec9829',\n      size: ENTRY1.size + ENTRY2.size,\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-file-details.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\nconst proxyquire = require('proxyquire');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/get-file-details.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/get-file-details';\n  const GLOB_DIRECTORY = './';\n  const GLOB_PATTERN = 'file*';\n  const DIRECTORY = 'directory';\n  const FILE1 = 'file1.txt';\n  const FILE2 = 'file2.js';\n  const SIZE = 1234;\n  const HASH = 'example-hash';\n\n  it(`should throw when there's a glob.globSync() error`, function () {\n    const {getFileDetails} = proxyquire(MODULE_PATH, {\n      glob: {\n        globSync: () => {\n          throw new Error();\n        },\n      },\n    });\n\n    try {\n      getFileDetails({\n        globDirectory: GLOB_DIRECTORY,\n        globPattern: GLOB_PATTERN,\n      });\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['unable-to-glob-files']);\n    }\n  });\n\n  it(`should return a warning when the pattern doesn't match anything`, function () {\n    const {getFileDetails} = proxyquire(MODULE_PATH, {\n      glob: {\n        globSync: () => [],\n      },\n    });\n\n    const {globbedFileDetails, warning} = getFileDetails({\n      globDirectory: GLOB_DIRECTORY,\n      globPattern: GLOB_PATTERN,\n    });\n    expect(globbedFileDetails).to.be.empty;\n    expect(warning).to.have.string(errors['useless-glob-pattern']);\n  });\n\n  it(`should return array of file details, without null values`, function () {\n    const {getFileDetails} = proxyquire(MODULE_PATH, {\n      'glob': {\n        globSync: () => {\n          return [FILE1, FILE2, DIRECTORY];\n        },\n      },\n      './get-file-size': {\n        getFileSize: (value) => {\n          if (upath.normalize(value) === upath.normalize(DIRECTORY)) {\n            return null;\n          }\n          return SIZE;\n        },\n      },\n      './get-file-hash': {\n        getFileHash: (value) => {\n          if (upath.normalize(value) === upath.normalize(DIRECTORY)) {\n            throw new Error(\n              `getFileHash(${DIRECTORY}) shouldn't have been called.`,\n            );\n          }\n          return HASH;\n        },\n      },\n    });\n\n    const {globbedFileDetails, warning} = getFileDetails({\n      globDirectory: GLOB_DIRECTORY,\n      globPattern: GLOB_PATTERN,\n    });\n\n    expect(warning).to.eql('');\n    expect(globbedFileDetails).to.deep.equal([\n      {\n        file: FILE1,\n        hash: HASH,\n        size: SIZE,\n      },\n      {\n        file: FILE2,\n        hash: HASH,\n        size: SIZE,\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-file-hash.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/get-file-hash.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/get-file-hash';\n  const FILE = 'file.txt';\n\n  it(`should throw when there's a fs.readFileSync() error`, function () {\n    const {getFileHash} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        readFileSync: () => {\n          throw new Error();\n        },\n      },\n    });\n\n    try {\n      getFileHash(FILE);\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['unable-to-get-file-hash']);\n    }\n  });\n\n  it(`should return the hash corresponding to a file's contents`, function () {\n    const buffer = Buffer.alloc(10);\n    const hashForBuffer = 'a63c90cc3684ad8b0a2176a6a8fe9005';\n\n    const {getFileHash} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        readFileSync: (file) => {\n          if (file !== FILE) {\n            throw new Error(`Unexpected file name: ${file}`);\n          }\n          return buffer;\n        },\n      },\n    });\n\n    const hash = getFileHash(FILE);\n    expect(hash).to.eql(hashForBuffer);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-file-manifest-entries.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] Test getFileManifestEntries`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/get-file-manifest-entries';\n  const GLOB_DIRECTORY = './';\n  const GLOB_PATTERNS = ['invalid*'];\n  const FILE = {\n    file: 'file1.txt',\n    size: 1234,\n    hash: 'hash1',\n  };\n\n  it(`should return empty info when neither globDirectory nor templatedURLs are provided`, async function () {\n    const {getFileManifestEntries} = require(MODULE_PATH);\n\n    const {count, size, manifestEntries} = await getFileManifestEntries({});\n\n    expect(count).to.eql(0);\n    expect(size).to.eql(0);\n    expect(manifestEntries).to.have.lengthOf(0);\n  });\n\n  it(`should not return the same file twice`, async function () {\n    const {getFileManifestEntries} = proxyquire(MODULE_PATH, {\n      './get-file-details': {\n        getFileDetails: () => {\n          return {\n            globbedFileDetails: [FILE, FILE],\n            warning: undefined,\n          };\n        },\n      },\n    });\n\n    const {count, size, manifestEntries} = await getFileManifestEntries({\n      globDirectory: GLOB_DIRECTORY,\n      globPatterns: GLOB_PATTERNS,\n    });\n\n    expect(count).to.eql(1);\n    expect(size).to.eql(FILE.size);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: FILE.file,\n        revision: FILE.hash,\n      },\n    ]);\n  });\n\n  it(`should throw when a templatedURL matches a globbed file`, async function () {\n    const {getFileManifestEntries} = proxyquire(MODULE_PATH, {\n      './get-file-details': {\n        getFileDetails: () => {\n          return {\n            globbedFileDetails: [FILE],\n            warning: undefined,\n          };\n        },\n      },\n    });\n\n    try {\n      await getFileManifestEntries({\n        globDirectory: GLOB_DIRECTORY,\n        globPatterns: GLOB_PATTERNS,\n        templatedURLs: {\n          [FILE.file]: '',\n        },\n      });\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(\n        errors['templated-url-matches-glob'],\n      );\n    }\n  });\n\n  it(`should treat an exception thrown by getFileDetails() as a warning message`, async function () {\n    const warningMessage = 'test warning';\n    const {getFileManifestEntries} = proxyquire(MODULE_PATH, {\n      './get-file-details': {\n        getFileDetails: () => {\n          throw new Error(warningMessage);\n        },\n      },\n    });\n\n    const {warnings} = await getFileManifestEntries({\n      globDirectory: GLOB_DIRECTORY,\n      globPatterns: GLOB_PATTERNS,\n      templatedURLs: {\n        [FILE.file]: '',\n      },\n    });\n\n    expect(warnings).to.eql([warningMessage]);\n  });\n\n  it(`should throw when a templatedURL contains a pattern that doesn't match anything`, async function () {\n    const {getFileManifestEntries} = require(MODULE_PATH);\n\n    try {\n      await getFileManifestEntries({\n        globDirectory: GLOB_DIRECTORY,\n        templatedURLs: {\n          [FILE.file]: GLOB_PATTERNS,\n        },\n      });\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['bad-template-urls-asset']);\n    }\n  });\n\n  it(`should return results that take both glob patterns and templatedURLs into account`, async function () {\n    const url1 = '/path/to/url1';\n    const url2 = '/path/to/url2';\n    const stringValue = 'string';\n\n    const {getFileManifestEntries} = proxyquire(MODULE_PATH, {\n      './get-file-details': {\n        getFileDetails: () => {\n          return {\n            globbedFileDetails: [FILE],\n            warning: undefined,\n          };\n        },\n      },\n    });\n\n    const {count, size, manifestEntries} = await getFileManifestEntries({\n      globDirectory: GLOB_DIRECTORY,\n      globPatterns: GLOB_PATTERNS,\n      templatedURLs: {\n        [url1]: GLOB_PATTERNS,\n        [url2]: stringValue,\n      },\n    });\n\n    expect(count).to.eql(3);\n    expect(size).to.eql(FILE.size + FILE.size + stringValue.length);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: FILE.file,\n        revision: FILE.hash,\n      },\n      {\n        url: url1,\n        // This is the hash of FILE.hash.\n        revision: '00c6ee2e21a7548de6260cf72c4f4b5b',\n      },\n      {\n        url: url2,\n        // THis is the hash of stringValue.\n        revision: 'b45cffe084dd3d20d928bee85e7b0f21',\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-file-size.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../packages/workbox-build/build/lib/errors.js');\n\ndescribe(`[workbox-build] lib/get-file-size.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/get-file-size';\n  const FILE = 'file.txt';\n\n  it(`should throw when fs.statSync() fails`, function () {\n    const {getFileSize} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        statSync: () => {\n          throw new Error();\n        },\n      },\n    });\n\n    try {\n      getFileSize(FILE);\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['unable-to-get-file-size']);\n    }\n  });\n\n  it(`should return null for non-files`, function () {\n    const {getFileSize} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        statSync: () => {\n          return {\n            isFile: () => false,\n          };\n        },\n      },\n    });\n\n    const size = getFileSize(FILE);\n    expect(size).not.to.exist;\n  });\n\n  it(`should return the expected file size`, function () {\n    const expectedSize = 1234;\n\n    const {getFileSize} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        statSync: () => {\n          return {\n            isFile: () => true,\n            size: expectedSize,\n          };\n        },\n      },\n    });\n\n    const size = getFileSize(FILE);\n    expect(size).to.eql(expectedSize);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-string-details.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {\n  getStringDetails,\n} = require('../../../../packages/workbox-build/build/lib/get-string-details');\n\ndescribe(`[workbox-build] lib/get-string-details.js`, function () {\n  it(`should return the expected details`, function () {\n    const inputToExpectedDetails = new Map([\n      [\n        ['/url-one', 'first-one'],\n        {\n          file: '/url-one',\n          hash: 'e725107146e32e2e7e75feaa303b7fbc',\n          size: 9,\n        },\n      ],\n      [\n        ['/url-two', 'another-string'],\n        {\n          file: '/url-two',\n          hash: '7fb80c5fad3565fd6ce3d9f61a53c659',\n          size: 14,\n        },\n      ],\n    ]);\n\n    for (const [[url, string], expectedDetails] of inputToExpectedDetails) {\n      const details = getStringDetails(url, string);\n      expect(details).to.eql(expectedDetails);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/get-string-hash.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {\n  getStringHash,\n} = require('../../../../packages/workbox-build/build/lib/get-string-hash');\n\ndescribe(`[workbox-build] lib/get-string-hash.js`, function () {\n  it(`should return the expected hashes`, function () {\n    const stringsToHashes = new Map([\n      ['abc', '900150983cd24fb0d6963f7d28e17f72'],\n      ['xyz', 'd16fb36f0911f878998c136191af705e'],\n    ]);\n\n    for (const [string, hash] of stringsToHashes) {\n      expect(getStringHash(string)).to.eql(hash);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/modify-url-prefix-transform.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\nconst {\n  modifyURLPrefixTransform,\n} = require('../../../../packages/workbox-build/build/lib/modify-url-prefix-transform');\n\ndescribe(`[workbox-build] lib/modify-url-prefix-transform.js`, function () {\n  function getManifest() {\n    return [\n      {\n        url: '/first-match/12345/hello',\n      },\n      {\n        url: '/second-match/12345/hello',\n      },\n    ];\n  }\n\n  it(`should handle bad URLs in the manifest`, function () {\n    const badInputs = [null, undefined, true, false, {}, []];\n\n    const modifications = {\n      '/example-1': '/example-1-altered',\n      '/example-2/multi-section/1234': '/example-2-altered/5678',\n    };\n\n    const transform = modifyURLPrefixTransform(modifications);\n    for (const badInput of badInputs) {\n      expect(() => transform([{url: badInput}])).to.throw(\n        errors['manifest-entry-bad-url'],\n      );\n    }\n  });\n\n  it(`should handle bad modifyURLPrefixTransform input`, function () {\n    const badInputs = [\n      null,\n      undefined,\n      true,\n      false,\n      [],\n      '',\n      {\n        Hi: [],\n      },\n    ];\n\n    for (const badInput of badInputs) {\n      expect(() => modifyURLPrefixTransform(badInput)).to.throw(\n        errors['modify-url-prefix-bad-prefixes'],\n      );\n    }\n  });\n\n  it(`should strip prefixes`, function () {\n    const modifications = {\n      '/first-match': '',\n    };\n\n    const transform = modifyURLPrefixTransform(modifications);\n    expect(transform(getManifest())).to.eql({\n      manifest: [\n        {\n          url: '/12345/hello',\n        },\n        {\n          url: '/second-match/12345/hello',\n        },\n      ],\n    });\n  });\n\n  it(`should prepend prefixes`, function () {\n    const modifications = {\n      '': '/public',\n    };\n\n    const transform = modifyURLPrefixTransform(modifications);\n    expect(transform(getManifest())).to.eql({\n      manifest: [\n        {\n          url: '/public/first-match/12345/hello',\n        },\n        {\n          url: '/public/second-match/12345/hello',\n        },\n      ],\n    });\n  });\n\n  it(`should only replace the initial match`, function () {\n    const modifications = {\n      '/first-match': '/second-match',\n      '/second-match': '/third-match',\n    };\n\n    const transform = modifyURLPrefixTransform(modifications);\n    expect(transform(getManifest())).to.eql({\n      manifest: [\n        {\n          url: '/second-match/12345/hello',\n        },\n        {\n          url: '/third-match/12345/hello',\n        },\n      ],\n    });\n  });\n\n  it(`should not replace when the match is not at the start of the URL`, function () {\n    const modifications = {\n      '/hello': '/altered',\n    };\n\n    const transform = modifyURLPrefixTransform(modifications);\n    expect(transform(getManifest())).to.eql({manifest: getManifest()});\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/module-registry.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\n\nconst {\n  ModuleRegistry,\n} = require('../../../../packages/workbox-build/build/lib/module-registry');\n\ndescribe(`[workbox-build] lib/module-registry.js`, function () {\n  let moduleRegistry;\n  // We can't use proxyquire to override require.resolve(), so let's get the\n  // actual expected base path that will be used in the test cases.\n  const basePath = upath.resolve(__dirname, '..', '..', '..', '..');\n\n  beforeEach(() => {\n    moduleRegistry = new ModuleRegistry();\n  });\n\n  describe(`getImportStatements()`, function () {\n    it(`should return [] when nothing is used`, function () {\n      expect(moduleRegistry.getImportStatements()).to.be.empty;\n    });\n\n    it(`return the expected output given multiple calls to use()`, function () {\n      const module1Name = moduleRegistry.use('workbox-core', 'index');\n      // Multiple use()s should result in only one entry.\n      moduleRegistry.use('workbox-core', 'index');\n      const module2Name = moduleRegistry.use('workbox-routing', 'index');\n\n      const importStatements = moduleRegistry.getImportStatements();\n\n      expect(importStatements).to.have.members([\n        `import {index as workbox_core_index} from '${basePath}/packages/workbox-core/index.mjs';`,\n        `import {index as workbox_routing_index} from '${basePath}/packages/workbox-routing/index.mjs';`,\n      ]);\n\n      expect(importStatements[0]).to.contain(module1Name);\n      expect(importStatements[1]).to.contain(module2Name);\n    });\n  });\n\n  describe(`getLocalName()`, function () {\n    it(`should return the expected name`, function () {\n      expect(moduleRegistry.getLocalName('a-b-c', 'd')).to.eql('a_b_c_d');\n    });\n\n    it(`should return the expected name when called via use()`, function () {\n      expect(moduleRegistry.use('a-b-c', 'd')).to.eql('a_b_c_d');\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/no-revision-for-urls-matching-transform.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\nconst {\n  noRevisionForURLsMatchingTransform,\n} = require('../../../../packages/workbox-build/build/lib/no-revision-for-urls-matching-transform');\n\ndescribe(`[workbox-build] lib/no-revision-for-urls-matching-transform.js`, function () {\n  const MANIFEST = [\n    {\n      url: '/first-match/12345/hello',\n      revision: '1234abcd',\n    },\n    {\n      url: '/second-match/12345/hello',\n      revision: '1234abcd',\n    },\n    {\n      url: '/third-match/12345/hello',\n    },\n  ];\n\n  it(`should handle bad URLs in the manifest`, function () {\n    const badInputs = [null, undefined, true, false, {}, []];\n\n    const transform = noRevisionForURLsMatchingTransform(/ignored/);\n    for (const badInput of badInputs) {\n      expect(() => transform([{url: badInput}])).to.throw(\n        errors['manifest-entry-bad-url'],\n      );\n    }\n  });\n\n  it(`should handle bad dontCacheBustURLsMatching input`, function () {\n    const badInputs = [\n      null,\n      undefined,\n      true,\n      false,\n      [],\n      '',\n      {\n        Hi: [],\n      },\n    ];\n\n    for (const badInput of badInputs) {\n      expect(() => noRevisionForURLsMatchingTransform(badInput)).to.throw(\n        errors['invalid-dont-cache-bust'],\n      );\n    }\n  });\n\n  it(`should set revision info to null in a single matching entry`, function () {\n    const transform = noRevisionForURLsMatchingTransform(/first-match/);\n    expect(transform(MANIFEST)).to.eql({\n      manifest: [\n        {\n          url: '/first-match/12345/hello',\n          revision: null,\n        },\n        {\n          url: '/second-match/12345/hello',\n          revision: '1234abcd',\n        },\n        {\n          url: '/third-match/12345/hello',\n        },\n      ],\n    });\n  });\n\n  it(`should set revision info to null in multiple matching entries`, function () {\n    const transform = noRevisionForURLsMatchingTransform(/12345/);\n    expect(transform(MANIFEST)).to.eql({\n      manifest: [\n        {\n          url: '/first-match/12345/hello',\n          revision: null,\n        },\n        {\n          url: '/second-match/12345/hello',\n          revision: null,\n        },\n        {\n          url: '/third-match/12345/hello',\n          revision: null,\n        },\n      ],\n    });\n  });\n\n  it(`should do nothing when there's a match for an entry without a revision`, function () {\n    const transform = noRevisionForURLsMatchingTransform(/third-match/);\n    expect(transform(MANIFEST)).to.eql({manifest: MANIFEST});\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/populate-sw-template.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/populate-sw-template.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/populate-sw-template';\n\n  it(`should throw an error if templating fails`, function () {\n    const manifestEntries = ['ignored'];\n\n    const {populateSWTemplate} = proxyquire(MODULE_PATH, {\n      'lodash/template': () => {\n        throw new Error();\n      },\n    });\n\n    try {\n      populateSWTemplate({manifestEntries});\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['populating-sw-tmpl-failed']);\n    }\n  });\n\n  it(`should throw an error if both manifestEntries and runtimeCaching are empty`, function () {\n    const {populateSWTemplate} = proxyquire(MODULE_PATH, {\n      'lodash/template': () => {},\n    });\n\n    try {\n      populateSWTemplate({manifestEntries: [], runtimeCaching: []});\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(\n        errors['no-manifest-entries-or-runtime-caching'],\n      );\n    }\n  });\n\n  it(`should pass the expected options to the template using mostly defaults`, function () {\n    const runtimeCachingPlaceholder = 'runtime-caching-placeholder';\n    const swTemplate = 'template';\n    const precacheOptionsString = '{}';\n    const manifestEntries = ['ignored'];\n\n    const innerStub = sinon.stub().returns('');\n    const outerStub = sinon.stub().returns(innerStub);\n    const {populateSWTemplate} = proxyquire(MODULE_PATH, {\n      'lodash/template': outerStub,\n      './runtime-caching-converter': {\n        runtimeCachingConverter: () => runtimeCachingPlaceholder,\n      },\n      '../templates/sw-template': {swTemplate},\n    });\n\n    populateSWTemplate({manifestEntries});\n\n    expect(outerStub.alwaysCalledWith(swTemplate)).to.be.true;\n\n    // Doing a strict comparison with functions isn't easy.\n    expect(innerStub.args[0][0].use).to.be.a('function');\n    delete innerStub.args[0][0].use;\n\n    expect(innerStub.args[0]).to.eql([\n      {\n        manifestEntries,\n        cacheId: undefined,\n        cleanupOutdatedCaches: undefined,\n        clientsClaim: undefined,\n        disableDevLogs: undefined,\n        importScripts: undefined,\n        navigateFallback: undefined,\n        navigateFallbackDenylist: undefined,\n        navigateFallbackAllowlist: undefined,\n        navigationPreload: undefined,\n        offlineAnalyticsConfigString: undefined,\n        precacheOptionsString,\n        runtimeCaching: runtimeCachingPlaceholder,\n        skipWaiting: undefined,\n      },\n    ]);\n  });\n\n  it(`should pass the expected options to the template`, function () {\n    const cacheId = 'test-cache-id';\n    const cleanupOutdatedCaches = true;\n    const clientsClaim = true;\n    const directoryIndex = 'index.html';\n    const disableDevLogs = true;\n    const handleFetch = true;\n    const ignoreURLParametersMatching = [/a/, /b/];\n    const importScripts = ['test.js'];\n    const manifestEntries = [{url: '/path/to/index.html', revision: '1234'}];\n    const navigateFallback = '/shell.html';\n    const navigateFallbackDenylist = [/another-test/];\n    const navigateFallbackAllowlist = [/test/];\n    const navigationPreload = true;\n    const offlineGoogleAnalytics = true;\n    const offlineAnalyticsConfigString = '{}';\n    const runtimeCaching = [];\n    const runtimeCachingPlaceholder = 'runtime-caching-placeholder';\n    const skipWaiting = true;\n    const swTemplate = 'template';\n    const precacheOptionsString =\n      '{\\n  \"directoryIndex\": \"index.html\",\\n  \"ignoreURLParametersMatching\": [/a/, /b/]\\n}';\n\n    // There are two stages in templating: creating the active template function\n    // from an initial string, and passing variables to that template function\n    // to get back a final, populated template string.\n    // We need to stub out both of those steps to test the full flow.\n    const templatePopulationStub = sinon.stub().returns('');\n    const templateCreationStub = sinon.stub().returns(templatePopulationStub);\n    const {populateSWTemplate} = proxyquire(MODULE_PATH, {\n      'lodash/template': templateCreationStub,\n      './runtime-caching-converter': {\n        runtimeCachingConverter: () => runtimeCachingPlaceholder,\n      },\n      '../templates/sw-template': {swTemplate},\n    });\n\n    populateSWTemplate({\n      cacheId,\n      cleanupOutdatedCaches,\n      clientsClaim,\n      directoryIndex,\n      disableDevLogs,\n      handleFetch,\n      ignoreURLParametersMatching,\n      importScripts,\n      manifestEntries,\n      navigateFallback,\n      navigateFallbackDenylist,\n      navigateFallbackAllowlist,\n      navigationPreload,\n      offlineGoogleAnalytics,\n      runtimeCaching,\n      skipWaiting,\n    });\n\n    expect(templateCreationStub.alwaysCalledWith(swTemplate)).to.be.true;\n\n    // Doing a strict comparison with functions isn't easy.\n    expect(templatePopulationStub.args[0][0].use).to.be.a('function');\n    delete templatePopulationStub.args[0][0].use;\n\n    expect(templatePopulationStub.args[0]).to.eql([\n      {\n        cacheId,\n        cleanupOutdatedCaches,\n        clientsClaim,\n        disableDevLogs,\n        importScripts,\n        manifestEntries,\n        navigateFallback,\n        navigateFallbackDenylist,\n        navigateFallbackAllowlist,\n        navigationPreload,\n        offlineAnalyticsConfigString,\n        runtimeCaching: runtimeCachingPlaceholder,\n        precacheOptionsString,\n        skipWaiting,\n      },\n    ]);\n  });\n\n  it(`should handle a complex offlineGoogleAnalytics value when populating the template`, function () {\n    const runtimeCachingPlaceholder = 'runtime-caching-placeholder';\n    const swTemplate = 'template';\n    const precacheOptionsString = '{}';\n    const offlineGoogleAnalytics = {\n      parameterOverrides: {\n        cd1: 'offline',\n      },\n      hitFilter: (params) => {\n        // Comments are stripped.\n        params.set('cm1', params.get('qt'));\n      },\n    };\n    const offlineAnalyticsConfigString = `{\\n\\tparameterOverrides: {\\n\\t\\tcd1: 'offline'\\n\\t},\\n\\thitFilter: (params) => {\\n        \\n        params.set('cm1', params.get('qt'));\\n      }\\n}`;\n    const manifestEntries = ['ignored'];\n\n    const innerStub = sinon.stub().returns('');\n    const outerStub = sinon.stub().returns(innerStub);\n    const {populateSWTemplate} = proxyquire(MODULE_PATH, {\n      'lodash/template': outerStub,\n      './runtime-caching-converter': {\n        runtimeCachingConverter: () => runtimeCachingPlaceholder,\n      },\n      '../templates/sw-template': {swTemplate},\n    });\n\n    populateSWTemplate({manifestEntries, offlineGoogleAnalytics});\n\n    expect(outerStub.alwaysCalledWith(swTemplate)).to.be.true;\n\n    // Doing a strict comparison with functions isn't easy.\n    expect(innerStub.args[0][0].use).to.be.a('function');\n    delete innerStub.args[0][0].use;\n\n    expect(innerStub.args[0]).to.eql([\n      {\n        manifestEntries,\n        cacheId: undefined,\n        cleanupOutdatedCaches: undefined,\n        clientsClaim: undefined,\n        disableDevLogs: undefined,\n        importScripts: undefined,\n        navigateFallback: undefined,\n        navigateFallbackDenylist: undefined,\n        navigateFallbackAllowlist: undefined,\n        navigationPreload: undefined,\n        offlineAnalyticsConfigString,\n        precacheOptionsString,\n        runtimeCaching: runtimeCachingPlaceholder,\n        skipWaiting: undefined,\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/replace-and-update-source-map.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {\n  replaceAndUpdateSourceMap,\n} = require('../../../../packages/workbox-build/build/lib/replace-and-update-source-map');\n\ndescribe(`[workbox-build] lib/replace-and-update-source-map`, function () {\n  // Test case borrowed from https://github.com/Rich-Harris/magic-string/blob/a312519cfe9caa78ade7f09cc2b07459d3d17f4d/test/MagicString.js#L225\n  const JS_FILENAME = 'file.js';\n  const SOURCE = `abcdefkl`;\n  const SOURCE_MAP = {\n    file: JS_FILENAME,\n    mappings: 'AAAA,GAAG,GAAG,AAAG,CAAC',\n    names: ['testing'],\n    sourceRoot: '.',\n    sources: ['test.js'],\n    sourcesContent: ['abcdefghijkl'],\n    version: 3,\n  };\n\n  it(`should be a no-op if there's no match`, async function () {\n    const {map, source} = await replaceAndUpdateSourceMap({\n      jsFilename: JS_FILENAME,\n      originalSource: SOURCE,\n      originalMap: SOURCE_MAP,\n      searchString: 'is-not-found',\n      replaceString: 'ignored',\n    });\n\n    expect(JSON.parse(map)).to.eql(SOURCE_MAP);\n    expect(source).to.eql(SOURCE);\n  });\n\n  it(`should be perform the replacement and update the sourcemap when there is a match`, async function () {\n    const searchString = 'bc';\n    const replaceString = 'wxyz';\n\n    const {map, source} = await replaceAndUpdateSourceMap({\n      searchString,\n      replaceString,\n      jsFilename: JS_FILENAME,\n      originalSource: SOURCE,\n      originalMap: SOURCE_MAP,\n    });\n\n    const expectedSourceMap = Object.assign({}, SOURCE_MAP, {\n      mappings: 'AAAA,KAAG,GAAG,AAAG,CAAC',\n    });\n\n    expect(JSON.parse(map)).to.eql(expectedSourceMap);\n    expect(source).to.eql('awxyzdefkl');\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/runtime-caching-converter.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\nconst vm = require('vm');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\nconst {\n  ModuleRegistry,\n} = require('../../../../packages/workbox-build/build/lib/module-registry');\nconst {\n  runtimeCachingConverter,\n} = require('../../../../packages/workbox-build/build/lib/runtime-caching-converter');\n\nconst moduleRegistry = new ModuleRegistry();\n\n/**\n * Validates the method calls for a given set of runtimeCachingOptions.\n *\n * @private\n * @param {Array<Object>} runtimeCachingOptions\n * @param {Array<string>} convertedOptions\n */\nfunction validate(runtimeCachingOptions, convertedOptions) {\n  expect(convertedOptions).to.have.lengthOf(runtimeCachingOptions.length);\n\n  const globalScope = {\n    workbox_cacheable_response_CacheableResponsePlugin: sinon.spy(),\n    workbox_expiration_ExpirationPlugin: sinon.spy(),\n    workbox_background_sync_BackgroundSyncPlugin: sinon.spy(),\n    workbox_broadcast_update_BroadcastUpdatePlugin: sinon.spy(),\n    workbox_precaching_PrecacheFallbackPlugin: sinon.spy(),\n    workbox_range_requests_RangeRequestsPlugin: sinon.spy(),\n    workbox_routing_registerRoute: sinon.spy(),\n    workbox_strategies_CacheFirst: sinon.spy(),\n    workbox_strategies_CacheOnly: sinon.spy(),\n    workbox_strategies_NetworkFirst: sinon.spy(),\n    workbox_strategies_NetworkOnly: sinon.spy(),\n    workbox_strategies_StaleWhileRevalidate: sinon.spy(),\n  };\n\n  // Make it easier to find the right spy given a handler name.\n  const handlerMapping = {\n    CacheFirst: globalScope.workbox_strategies_CacheFirst,\n    CacheOnly: globalScope.workbox_strategies_CacheOnly,\n    NetworkFirst: globalScope.workbox_strategies_NetworkFirst,\n    NetworkOnly: globalScope.workbox_strategies_NetworkOnly,\n    StaleWhileRevalidate: globalScope.workbox_strategies_StaleWhileRevalidate,\n  };\n\n  const script = new vm.Script(convertedOptions.join('\\n'));\n  script.runInNewContext(globalScope);\n  runtimeCachingOptions.forEach((runtimeCachingOption, i) => {\n    const registerRouteCall =\n      globalScope.workbox_routing_registerRoute.getCall(i);\n    expect(registerRouteCall.args[0]).to.eql(runtimeCachingOption.urlPattern);\n\n    if (runtimeCachingOption.method) {\n      expect(registerRouteCall.args[2]).to.eql(runtimeCachingOption.method);\n    } else {\n      expect(registerRouteCall.args[2]).to.eql('GET');\n    }\n\n    if (typeof runtimeCachingOption.handler === 'function') {\n      // We can't make assumptions about what custom function handlers will do.\n      return;\n    }\n\n    // This validation assumes that there's only going to be one call to each\n    // named strategy per test.\n    const strategiesCall =\n      handlerMapping[runtimeCachingOption.handler].firstCall;\n    const strategiesOptions = strategiesCall.args[0];\n\n    if (runtimeCachingOption.options) {\n      const options = runtimeCachingOption.options;\n      if (options.networkTimeoutSeconds) {\n        expect(options.networkTimeoutSeconds).to.eql(\n          strategiesOptions.networkTimeoutSeconds,\n        );\n      }\n\n      if (options.cacheName) {\n        expect(options.cacheName).to.eql(strategiesOptions.cacheName);\n      }\n\n      if (options.fetchOptions) {\n        expect(options.fetchOptions).to.deep.eql(\n          strategiesOptions.fetchOptions,\n        );\n      }\n\n      if (options.matchOptions) {\n        expect(options.matchOptions).to.deep.eql(\n          strategiesOptions.matchOptions,\n        );\n      }\n\n      if (Object.keys(options.expiration).length > 0) {\n        expect(\n          globalScope.workbox_expiration_ExpirationPlugin.calledWith(\n            options.expiration,\n          ),\n        ).to.be.true;\n      }\n\n      if (options.cacheableResponse) {\n        expect(\n          globalScope.workbox_cacheable_response_CacheableResponsePlugin.calledWith(\n            options.cacheableResponse,\n          ),\n        ).to.be.true;\n      }\n\n      if (options.precacheFallback) {\n        expect(\n          globalScope.workbox_precaching_PrecacheFallbackPlugin.calledWith(\n            options.precacheFallback,\n          ),\n        ).to.be.true;\n      }\n\n      if (options.rangeRequests) {\n        expect(\n          globalScope.workbox_range_requests_RangeRequestsPlugin.calledWith(),\n        ).to.be.true;\n      }\n\n      if (options.backgroundSync) {\n        if ('options' in options.backgroundSync) {\n          expect(\n            globalScope.workbox_background_sync_BackgroundSyncPlugin.calledWith(\n              options.backgroundSync.name,\n              options.backgroundSync.options,\n            ),\n          ).to.be.true;\n        } else {\n          expect(\n            globalScope.workbox_background_sync_BackgroundSyncPlugin.calledWith(\n              options.backgroundSync.name,\n            ),\n          ).to.be.true;\n        }\n      }\n\n      if (options.broadcastUpdate) {\n        if ('options' in options.broadcastUpdate) {\n          const expectedOptions = Object.assign(\n            {channelName: options.broadcastUpdate.channelName},\n            options.broadcastUpdate.options,\n          );\n          expect(\n            globalScope.workbox_broadcast_update_BroadcastUpdatePlugin.calledWith(\n              expectedOptions,\n            ),\n          ).to.be.true;\n        } else {\n          expect(\n            globalScope.workbox_broadcast_update_BroadcastUpdatePlugin.calledWith(\n              {channelName: options.broadcastUpdate.channelName},\n            ),\n          ).to.be.true;\n        }\n      }\n    }\n  });\n}\n\ndescribe(`[workbox-build] src/lib/utils/runtime-caching-converter.js`, function () {\n  it(`should throw when urlPattern isn't set`, function () {\n    const runtimeCachingOptions = [\n      {\n        handler: 'CacheFirst',\n      },\n    ];\n\n    expect(() => {\n      runtimeCachingConverter(moduleRegistry, runtimeCachingOptions);\n    }).to.throw(errors['urlPattern-is-required']);\n  });\n\n  it(`should throw when handler isn't set`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /xyz/,\n      },\n    ];\n\n    expect(() => {\n      runtimeCachingConverter(moduleRegistry, runtimeCachingOptions);\n    }).to.throw(errors['handler-string-is-required']);\n  });\n\n  it(`should support an empty array of runtimeCaching options`, function () {\n    const runtimeCachingOptions = [];\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  it(`should support a single option with a RegExp urlPattern, using mostly defaults`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /xyz/,\n        handler: 'CacheFirst',\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  // See https://github.com/GoogleChrome/workbox/issues/574#issue-230170963\n  it(`should support multiple options, each setting multiple properties`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: 'NetworkFirst',\n        options: {\n          networkTimeoutSeconds: 20,\n          cacheName: 'abc-cache',\n          expiration: {\n            maxEntries: 5,\n            maxAgeSeconds: 50,\n          },\n          broadcastUpdate: {\n            channelName: 'test',\n          },\n          backgroundSync: {\n            name: 'test',\n          },\n          precacheFallback: {\n            fallbackURL: '/test1',\n          },\n          rangeRequests: false,\n          fetchOptions: {\n            headers: {\n              Custom: 'Header',\n            },\n          },\n        },\n      },\n      {\n        urlPattern: '/test',\n        handler: 'StaleWhileRevalidate',\n        options: {\n          expiration: {\n            maxEntries: 10,\n          },\n          cacheableResponse: {\n            statuses: [0, 200],\n          },\n          broadcastUpdate: {\n            channelName: 'test',\n            options: {\n              source: 'test-source',\n            },\n          },\n          backgroundSync: {\n            name: 'test',\n            options: {\n              maxRetentionTime: 123,\n            },\n          },\n          rangeRequests: true,\n          precacheFallback: {\n            fallbackURL: '/test2',\n          },\n          matchOptions: {\n            ignoreSearch: true,\n          },\n        },\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  it(`should support a string urlPattern, using mostly defaults`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: '/path/to/file',\n        handler: 'CacheFirst',\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  it(`should support handler being a function`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: () => {},\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  it(`should support registering non-GET methods`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: 'CacheFirst',\n        method: 'POST',\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    validate(runtimeCachingOptions, convertedOptions);\n  });\n\n  it(`should support custom plugins`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: 'CacheFirst',\n        options: {\n          plugins: [\n            {\n              cacheWillUpdate: async ({request, response}) => {\n                return response;\n              },\n              cacheDidUpdate: async ({\n                cacheName,\n                request,\n                oldResponse,\n                newResponse,\n              }) => {},\n              cachedResponseWillBeUsed: async ({\n                cacheName,\n                request,\n                matchOptions,\n                cachedResponse,\n              }) => {\n                return cachedResponse;\n              },\n              requestWillFetch: async ({request}) => {\n                return request;\n              },\n              fetchDidFail: async ({originalRequest, request, error}) => {},\n            },\n          ],\n        },\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    expect(convertedOptions[0].includes('cacheWillUpdate: async')).to.true;\n    expect(convertedOptions[0].includes('cacheDidUpdate: async')).to.true;\n    expect(convertedOptions[0].includes('cachedResponseWillBeUsed: async')).to\n      .true;\n    expect(convertedOptions[0].includes('requestWillFetch: async')).to.true;\n    expect(convertedOptions[0].includes('fetchDidFail: async')).to.true;\n  });\n\n  it(`should strip comments on custom plugins`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: 'CacheFirst',\n        options: {\n          plugins: [\n            {\n              cacheWillUpdate: async ({request, response}) => {\n                // Commenting\n                return response;\n              },\n              cachedResponseWillBeUsed: async ({\n                cacheName,\n                request,\n                matchOptions,\n                cachedResponse,\n              }) => {\n                /* Commenting */\n                return cachedResponse;\n              },\n            },\n          ],\n        },\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    expect(convertedOptions[0].includes('// Commenting')).to.false;\n    expect(convertedOptions[0].includes('/* Commenting */')).to.false;\n  });\n\n  it(`should keep contents with // that are not comments`, function () {\n    const runtimeCachingOptions = [\n      {\n        urlPattern: /abc/,\n        handler: 'CacheFirst',\n        options: {\n          plugins: [\n            {\n              cacheWillUpdate: async ({request, response}) => {\n                // Commenting\n\n                if (request.url === 'https://test.com') {\n                  return null;\n                }\n\n                return response;\n              },\n            },\n          ],\n        },\n      },\n    ];\n\n    const convertedOptions = runtimeCachingConverter(\n      moduleRegistry,\n      runtimeCachingOptions,\n    );\n    expect(convertedOptions[0].includes('// Commenting')).to.false;\n    expect(convertedOptions[0].includes('https://test.com')).to.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/transform-manifest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst {\n  transformManifest,\n} = require('../../../../packages/workbox-build/build/lib/transform-manifest');\n\ndescribe(`[workbox-build] lib/transform-manifest.js`, function () {\n  const MAXIMUM_FILE_SIZE = 1234;\n  const ENTRY1 = {\n    file: 'file1.txt',\n    size: MAXIMUM_FILE_SIZE - 1,\n    hash: 'hash1',\n  };\n  const ENTRY2 = {\n    file: 'file2.txt',\n    size: MAXIMUM_FILE_SIZE,\n    hash: 'hash2',\n  };\n  const ENTRY3 = {\n    file: 'file3.txt',\n    size: MAXIMUM_FILE_SIZE + 1,\n    hash: 'hash3',\n  };\n  const FILE_DETAILS = [ENTRY1, ENTRY2, ENTRY3];\n\n  it(`should filter out files above maximumFileSizeToCacheInBytes`, async function () {\n    const {size, count, manifestEntries} = await transformManifest({\n      maximumFileSizeToCacheInBytes: MAXIMUM_FILE_SIZE,\n      fileDetails: FILE_DETAILS,\n    });\n\n    expect(size).to.eql(ENTRY1.size + ENTRY2.size);\n    expect(count).to.eql(2);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: ENTRY1.file,\n        revision: ENTRY1.hash,\n      },\n      {\n        url: ENTRY2.file,\n        revision: ENTRY2.hash,\n      },\n    ]);\n  });\n\n  it(`should remove revision info based on dontCacheBustURLsMatching`, async function () {\n    const {size, count, manifestEntries} = await transformManifest({\n      dontCacheBustURLsMatching: new RegExp(ENTRY1.file),\n      fileDetails: FILE_DETAILS,\n    });\n\n    expect(size).to.eql(ENTRY1.size + ENTRY2.size + ENTRY3.size);\n    expect(count).to.eql(3);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: ENTRY1.file,\n        revision: null,\n      },\n      {\n        url: ENTRY2.file,\n        revision: ENTRY2.hash,\n      },\n      {\n        url: ENTRY3.file,\n        revision: ENTRY3.hash,\n      },\n    ]);\n  });\n\n  it(`should modify the URLs based on modifyURLPrefix`, async function () {\n    const prefix = 'prefix/';\n\n    const {size, count, manifestEntries} = await transformManifest({\n      modifyURLPrefix: {\n        '': prefix,\n      },\n      fileDetails: FILE_DETAILS,\n    });\n\n    expect(size).to.eql(ENTRY1.size + ENTRY2.size + ENTRY3.size);\n    expect(count).to.eql(3);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: prefix + ENTRY1.file,\n        revision: ENTRY1.hash,\n      },\n      {\n        url: prefix + ENTRY2.file,\n        revision: ENTRY2.hash,\n      },\n      {\n        url: prefix + ENTRY3.file,\n        revision: ENTRY3.hash,\n      },\n    ]);\n  });\n\n  it(`should use custom manifestTransforms`, async function () {\n    const prefix1 = 'prefix1/';\n    const prefix2 = 'prefix2/';\n\n    const warning1 = 'test warning 1';\n    const warning2 = 'test warning 1';\n\n    const transformParam = 'test param';\n\n    const transform1 = (files, param) => {\n      expect(param).to.eql(transformParam);\n\n      const manifest = files.map((file) => {\n        file.url = prefix1 + file.url;\n        return file;\n      });\n      return {manifest, warnings: [warning1]};\n    };\n\n    const transform2 = (files, param) => {\n      expect(param).to.eql(transformParam);\n\n      const manifest = files.map((file) => {\n        file.url = prefix2 + file.url;\n        return file;\n      });\n      return {manifest, warnings: [warning2]};\n    };\n\n    const {size, count, manifestEntries, warnings} = await transformManifest({\n      fileDetails: FILE_DETAILS,\n      manifestTransforms: [transform1, transform2],\n      transformParam,\n    });\n\n    expect(warnings).to.eql([warning1, warning2]);\n    expect(size).to.eql(ENTRY1.size + ENTRY2.size + ENTRY3.size);\n    expect(count).to.eql(3);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: prefix2 + prefix1 + ENTRY1.file,\n        revision: ENTRY1.hash,\n      },\n      {\n        url: prefix2 + prefix1 + ENTRY2.file,\n        revision: ENTRY2.hash,\n      },\n      {\n        url: prefix2 + prefix1 + ENTRY3.file,\n        revision: ENTRY3.hash,\n      },\n    ]);\n  });\n\n  it(`should support an async manifestTransform`, async function () {\n    const asyncTransform = async (manifest) => {\n      await Promise.resolve();\n      return {manifest, warnings: []};\n    };\n\n    const {size, count, manifestEntries, warnings} = await transformManifest({\n      fileDetails: FILE_DETAILS,\n      manifestTransforms: [asyncTransform],\n    });\n\n    expect(warnings).to.be.empty;\n    expect(size).to.eql(ENTRY1.size + ENTRY2.size + ENTRY3.size);\n    expect(count).to.eql(3);\n    expect(manifestEntries).to.deep.equal([\n      {\n        url: ENTRY1.file,\n        revision: ENTRY1.hash,\n      },\n      {\n        url: ENTRY2.file,\n        revision: ENTRY2.hash,\n      },\n      {\n        url: ENTRY3.file,\n        revision: ENTRY3.hash,\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/translate-url-to-sourcemap-paths.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/translate-url-to-sourcemap-paths.ts`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/translate-url-to-sourcemap-paths';\n  const URL = 'sw.js.map';\n  const SWSRC = 'src/sw.js';\n  const SWDEST = 'dist/sw.js';\n\n  it(`should return undefined paths when url is undefined`, function () {\n    const {translateURLToSourcemapPaths} = require(MODULE_PATH);\n\n    const {destPath, srcPath, warning} = translateURLToSourcemapPaths(\n      undefined,\n      SWSRC,\n      SWDEST,\n    );\n\n    expect(destPath).to.be.undefined;\n    expect(srcPath).to.be.undefined;\n    expect(warning).to.be.undefined;\n  });\n\n  it(`should return undefined paths when url starts with data:`, function () {\n    const {translateURLToSourcemapPaths} = require(MODULE_PATH);\n\n    const {destPath, srcPath, warning} = translateURLToSourcemapPaths(\n      `data:${URL}`,\n      SWSRC,\n      SWDEST,\n    );\n\n    expect(destPath).to.be.undefined;\n    expect(srcPath).to.be.undefined;\n    expect(warning).to.be.undefined;\n  });\n\n  it(`should return undefined paths and a warning when the resolved URL path doesn't exist`, function () {\n    const {translateURLToSourcemapPaths} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        existsSync: () => false,\n      },\n    });\n\n    const {destPath, srcPath, warning} = translateURLToSourcemapPaths(\n      URL,\n      SWSRC,\n      SWDEST,\n    );\n\n    expect(destPath).to.be.undefined;\n    expect(srcPath).to.be.undefined;\n    expect(warning).to.include(errors['cant-find-sourcemap']);\n  });\n\n  it(`should return valid paths and no warning when the resolved URL path exists`, function () {\n    const {translateURLToSourcemapPaths} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        existsSync: () => true,\n      },\n      'upath': {\n        resolve: (...args) => args.join('/'),\n      },\n    });\n\n    const {destPath, srcPath, warning} = translateURLToSourcemapPaths(\n      URL,\n      SWSRC,\n      SWDEST,\n    );\n\n    expect(destPath).to.eql('dist/sw.js.map');\n    expect(srcPath).to.eq('src/sw.js.map');\n    expect(warning).to.be.undefined;\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/validate-options.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nclass AJVFailsValidation {\n  compile() {\n    const stub = sinon.stub().returns(false);\n    stub.errors = [];\n    return stub;\n  }\n  addKeyword() {\n    // no-op\n  }\n}\n\nclass AJVPassesValidation {\n  compile() {\n    return sinon.stub().returns(true);\n  }\n  addKeyword() {\n    // no-op\n  }\n}\n\n// The integration tests will exercise the actual validation logic.\ndescribe(`[workbox-build] entry-points/options/validate-options.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/validate-options';\n  const testCases = [\n    'validateGenerateSWOptions',\n    'validateGetManifestOptions',\n    'validateInjectManifestOptions',\n  ];\n\n  for (const func of testCases) {\n    it(`${func}() should throw when validation fails`, function () {\n      const validateOptions = proxyquire(MODULE_PATH, {\n        'ajv': {\n          default: AJVFailsValidation,\n        },\n        '@apideck/better-ajv-errors': {\n          betterAjvErrors: sinon.stub().returns([\n            {\n              message: 'message1',\n              path: 'path1',\n              suggestion: 'suggestion1',\n            },\n            {\n              message: 'message2',\n              path: 'path2',\n              suggestion: 'suggestion2',\n            },\n          ]),\n        },\n      });\n\n      expect(() => validateOptions[func]()).to.throw(\n        validateOptions.WorkboxConfigError,\n        `[path1] message1. suggestion1\\n\\n[path2] message2. suggestion2`,\n      );\n    });\n\n    it(`${func}() should not throw when validation passes`, function () {\n      const validateOptions = proxyquire(MODULE_PATH, {\n        ajv: {\n          default: AJVPassesValidation,\n        },\n      });\n\n      const defaultOptions = validateOptions[func]({\n        globDirectory: '.',\n      });\n      expect(defaultOptions).to.be.an('object');\n    });\n  }\n});\n"
  },
  {
    "path": "test/workbox-build/node/lib/write-sw-using-default-template.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nconst {errors} = require('../../../../packages/workbox-build/build/lib/errors');\n\ndescribe(`[workbox-build] lib/write-sw-using-default-template.js`, function () {\n  const MODULE_PATH =\n    '../../../../packages/workbox-build/build/lib/write-sw-using-default-template';\n\n  it(`should reject with an error when fs-extra.mkdirp() fails`, async function () {\n    const {writeSWUsingDefaultTemplate} = proxyquire(MODULE_PATH, {\n      'upath': {\n        dirname: () => 'ignored',\n      },\n      'fs-extra': {\n        mkdirp: () => Promise.reject(new Error()),\n      },\n    });\n\n    try {\n      await writeSWUsingDefaultTemplate({});\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(\n        errors['unable-to-make-sw-directory'],\n      );\n    }\n  });\n\n  it(`should reject with an error when fs-extra.writeFile() fails`, async function () {\n    const {writeSWUsingDefaultTemplate} = proxyquire(MODULE_PATH, {\n      'upath': {\n        dirname: () => 'ignored',\n      },\n      'fs-extra': {\n        mkdirp: () => Promise.resolve(),\n        writeFile: () => Promise.reject(new Error()),\n      },\n    });\n\n    try {\n      await writeSWUsingDefaultTemplate({manifestEntries: ['ignored']});\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(errors['sw-write-failure']);\n    }\n  });\n\n  it(`should reject with a specific error when fs-extra.writeFile() fails due to EISDIR`, async function () {\n    const eisdirError = new Error();\n    eisdirError.code = 'EISDIR';\n\n    const {writeSWUsingDefaultTemplate} = proxyquire(MODULE_PATH, {\n      'upath': {\n        dirname: () => 'ignored',\n      },\n      'fs-extra': {\n        mkdirp: () => Promise.resolve(),\n        readFile: () => Promise.resolve(),\n        writeFile: () => Promise.reject(eisdirError),\n      },\n      './bundle': {\n        bundle: async () => [\n          {\n            name: 'ignored',\n            contents: 'ignored',\n          },\n        ],\n      },\n    });\n\n    try {\n      await writeSWUsingDefaultTemplate({manifestEntries: ['ignored']});\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.have.string(\n        errors['sw-write-failure-directory'],\n      );\n    }\n  });\n\n  it(`should call fs-extra.writeFile() with the expected parameters when everything succeeds`, async function () {\n    const expectedPath = upath.join('expected', 'path');\n    const swDest = upath.join(expectedPath, 'sw.js');\n    const file1 = 'file1.js';\n    const file2 = 'file2.js';\n    const contents1 = 'contents1';\n    const contents2 = 'contents2';\n\n    const writeFileStub = sinon.stub().returns(Promise.resolve());\n    const {writeSWUsingDefaultTemplate} = proxyquire(MODULE_PATH, {\n      'fs-extra': {\n        mkdirp: (path) => {\n          expect(path).to.eql(expectedPath);\n        },\n        readFile: () => Promise.resolve(),\n        writeFile: writeFileStub,\n      },\n      './bundle': {\n        bundle: async () => [\n          {\n            name: upath.join(expectedPath, file1),\n            contents: contents1,\n          },\n          {\n            name: upath.join(expectedPath, file2),\n            contents: contents2,\n          },\n        ],\n      },\n      './populate-sw-template': {\n        populateSWTemplate: () => '',\n      },\n    });\n\n    await writeSWUsingDefaultTemplate({swDest});\n\n    // There should be exactly two calls to fs-extra.writeFile().\n    expect(writeFileStub.args).to.eql([\n      [upath.resolve(expectedPath, file1), contents1],\n      [upath.resolve(expectedPath, file2), contents2],\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/.hidden-directory/hello.html",
    "content": "<h1>.hidden-directory/hello</h1>\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/.hidden-directory/hello.js",
    "content": "console.log('.hidden-directory/hello.js');\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/index.html",
    "content": "<h1>Index</h1>\n\n<script>\n  // Helper function which returns a promise which resolves once the service worker registration\n  // is past the \"installing\" state.\n  function waitUntilInstalled(registration) {\n    return new Promise(function (resolve, reject) {\n      if (registration.installing) {\n        // If the current registration represents the \"installing\" service worker, then wait\n        // until the installation step (during which the resources are pre-fetched) completes\n        // to display the file list.\n        registration.installing.addEventListener('statechange', function (e) {\n          if (e.target.state === 'installed') {\n            resolve();\n          } else if (e.target.state === 'redundant') {\n            reject();\n          }\n        });\n      } else {\n        // Otherwise, if this isn't the \"installing\" service worker, then installation must have been\n        // completed during a previous visit to this page, and the resources are already pre-fetched.\n        // So we can show the list of files right away.\n        resolve();\n      }\n    });\n  }\n\n  const queryString = location.search.substring(1);\n  const keyValues = queryString.split('&');\n  const searchParams = {};\n  keyValues.forEach((keyValue) => {\n    const split = keyValue.split('=');\n    if (split[1].indexOf('/') === split[1].length - 1) {\n      split[1] = split[1].substring(0, split[1].length - 1);\n    }\n    searchParams[split[0]] = decodeURIComponent(split[1]);\n  });\n  navigator.serviceWorker\n    .register(searchParams.sw)\n    .then((registration) => {\n      return waitUntilInstalled(registration);\n    })\n    .then(() => {\n      return window.caches\n        .keys()\n        .then((keys) => {\n          return window.caches.open(keys[0]);\n        })\n        .then((cache) => {\n          return cache.keys();\n        });\n    })\n    .then((entries) => {\n      window.__testresult = {\n        entries: entries.map((entry) => {\n          return entry.url;\n        }),\n      };\n    })\n    .catch((err) => {\n      document.body.style.color = 'red';\n      document.body.innerHTML = `<h1>${err.message}</h1>\n\n    <pre>${err.stack}</pre>`;\n\n      console.error(err.message);\n      console.error(err.stack);\n\n      // This is just to help with local development so we know there is an error\n      setTimeout(() => {\n        window.__testresult = {\n          error: err,\n        };\n      }, 5 * 1000);\n    });\n</script>\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/page-1.html",
    "content": "<h1>Page 1</h1>\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/page-2.html",
    "content": "<h1>Page 2</h1>\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/styles/stylesheet-1.css",
    "content": "body {\n  background-color: red;\n}\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/styles/stylesheet-2.css",
    "content": "body {\n  background-color: blue;\n}\n"
  },
  {
    "path": "test/workbox-build/static/example-project-1/webpackEntry.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/bad-multiple-injection.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Multiple entries.\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/bad-no-injection.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// This file has no injection point....should throw in the tests.\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/basic-with-invalid-sourcemap.js.nolint",
    "content": "!function(){\"use strict\";try{self[\"workbox:core:5.0.0-beta.0\"]&&_()}catch(e){}const e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};class t extends Error{constructor(t,n){super(e(t,n)),this.name=t,this.details=n}}const n={googleAnalytics:\"googleAnalytics\",precache:\"precache-v2\",prefix:\"workbox\",runtime:\"runtime\",suffix:registration.scope},s=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(\"-\"),a={updateDetails:e=>{(e=>{for(const t of Object.keys(n))e(t)})(t=>{\"string\"==typeof e[t]&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||s(n.googleAnalytics),getPrecacheName:e=>e||s(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||s(n.runtime),getSuffix:()=>n.suffix},r=e=>{const t=new URL(String(e),location.href);return t.origin===location.origin?t.pathname:t.href},o=new Set;const i=(e,t)=>e.filter(e=>t in e),c=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:a=[]})=>{const r=await caches.open(e),o=await l({plugins:a,request:t,mode:\"read\"});let i=await r.match(o,s);for(const t of a)if(\"cachedResponseWillBeUsed\"in t){const a=t.cachedResponseWillBeUsed;i=await a.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:i,request:o})}return i},h=async({request:e,response:t,event:n,plugins:s=[]})=>{let a=t,r=!1;for(let t of s)if(\"cacheWillUpdate\"in t){r=!0;const s=t.cacheWillUpdate;if(!(a=await s.call(t,{request:e,response:a,event:n})))break}return r||(a=a&&200===a.status?a:void 0),a||null},l=async({request:e,mode:t,plugins:n=[]})=>{const s=i(n,\"cacheKeyWillBeUsed\");let a=e;for(const e of s)\"string\"==typeof(a=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:a}))&&(a=new Request(a));return a},u={put:async({cacheName:e,request:n,response:s,event:a,plugins:u=[],matchOptions:d})=>{const f=await l({plugins:u,request:n,mode:\"write\"});if(!s)throw new t(\"cache-put-with-no-response\",{url:r(f.url)});let p=await h({event:a,plugins:u,response:s,request:f});if(!p)return;const g=await caches.open(e),w=i(u,\"cacheDidUpdate\");let y=w.length>0?await c({cacheName:e,matchOptions:d,request:f}):null;try{await g.put(f,p)}catch(e){throw\"QuotaExceededError\"===e.name&&await async function(){for(const e of o)await e()}(),e}for(let t of w)await t.cacheDidUpdate.call(t,{cacheName:e,event:a,oldResponse:y,newResponse:p,request:f})},match:c},d={fetch:async({request:e,fetchOptions:n,event:s,plugins:a=[]})=>{if(\"string\"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const r=i(a,\"fetchDidFail\"),o=r.length>0?e.clone():null;try{for(let t of a)if(\"requestWillFetch\"in t){const n=t.requestWillFetch,a=e.clone();e=await n.call(t,{request:a,event:s})}}catch(e){throw new t(\"plugin-error-request-will-fetch\",{thrownError:e})}let c=e.clone();try{let t;t=\"navigate\"===e.mode?await fetch(e):await fetch(e,n);for(const e of a)\"fetchDidSucceed\"in e&&(t=await e.fetchDidSucceed.call(e,{event:s,request:c,response:t}));return t}catch(e){for(const t of r)await t.fetchDidFail.call(t,{error:e,event:s,originalRequest:o.clone(),request:c.clone()});throw e}}};try{self[\"workbox:strategies:5.0.0-beta.0\"]&&_()}catch(e){}let f;class p{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this._db=null,this._name=e,this._version=t,this._onupgradeneeded=n,this._onversionchange=s||(()=>this.close())}get db(){return this._db}async open(){if(!this._db)return this._db=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error(\"The open request was blocked and timed out\"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this._name,this._version);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):\"function\"==typeof this._onupgradeneeded&&this._onupgradeneeded(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this._onversionchange.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s=\"next\",count:a,includeKeys:r=!1}={}){return await this.transaction([e],\"readonly\",(o,i)=>{const c=o.objectStore(e),h=t?c.index(t):c,l=[],u=h.openCursor(n,s);u.onsuccess=()=>{const e=u.result;e?(l.push(r?e:e.value),a&&l.length>=a?i(l):e.continue()):i(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,a)=>{const r=this._db.transaction(e,t);r.onabort=()=>a(r.error),r.oncomplete=()=>s(),n(r,e=>s(e))})}async _call(e,t,n,...s){return await this.transaction([t],n,(n,a)=>{const r=n.objectStore(t),o=r[e].apply(r,s);o.onsuccess=()=>a(o.result)})}close(){this._db&&(this._db.close(),this._db=null)}}p.prototype.OPEN_TIMEOUT=2e3;const g={readonly:[\"get\",\"count\",\"getKey\",\"getAll\",\"getAllKeys\"],readwrite:[\"add\",\"put\",\"clear\",\"delete\"]};for(const[e,t]of Object.entries(g))for(const n of t)n in IDBObjectStore.prototype&&(p.prototype[n]=async function(t,...s){return await this._call(n,t,e,...s)});async function w(e,t){const n=e.clone(),s={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},a=t?t(s):s,r=function(){if(void 0===f){const e=new Response(\"\");if(\"body\"in e)try{new Response(e.body),f=!0}catch(e){f=!1}f=!1}return f}()?n.body:await n.blob();return new Response(r,a)}try{self[\"workbox:precaching:5.0.0-beta.0\"]&&_()}catch(e){}const y=[],m={get:()=>y,add(e){y.push(...e)}},v=\"__WB_REVISION__\";function R(e){if(!e)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(\"string\"==typeof e){const t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}const{revision:n,url:s}=e;if(!s)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(!n){const e=new URL(s,location.href);return{cacheKey:e.href,url:e.href}}const a=new URL(s,location.href),r=new URL(s,location.href);return a.searchParams.set(v,n),{cacheKey:a.href,url:r.href}}class b{constructor(e){this._cacheName=a.getPrecacheName(e),this._urlsToCacheKeys=new Map,this._cacheKeysToIntegrities=new Map}addToCacheList(e){for(const n of e){const{cacheKey:e,url:s}=R(n);if(this._urlsToCacheKeys.has(s)&&this._urlsToCacheKeys.get(s)!==e)throw new t(\"add-to-cache-list-conflicting-entries\",{firstEntry:this._urlsToCacheKeys.get(s),secondEntry:e});if(\"string\"!=typeof n&&n.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==n.integrity)throw new t(\"add-to-cache-list-conflicting-integrities\",{url:s});this._cacheKeysToIntegrities.set(e,n.integrity)}this._urlsToCacheKeys.set(s,e)}}async install({event:e,plugins:t}={}){const n=[],s=[],a=await caches.open(this._cacheName),r=await a.keys(),o=new Set(r.map(e=>e.url));for(const[e,t]of this._urlsToCacheKeys)o.has(t)?s.push(e):n.push({cacheKey:t,url:e});const i=n.map(({cacheKey:n,url:s})=>{const a=this._cacheKeysToIntegrities.get(n);return this._addURLToCache({cacheKey:n,event:e,plugins:t,url:s,integrity:a})});return await Promise.all(i),{updatedURLs:n.map(e=>e.url),notUpdatedURLs:s}}async activate(){const e=await caches.open(this._cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),s=[];for(const a of t)n.has(a.url)||(await e.delete(a),s.push(a.url));return{deletedURLs:s}}async _addURLToCache({cacheKey:e,url:n,event:s,plugins:a,integrity:r}){const o=new Request(n,{integrity:r,cache:\"reload\",credentials:\"same-origin\"});let i,c=await d.fetch({event:s,plugins:a,request:o});for(const e of a||[])\"cacheWillUpdate\"in e&&(i=e);if(!(i?i.cacheWillUpdate({event:s,request:o,response:c}):c.status<400))throw new t(\"bad-precaching-response\",{url:n,status:c.status});c.redirected&&(c=await w(c)),await u.put({event:s,plugins:a,response:c,request:e===n?o:new Request(e),cacheName:this._cacheName,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}createHandlerForURL(e){const n=this.getCacheKeyForURL(e);if(!n)throw new t(\"non-precached-url\",{url:e});return async()=>{try{const e=await caches.open(this._cacheName),t=await e.match(n);if(t)return t;throw new Error(`The cache ${this._cacheName} did not have an entry `+`for ${n}.`)}catch(e){return fetch(n)}}}}let q;const U=()=>(q||(q=new b),q);const L=(e,t)=>{const n=U().getURLsToCacheKeys();for(const s of function*(e,{ignoreURLParametersMatching:t,directoryIndex:n,cleanURLs:s,urlManipulation:a}={}){const r=new URL(e,location.href);r.hash=\"\",yield r.href;const o=function(e,t=[]){for(const n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}(r,t);if(yield o.href,n&&o.pathname.endsWith(\"/\")){const e=new URL(o.href);e.pathname+=n,yield e.href}if(s){const e=new URL(o.href);e.pathname+=\".html\",yield e.href}if(a){const e=a({url:r});for(const t of e)yield t.href}}(e,t)){const e=n.get(s);if(e)return e}};let x=!1;const T=e=>{x||((({ignoreURLParametersMatching:e=[/^utm_/],directoryIndex:t=\"index.html\",cleanURLs:n=!0,urlManipulation:s}={})=>{const r=a.getPrecacheName();addEventListener(\"fetch\",a=>{const o=L(a.request.url,{cleanURLs:n,directoryIndex:t,ignoreURLParametersMatching:e,urlManipulation:s});if(!o)return;let i=caches.open(r).then(e=>e.match(o)).then(e=>e||fetch(o));a.respondWith(i)})})(e),x=!0)},K=e=>{const t=U(),n=m.get();e.waitUntil(t.install({event:e,plugins:n}).catch(e=>{throw e}))},N=e=>{const t=U();e.waitUntil(t.activate())};try{self[\"workbox:range-requests:5.0.0-beta.0\"]&&_()}catch(e){}async function C(e,n){try{if(206===n.status)return n;const s=e.headers.get(\"range\");if(!s)throw new t(\"no-range-header\");const a=function(e){const n=e.trim().toLowerCase();if(!n.startsWith(\"bytes=\"))throw new t(\"unit-must-be-bytes\",{normalizedRangeHeader:n});if(n.includes(\",\"))throw new t(\"single-range-only\",{normalizedRangeHeader:n});const s=/(\\d*)-(\\d*)/.exec(n);if(!s||!s[1]&&!s[2])throw new t(\"invalid-range-values\",{normalizedRangeHeader:n});return{start:\"\"===s[1]?void 0:Number(s[1]),end:\"\"===s[2]?void 0:Number(s[2])}}(s),r=await n.blob(),o=function(e,n,s){const a=e.size;if(s&&s>a||n&&n<0)throw new t(\"range-not-satisfiable\",{size:a,end:s,start:n});let r,o;return void 0!==n&&void 0!==s?(r=n,o=s+1):void 0!==n&&void 0===s?(r=n,o=a):void 0!==s&&void 0===n&&(r=a-s,o=a),{start:r,end:o}}(r,a.start,a.end),i=r.slice(o.start,o.end),c=i.size,h=new Response(i,{status:206,statusText:\"Partial Content\",headers:n.headers});return h.headers.set(\"Content-Length\",String(c)),h.headers.set(\"Content-Range\",`bytes ${o.start}-${o.end-1}/`+r.size),h}catch(e){return new Response(\"\",{status:416,statusText:\"Range Not Satisfiable\"})}}try{self[\"workbox:routing:5.0.0-beta.0\"]&&_()}catch(e){}const E=\"GET\",P=e=>e&&\"object\"==typeof e?e:{handle:e};class O{constructor(e,t,n=E){this.handler=P(t),this.match=e,this.method=n}}class M extends O{constructor(e,t,n){super(({url:t})=>{const n=e.exec(t.href);if(n&&(t.origin===location.origin||0===n.index))return n.slice(1)},t,n)}}class S{constructor(){this._routes=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(\"fetch\",e=>{const{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)})}addCacheListener(){self.addEventListener(\"message\",async e=>{if(e.data&&\"CACHE_URLS\"===e.data.type){const{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(e=>{\"string\"==typeof e&&(e=[e]);const t=new Request(...e);return this.handleRequest({request:t})}));e.waitUntil(n),e.ports&&e.ports[0]&&(await n,e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){const n=new URL(e.url,location.href);if(!n.protocol.startsWith(\"http\"))return;let s,{params:a,route:r}=this.findMatchingRoute({url:n,request:e,event:t}),o=r&&r.handler;if(!o&&this._defaultHandler&&(o=this._defaultHandler),o){try{s=o.handle({url:n,request:e,event:t,params:a})}catch(e){s=Promise.reject(e)}return s&&this._catchHandler&&(s=s.catch(s=>this._catchHandler.handle({url:n,request:e,event:t}))),s}}findMatchingRoute({url:e,request:t,event:n}){const s=this._routes.get(t.method)||[];for(const a of s){let s,r=a.match({url:e,request:t,event:n});if(r)return s=r,Array.isArray(r)&&0===r.length?s=void 0:r.constructor===Object&&0===Object.keys(r).length?s=void 0:\"boolean\"==typeof r&&(s=void 0),{route:a,params:s}}return{}}setDefaultHandler(e){this._defaultHandler=P(e)}setCatchHandler(e){this._catchHandler=P(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(\"unregister-route-but-not-found-with-method\",{method:e.method});const n=this._routes.get(e.method).indexOf(e);if(!(n>-1))throw new t(\"unregister-route-route-not-registered\");this._routes.get(e.method).splice(n,1)}}let W;const k=()=>(W||((W=new S).addFetchListener(),W.addCacheListener()),W),A=(e,n,s)=>{let a;if(\"string\"==typeof e){const t=new URL(e,location.href);a=new O(({url:e})=>e.href===t.href,n,s)}else if(e instanceof RegExp)a=new M(e,n,s);else if(\"function\"==typeof e)a=new O(e,n,s);else{if(!(e instanceof O))throw new t(\"unsupported-route-type\",{moduleName:\"workbox-routing\",funcName:\"registerRoute\",paramName:\"capture\"});a=e}return k().registerRoute(a),a},H=\"media\";!async function(){const e=await caches.open(H),t=await e.keys();Promise.all(t.reverse().map(async t=>{return{contentType:(await e.match(t)).headers.get(\"content-type\"),src:t.url}}))}();const I={href:\"/audio\",mimePrefix:\"audio/\"},D={href:\"/images\",mimePrefix:\"image/\"},B={href:\"/videos\",mimePrefix:\"video/\"},F=BroadcastChannel?new BroadcastChannel(\"messages\"):null,j=new class{constructor(e={}){this._cacheName=a.getRuntimeName(e.cacheName),this._plugins=e.plugins||[],this._matchOptions=e.matchOptions}async handle({event:e,request:n}){const s=await u.match({cacheName:this._cacheName,request:n,event:e,matchOptions:this._matchOptions,plugins:this._plugins});if(!s)throw new t(\"no-response\",{url:n.url});return s}}({cacheName:H,plugins:[new class{constructor(){this.cachedResponseWillBeUsed=async({request:e,cachedResponse:t})=>t&&e.headers.has(\"range\")?await C(e,t):t}}]});var z;addEventListener(\"install\",()=>self.skipWaiting()),addEventListener(\"activate\",()=>self.clients.claim()),(e=>{U().addToCacheList(e),e.length>0&&(addEventListener(\"install\",K),addEventListener(\"activate\",N))})(self.__WB_MANIFEST),T(z),A(\"/_share-target\",async({event:e})=>{F&&F.postMessage(\"Saving media locally...\");const t=(await e.request.formData()).getAll(\"media\"),n=await caches.open(H);for(const e of t)e.name?await n.put(`/_media/${Date.now()}-${e.name}`,new Response(e,{headers:{\"content-length\":e.size,\"content-type\":e.type}})):F&&F.postMessage(\"Sorry! No name found on incoming media.\");const s=[I,D,B].find(e=>t[0].type.startsWith(e.mimePrefix)),a=s?`/#${s.href}`:\"/\";return Response.redirect(a,303)},\"POST\"),A(new RegExp(\"/_media/\"),j)}();\n//# sourceMappingURL=does-not-exist.js.map\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/basic-with-sourcemap-data-url.js.nolint",
    "content": "!function(){\"use strict\";try{self[\"workbox:core:5.0.0-beta.0\"]&&_()}catch(e){}const e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};class t extends Error{constructor(t,n){super(e(t,n)),this.name=t,this.details=n}}const n={googleAnalytics:\"googleAnalytics\",precache:\"precache-v2\",prefix:\"workbox\",runtime:\"runtime\",suffix:registration.scope},s=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(\"-\"),a={updateDetails:e=>{(e=>{for(const t of Object.keys(n))e(t)})(t=>{\"string\"==typeof e[t]&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||s(n.googleAnalytics),getPrecacheName:e=>e||s(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||s(n.runtime),getSuffix:()=>n.suffix},r=e=>{const t=new URL(String(e),location.href);return t.origin===location.origin?t.pathname:t.href},o=new Set;const i=(e,t)=>e.filter(e=>t in e),c=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:a=[]})=>{const r=await caches.open(e),o=await l({plugins:a,request:t,mode:\"read\"});let i=await r.match(o,s);for(const t of a)if(\"cachedResponseWillBeUsed\"in t){const a=t.cachedResponseWillBeUsed;i=await a.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:i,request:o})}return i},h=async({request:e,response:t,event:n,plugins:s=[]})=>{let a=t,r=!1;for(let t of s)if(\"cacheWillUpdate\"in t){r=!0;const s=t.cacheWillUpdate;if(!(a=await s.call(t,{request:e,response:a,event:n})))break}return r||(a=a&&200===a.status?a:void 0),a||null},l=async({request:e,mode:t,plugins:n=[]})=>{const s=i(n,\"cacheKeyWillBeUsed\");let a=e;for(const e of s)\"string\"==typeof(a=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:a}))&&(a=new Request(a));return a},u={put:async({cacheName:e,request:n,response:s,event:a,plugins:u=[],matchOptions:d})=>{const f=await l({plugins:u,request:n,mode:\"write\"});if(!s)throw new t(\"cache-put-with-no-response\",{url:r(f.url)});let p=await h({event:a,plugins:u,response:s,request:f});if(!p)return;const g=await caches.open(e),w=i(u,\"cacheDidUpdate\");let y=w.length>0?await c({cacheName:e,matchOptions:d,request:f}):null;try{await g.put(f,p)}catch(e){throw\"QuotaExceededError\"===e.name&&await async function(){for(const e of o)await e()}(),e}for(let t of w)await t.cacheDidUpdate.call(t,{cacheName:e,event:a,oldResponse:y,newResponse:p,request:f})},match:c},d={fetch:async({request:e,fetchOptions:n,event:s,plugins:a=[]})=>{if(\"string\"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const r=i(a,\"fetchDidFail\"),o=r.length>0?e.clone():null;try{for(let t of a)if(\"requestWillFetch\"in t){const n=t.requestWillFetch,a=e.clone();e=await n.call(t,{request:a,event:s})}}catch(e){throw new t(\"plugin-error-request-will-fetch\",{thrownError:e})}let c=e.clone();try{let t;t=\"navigate\"===e.mode?await fetch(e):await fetch(e,n);for(const e of a)\"fetchDidSucceed\"in e&&(t=await e.fetchDidSucceed.call(e,{event:s,request:c,response:t}));return t}catch(e){for(const t of r)await t.fetchDidFail.call(t,{error:e,event:s,originalRequest:o.clone(),request:c.clone()});throw e}}};try{self[\"workbox:strategies:5.0.0-beta.0\"]&&_()}catch(e){}let f;class p{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this._db=null,this._name=e,this._version=t,this._onupgradeneeded=n,this._onversionchange=s||(()=>this.close())}get db(){return this._db}async open(){if(!this._db)return this._db=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error(\"The open request was blocked and timed out\"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this._name,this._version);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):\"function\"==typeof this._onupgradeneeded&&this._onupgradeneeded(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this._onversionchange.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s=\"next\",count:a,includeKeys:r=!1}={}){return await this.transaction([e],\"readonly\",(o,i)=>{const c=o.objectStore(e),h=t?c.index(t):c,l=[],u=h.openCursor(n,s);u.onsuccess=()=>{const e=u.result;e?(l.push(r?e:e.value),a&&l.length>=a?i(l):e.continue()):i(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,a)=>{const r=this._db.transaction(e,t);r.onabort=()=>a(r.error),r.oncomplete=()=>s(),n(r,e=>s(e))})}async _call(e,t,n,...s){return await this.transaction([t],n,(n,a)=>{const r=n.objectStore(t),o=r[e].apply(r,s);o.onsuccess=()=>a(o.result)})}close(){this._db&&(this._db.close(),this._db=null)}}p.prototype.OPEN_TIMEOUT=2e3;const g={readonly:[\"get\",\"count\",\"getKey\",\"getAll\",\"getAllKeys\"],readwrite:[\"add\",\"put\",\"clear\",\"delete\"]};for(const[e,t]of Object.entries(g))for(const n of t)n in IDBObjectStore.prototype&&(p.prototype[n]=async function(t,...s){return await this._call(n,t,e,...s)});async function w(e,t){const n=e.clone(),s={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},a=t?t(s):s,r=function(){if(void 0===f){const e=new Response(\"\");if(\"body\"in e)try{new Response(e.body),f=!0}catch(e){f=!1}f=!1}return f}()?n.body:await n.blob();return new Response(r,a)}try{self[\"workbox:precaching:5.0.0-beta.0\"]&&_()}catch(e){}const y=[],m={get:()=>y,add(e){y.push(...e)}},v=\"__WB_REVISION__\";function R(e){if(!e)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(\"string\"==typeof e){const t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}const{revision:n,url:s}=e;if(!s)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(!n){const e=new URL(s,location.href);return{cacheKey:e.href,url:e.href}}const a=new URL(s,location.href),r=new URL(s,location.href);return a.searchParams.set(v,n),{cacheKey:a.href,url:r.href}}class b{constructor(e){this._cacheName=a.getPrecacheName(e),this._urlsToCacheKeys=new Map,this._cacheKeysToIntegrities=new Map}addToCacheList(e){for(const n of e){const{cacheKey:e,url:s}=R(n);if(this._urlsToCacheKeys.has(s)&&this._urlsToCacheKeys.get(s)!==e)throw new t(\"add-to-cache-list-conflicting-entries\",{firstEntry:this._urlsToCacheKeys.get(s),secondEntry:e});if(\"string\"!=typeof n&&n.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==n.integrity)throw new t(\"add-to-cache-list-conflicting-integrities\",{url:s});this._cacheKeysToIntegrities.set(e,n.integrity)}this._urlsToCacheKeys.set(s,e)}}async install({event:e,plugins:t}={}){const n=[],s=[],a=await caches.open(this._cacheName),r=await a.keys(),o=new Set(r.map(e=>e.url));for(const[e,t]of this._urlsToCacheKeys)o.has(t)?s.push(e):n.push({cacheKey:t,url:e});const i=n.map(({cacheKey:n,url:s})=>{const a=this._cacheKeysToIntegrities.get(n);return this._addURLToCache({cacheKey:n,event:e,plugins:t,url:s,integrity:a})});return await Promise.all(i),{updatedURLs:n.map(e=>e.url),notUpdatedURLs:s}}async activate(){const e=await caches.open(this._cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),s=[];for(const a of t)n.has(a.url)||(await e.delete(a),s.push(a.url));return{deletedURLs:s}}async _addURLToCache({cacheKey:e,url:n,event:s,plugins:a,integrity:r}){const o=new Request(n,{integrity:r,cache:\"reload\",credentials:\"same-origin\"});let i,c=await d.fetch({event:s,plugins:a,request:o});for(const e of a||[])\"cacheWillUpdate\"in e&&(i=e);if(!(i?i.cacheWillUpdate({event:s,request:o,response:c}):c.status<400))throw new t(\"bad-precaching-response\",{url:n,status:c.status});c.redirected&&(c=await w(c)),await u.put({event:s,plugins:a,response:c,request:e===n?o:new Request(e),cacheName:this._cacheName,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}createHandlerForURL(e){const n=this.getCacheKeyForURL(e);if(!n)throw new t(\"non-precached-url\",{url:e});return async()=>{try{const e=await caches.open(this._cacheName),t=await e.match(n);if(t)return t;throw new Error(`The cache ${this._cacheName} did not have an entry `+`for ${n}.`)}catch(e){return fetch(n)}}}}let q;const U=()=>(q||(q=new b),q);const L=(e,t)=>{const n=U().getURLsToCacheKeys();for(const s of function*(e,{ignoreURLParametersMatching:t,directoryIndex:n,cleanURLs:s,urlManipulation:a}={}){const r=new URL(e,location.href);r.hash=\"\",yield r.href;const o=function(e,t=[]){for(const n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}(r,t);if(yield o.href,n&&o.pathname.endsWith(\"/\")){const e=new URL(o.href);e.pathname+=n,yield e.href}if(s){const e=new URL(o.href);e.pathname+=\".html\",yield e.href}if(a){const e=a({url:r});for(const t of e)yield t.href}}(e,t)){const e=n.get(s);if(e)return e}};let x=!1;const T=e=>{x||((({ignoreURLParametersMatching:e=[/^utm_/],directoryIndex:t=\"index.html\",cleanURLs:n=!0,urlManipulation:s}={})=>{const r=a.getPrecacheName();addEventListener(\"fetch\",a=>{const o=L(a.request.url,{cleanURLs:n,directoryIndex:t,ignoreURLParametersMatching:e,urlManipulation:s});if(!o)return;let i=caches.open(r).then(e=>e.match(o)).then(e=>e||fetch(o));a.respondWith(i)})})(e),x=!0)},K=e=>{const t=U(),n=m.get();e.waitUntil(t.install({event:e,plugins:n}).catch(e=>{throw e}))},N=e=>{const t=U();e.waitUntil(t.activate())};try{self[\"workbox:range-requests:5.0.0-beta.0\"]&&_()}catch(e){}async function C(e,n){try{if(206===n.status)return n;const s=e.headers.get(\"range\");if(!s)throw new t(\"no-range-header\");const a=function(e){const n=e.trim().toLowerCase();if(!n.startsWith(\"bytes=\"))throw new t(\"unit-must-be-bytes\",{normalizedRangeHeader:n});if(n.includes(\",\"))throw new t(\"single-range-only\",{normalizedRangeHeader:n});const s=/(\\d*)-(\\d*)/.exec(n);if(!s||!s[1]&&!s[2])throw new t(\"invalid-range-values\",{normalizedRangeHeader:n});return{start:\"\"===s[1]?void 0:Number(s[1]),end:\"\"===s[2]?void 0:Number(s[2])}}(s),r=await n.blob(),o=function(e,n,s){const a=e.size;if(s&&s>a||n&&n<0)throw new t(\"range-not-satisfiable\",{size:a,end:s,start:n});let r,o;return void 0!==n&&void 0!==s?(r=n,o=s+1):void 0!==n&&void 0===s?(r=n,o=a):void 0!==s&&void 0===n&&(r=a-s,o=a),{start:r,end:o}}(r,a.start,a.end),i=r.slice(o.start,o.end),c=i.size,h=new Response(i,{status:206,statusText:\"Partial Content\",headers:n.headers});return h.headers.set(\"Content-Length\",String(c)),h.headers.set(\"Content-Range\",`bytes ${o.start}-${o.end-1}/`+r.size),h}catch(e){return new Response(\"\",{status:416,statusText:\"Range Not Satisfiable\"})}}try{self[\"workbox:routing:5.0.0-beta.0\"]&&_()}catch(e){}const E=\"GET\",P=e=>e&&\"object\"==typeof e?e:{handle:e};class O{constructor(e,t,n=E){this.handler=P(t),this.match=e,this.method=n}}class M extends O{constructor(e,t,n){super(({url:t})=>{const n=e.exec(t.href);if(n&&(t.origin===location.origin||0===n.index))return n.slice(1)},t,n)}}class S{constructor(){this._routes=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(\"fetch\",e=>{const{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)})}addCacheListener(){self.addEventListener(\"message\",async e=>{if(e.data&&\"CACHE_URLS\"===e.data.type){const{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(e=>{\"string\"==typeof e&&(e=[e]);const t=new Request(...e);return this.handleRequest({request:t})}));e.waitUntil(n),e.ports&&e.ports[0]&&(await n,e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){const n=new URL(e.url,location.href);if(!n.protocol.startsWith(\"http\"))return;let s,{params:a,route:r}=this.findMatchingRoute({url:n,request:e,event:t}),o=r&&r.handler;if(!o&&this._defaultHandler&&(o=this._defaultHandler),o){try{s=o.handle({url:n,request:e,event:t,params:a})}catch(e){s=Promise.reject(e)}return s&&this._catchHandler&&(s=s.catch(s=>this._catchHandler.handle({url:n,request:e,event:t}))),s}}findMatchingRoute({url:e,request:t,event:n}){const s=this._routes.get(t.method)||[];for(const a of s){let s,r=a.match({url:e,request:t,event:n});if(r)return s=r,Array.isArray(r)&&0===r.length?s=void 0:r.constructor===Object&&0===Object.keys(r).length?s=void 0:\"boolean\"==typeof r&&(s=void 0),{route:a,params:s}}return{}}setDefaultHandler(e){this._defaultHandler=P(e)}setCatchHandler(e){this._catchHandler=P(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(\"unregister-route-but-not-found-with-method\",{method:e.method});const n=this._routes.get(e.method).indexOf(e);if(!(n>-1))throw new t(\"unregister-route-route-not-registered\");this._routes.get(e.method).splice(n,1)}}let W;const k=()=>(W||((W=new S).addFetchListener(),W.addCacheListener()),W),A=(e,n,s)=>{let a;if(\"string\"==typeof e){const t=new URL(e,location.href);a=new O(({url:e})=>e.href===t.href,n,s)}else if(e instanceof RegExp)a=new M(e,n,s);else if(\"function\"==typeof e)a=new O(e,n,s);else{if(!(e instanceof O))throw new t(\"unsupported-route-type\",{moduleName:\"workbox-routing\",funcName:\"registerRoute\",paramName:\"capture\"});a=e}return k().registerRoute(a),a},H=\"media\";!async function(){const e=await caches.open(H),t=await e.keys();Promise.all(t.reverse().map(async t=>{return{contentType:(await e.match(t)).headers.get(\"content-type\"),src:t.url}}))}();const I={href:\"/audio\",mimePrefix:\"audio/\"},D={href:\"/images\",mimePrefix:\"image/\"},B={href:\"/videos\",mimePrefix:\"video/\"},F=BroadcastChannel?new BroadcastChannel(\"messages\"):null,j=new class{constructor(e={}){this._cacheName=a.getRuntimeName(e.cacheName),this._plugins=e.plugins||[],this._matchOptions=e.matchOptions}async handle({event:e,request:n}){const s=await u.match({cacheName:this._cacheName,request:n,event:e,matchOptions:this._matchOptions,plugins:this._plugins});if(!s)throw new t(\"no-response\",{url:n.url});return s}}({cacheName:H,plugins:[new class{constructor(){this.cachedResponseWillBeUsed=async({request:e,cachedResponse:t})=>t&&e.headers.has(\"range\")?await C(e,t):t}}]});var z;addEventListener(\"install\",()=>self.skipWaiting()),addEventListener(\"activate\",()=>self.clients.claim()),(e=>{U().addToCacheList(e),e.length>0&&(addEventListener(\"install\",K),addEventListener(\"activate\",N))})(self.__WB_MANIFEST),T(z),A(\"/_share-target\",async({event:e})=>{F&&F.postMessage(\"Saving media locally...\");const t=(await e.request.formData()).getAll(\"media\"),n=await caches.open(H);for(const e of t)e.name?await n.put(`/_media/${Date.now()}-${e.name}`,new Response(e,{headers:{\"content-length\":e.size,\"content-type\":e.type}})):F&&F.postMessage(\"Sorry! No name found on incoming media.\");const s=[I,D,B].find(e=>t[0].type.startsWith(e.mimePrefix)),a=s?`/#${s.href}`:\"/\";return Response.redirect(a,303)},\"POST\"),A(new RegExp(\"/_media/\"),j)}();\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vLi9ub2RlX21vZHVsZXMvd29ya2JveC1jb3JlL192ZXJzaW9uLmpzPzA3MTkiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVciLCJmaWxlIjoiMC5qcyIsInNvdXJjZXNDb250ZW50IjpbIlwidXNlIHN0cmljdFwiO1xuLy8gQHRzLWlnbm9yZVxudHJ5IHtcbiAgICBzZWxmWyd3b3JrYm94OmNvcmU6NS4xLjQnXSAmJiBfKCk7XG59XG5jYXRjaCAoZSkgeyB9XG4iXSwic291cmNlUm9vdCI6IiJ9/n/\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/basic-with-sourcemap.js.nolint",
    "content": "!function(){\"use strict\";try{self[\"workbox:core:5.0.0-beta.0\"]&&_()}catch(e){}const e=(e,...t)=>{let n=e;return t.length>0&&(n+=` :: ${JSON.stringify(t)}`),n};class t extends Error{constructor(t,n){super(e(t,n)),this.name=t,this.details=n}}const n={googleAnalytics:\"googleAnalytics\",precache:\"precache-v2\",prefix:\"workbox\",runtime:\"runtime\",suffix:registration.scope},s=e=>[n.prefix,e,n.suffix].filter(e=>e&&e.length>0).join(\"-\"),a={updateDetails:e=>{(e=>{for(const t of Object.keys(n))e(t)})(t=>{\"string\"==typeof e[t]&&(n[t]=e[t])})},getGoogleAnalyticsName:e=>e||s(n.googleAnalytics),getPrecacheName:e=>e||s(n.precache),getPrefix:()=>n.prefix,getRuntimeName:e=>e||s(n.runtime),getSuffix:()=>n.suffix},r=e=>{const t=new URL(String(e),location.href);return t.origin===location.origin?t.pathname:t.href},o=new Set;const i=(e,t)=>e.filter(e=>t in e),c=async({cacheName:e,request:t,event:n,matchOptions:s,plugins:a=[]})=>{const r=await caches.open(e),o=await l({plugins:a,request:t,mode:\"read\"});let i=await r.match(o,s);for(const t of a)if(\"cachedResponseWillBeUsed\"in t){const a=t.cachedResponseWillBeUsed;i=await a.call(t,{cacheName:e,event:n,matchOptions:s,cachedResponse:i,request:o})}return i},h=async({request:e,response:t,event:n,plugins:s=[]})=>{let a=t,r=!1;for(let t of s)if(\"cacheWillUpdate\"in t){r=!0;const s=t.cacheWillUpdate;if(!(a=await s.call(t,{request:e,response:a,event:n})))break}return r||(a=a&&200===a.status?a:void 0),a||null},l=async({request:e,mode:t,plugins:n=[]})=>{const s=i(n,\"cacheKeyWillBeUsed\");let a=e;for(const e of s)\"string\"==typeof(a=await e.cacheKeyWillBeUsed.call(e,{mode:t,request:a}))&&(a=new Request(a));return a},u={put:async({cacheName:e,request:n,response:s,event:a,plugins:u=[],matchOptions:d})=>{const f=await l({plugins:u,request:n,mode:\"write\"});if(!s)throw new t(\"cache-put-with-no-response\",{url:r(f.url)});let p=await h({event:a,plugins:u,response:s,request:f});if(!p)return;const g=await caches.open(e),w=i(u,\"cacheDidUpdate\");let y=w.length>0?await c({cacheName:e,matchOptions:d,request:f}):null;try{await g.put(f,p)}catch(e){throw\"QuotaExceededError\"===e.name&&await async function(){for(const e of o)await e()}(),e}for(let t of w)await t.cacheDidUpdate.call(t,{cacheName:e,event:a,oldResponse:y,newResponse:p,request:f})},match:c},d={fetch:async({request:e,fetchOptions:n,event:s,plugins:a=[]})=>{if(\"string\"==typeof e&&(e=new Request(e)),s instanceof FetchEvent&&s.preloadResponse){const e=await s.preloadResponse;if(e)return e}const r=i(a,\"fetchDidFail\"),o=r.length>0?e.clone():null;try{for(let t of a)if(\"requestWillFetch\"in t){const n=t.requestWillFetch,a=e.clone();e=await n.call(t,{request:a,event:s})}}catch(e){throw new t(\"plugin-error-request-will-fetch\",{thrownError:e})}let c=e.clone();try{let t;t=\"navigate\"===e.mode?await fetch(e):await fetch(e,n);for(const e of a)\"fetchDidSucceed\"in e&&(t=await e.fetchDidSucceed.call(e,{event:s,request:c,response:t}));return t}catch(e){for(const t of r)await t.fetchDidFail.call(t,{error:e,event:s,originalRequest:o.clone(),request:c.clone()});throw e}}};try{self[\"workbox:strategies:5.0.0-beta.0\"]&&_()}catch(e){}let f;class p{constructor(e,t,{onupgradeneeded:n,onversionchange:s}={}){this._db=null,this._name=e,this._version=t,this._onupgradeneeded=n,this._onversionchange=s||(()=>this.close())}get db(){return this._db}async open(){if(!this._db)return this._db=await new Promise((e,t)=>{let n=!1;setTimeout(()=>{n=!0,t(new Error(\"The open request was blocked and timed out\"))},this.OPEN_TIMEOUT);const s=indexedDB.open(this._name,this._version);s.onerror=()=>t(s.error),s.onupgradeneeded=e=>{n?(s.transaction.abort(),s.result.close()):\"function\"==typeof this._onupgradeneeded&&this._onupgradeneeded(e)},s.onsuccess=()=>{const t=s.result;n?t.close():(t.onversionchange=this._onversionchange.bind(this),e(t))}}),this}async getKey(e,t){return(await this.getAllKeys(e,t,1))[0]}async getAll(e,t,n){return await this.getAllMatching(e,{query:t,count:n})}async getAllKeys(e,t,n){return(await this.getAllMatching(e,{query:t,count:n,includeKeys:!0})).map(e=>e.key)}async getAllMatching(e,{index:t,query:n=null,direction:s=\"next\",count:a,includeKeys:r=!1}={}){return await this.transaction([e],\"readonly\",(o,i)=>{const c=o.objectStore(e),h=t?c.index(t):c,l=[],u=h.openCursor(n,s);u.onsuccess=()=>{const e=u.result;e?(l.push(r?e:e.value),a&&l.length>=a?i(l):e.continue()):i(l)}})}async transaction(e,t,n){return await this.open(),await new Promise((s,a)=>{const r=this._db.transaction(e,t);r.onabort=()=>a(r.error),r.oncomplete=()=>s(),n(r,e=>s(e))})}async _call(e,t,n,...s){return await this.transaction([t],n,(n,a)=>{const r=n.objectStore(t),o=r[e].apply(r,s);o.onsuccess=()=>a(o.result)})}close(){this._db&&(this._db.close(),this._db=null)}}p.prototype.OPEN_TIMEOUT=2e3;const g={readonly:[\"get\",\"count\",\"getKey\",\"getAll\",\"getAllKeys\"],readwrite:[\"add\",\"put\",\"clear\",\"delete\"]};for(const[e,t]of Object.entries(g))for(const n of t)n in IDBObjectStore.prototype&&(p.prototype[n]=async function(t,...s){return await this._call(n,t,e,...s)});async function w(e,t){const n=e.clone(),s={headers:new Headers(n.headers),status:n.status,statusText:n.statusText},a=t?t(s):s,r=function(){if(void 0===f){const e=new Response(\"\");if(\"body\"in e)try{new Response(e.body),f=!0}catch(e){f=!1}f=!1}return f}()?n.body:await n.blob();return new Response(r,a)}try{self[\"workbox:precaching:5.0.0-beta.0\"]&&_()}catch(e){}const y=[],m={get:()=>y,add(e){y.push(...e)}},v=\"__WB_REVISION__\";function R(e){if(!e)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(\"string\"==typeof e){const t=new URL(e,location.href);return{cacheKey:t.href,url:t.href}}const{revision:n,url:s}=e;if(!s)throw new t(\"add-to-cache-list-unexpected-type\",{entry:e});if(!n){const e=new URL(s,location.href);return{cacheKey:e.href,url:e.href}}const a=new URL(s,location.href),r=new URL(s,location.href);return a.searchParams.set(v,n),{cacheKey:a.href,url:r.href}}class b{constructor(e){this._cacheName=a.getPrecacheName(e),this._urlsToCacheKeys=new Map,this._cacheKeysToIntegrities=new Map}addToCacheList(e){for(const n of e){const{cacheKey:e,url:s}=R(n);if(this._urlsToCacheKeys.has(s)&&this._urlsToCacheKeys.get(s)!==e)throw new t(\"add-to-cache-list-conflicting-entries\",{firstEntry:this._urlsToCacheKeys.get(s),secondEntry:e});if(\"string\"!=typeof n&&n.integrity){if(this._cacheKeysToIntegrities.has(e)&&this._cacheKeysToIntegrities.get(e)!==n.integrity)throw new t(\"add-to-cache-list-conflicting-integrities\",{url:s});this._cacheKeysToIntegrities.set(e,n.integrity)}this._urlsToCacheKeys.set(s,e)}}async install({event:e,plugins:t}={}){const n=[],s=[],a=await caches.open(this._cacheName),r=await a.keys(),o=new Set(r.map(e=>e.url));for(const[e,t]of this._urlsToCacheKeys)o.has(t)?s.push(e):n.push({cacheKey:t,url:e});const i=n.map(({cacheKey:n,url:s})=>{const a=this._cacheKeysToIntegrities.get(n);return this._addURLToCache({cacheKey:n,event:e,plugins:t,url:s,integrity:a})});return await Promise.all(i),{updatedURLs:n.map(e=>e.url),notUpdatedURLs:s}}async activate(){const e=await caches.open(this._cacheName),t=await e.keys(),n=new Set(this._urlsToCacheKeys.values()),s=[];for(const a of t)n.has(a.url)||(await e.delete(a),s.push(a.url));return{deletedURLs:s}}async _addURLToCache({cacheKey:e,url:n,event:s,plugins:a,integrity:r}){const o=new Request(n,{integrity:r,cache:\"reload\",credentials:\"same-origin\"});let i,c=await d.fetch({event:s,plugins:a,request:o});for(const e of a||[])\"cacheWillUpdate\"in e&&(i=e);if(!(i?i.cacheWillUpdate({event:s,request:o,response:c}):c.status<400))throw new t(\"bad-precaching-response\",{url:n,status:c.status});c.redirected&&(c=await w(c)),await u.put({event:s,plugins:a,response:c,request:e===n?o:new Request(e),cacheName:this._cacheName,matchOptions:{ignoreSearch:!0}})}getURLsToCacheKeys(){return this._urlsToCacheKeys}getCachedURLs(){return[...this._urlsToCacheKeys.keys()]}getCacheKeyForURL(e){const t=new URL(e,location.href);return this._urlsToCacheKeys.get(t.href)}createHandlerForURL(e){const n=this.getCacheKeyForURL(e);if(!n)throw new t(\"non-precached-url\",{url:e});return async()=>{try{const e=await caches.open(this._cacheName),t=await e.match(n);if(t)return t;throw new Error(`The cache ${this._cacheName} did not have an entry `+`for ${n}.`)}catch(e){return fetch(n)}}}}let q;const U=()=>(q||(q=new b),q);const L=(e,t)=>{const n=U().getURLsToCacheKeys();for(const s of function*(e,{ignoreURLParametersMatching:t,directoryIndex:n,cleanURLs:s,urlManipulation:a}={}){const r=new URL(e,location.href);r.hash=\"\",yield r.href;const o=function(e,t=[]){for(const n of[...e.searchParams.keys()])t.some(e=>e.test(n))&&e.searchParams.delete(n);return e}(r,t);if(yield o.href,n&&o.pathname.endsWith(\"/\")){const e=new URL(o.href);e.pathname+=n,yield e.href}if(s){const e=new URL(o.href);e.pathname+=\".html\",yield e.href}if(a){const e=a({url:r});for(const t of e)yield t.href}}(e,t)){const e=n.get(s);if(e)return e}};let x=!1;const T=e=>{x||((({ignoreURLParametersMatching:e=[/^utm_/],directoryIndex:t=\"index.html\",cleanURLs:n=!0,urlManipulation:s}={})=>{const r=a.getPrecacheName();addEventListener(\"fetch\",a=>{const o=L(a.request.url,{cleanURLs:n,directoryIndex:t,ignoreURLParametersMatching:e,urlManipulation:s});if(!o)return;let i=caches.open(r).then(e=>e.match(o)).then(e=>e||fetch(o));a.respondWith(i)})})(e),x=!0)},K=e=>{const t=U(),n=m.get();e.waitUntil(t.install({event:e,plugins:n}).catch(e=>{throw e}))},N=e=>{const t=U();e.waitUntil(t.activate())};try{self[\"workbox:range-requests:5.0.0-beta.0\"]&&_()}catch(e){}async function C(e,n){try{if(206===n.status)return n;const s=e.headers.get(\"range\");if(!s)throw new t(\"no-range-header\");const a=function(e){const n=e.trim().toLowerCase();if(!n.startsWith(\"bytes=\"))throw new t(\"unit-must-be-bytes\",{normalizedRangeHeader:n});if(n.includes(\",\"))throw new t(\"single-range-only\",{normalizedRangeHeader:n});const s=/(\\d*)-(\\d*)/.exec(n);if(!s||!s[1]&&!s[2])throw new t(\"invalid-range-values\",{normalizedRangeHeader:n});return{start:\"\"===s[1]?void 0:Number(s[1]),end:\"\"===s[2]?void 0:Number(s[2])}}(s),r=await n.blob(),o=function(e,n,s){const a=e.size;if(s&&s>a||n&&n<0)throw new t(\"range-not-satisfiable\",{size:a,end:s,start:n});let r,o;return void 0!==n&&void 0!==s?(r=n,o=s+1):void 0!==n&&void 0===s?(r=n,o=a):void 0!==s&&void 0===n&&(r=a-s,o=a),{start:r,end:o}}(r,a.start,a.end),i=r.slice(o.start,o.end),c=i.size,h=new Response(i,{status:206,statusText:\"Partial Content\",headers:n.headers});return h.headers.set(\"Content-Length\",String(c)),h.headers.set(\"Content-Range\",`bytes ${o.start}-${o.end-1}/`+r.size),h}catch(e){return new Response(\"\",{status:416,statusText:\"Range Not Satisfiable\"})}}try{self[\"workbox:routing:5.0.0-beta.0\"]&&_()}catch(e){}const E=\"GET\",P=e=>e&&\"object\"==typeof e?e:{handle:e};class O{constructor(e,t,n=E){this.handler=P(t),this.match=e,this.method=n}}class M extends O{constructor(e,t,n){super(({url:t})=>{const n=e.exec(t.href);if(n&&(t.origin===location.origin||0===n.index))return n.slice(1)},t,n)}}class S{constructor(){this._routes=new Map}get routes(){return this._routes}addFetchListener(){self.addEventListener(\"fetch\",e=>{const{request:t}=e,n=this.handleRequest({request:t,event:e});n&&e.respondWith(n)})}addCacheListener(){self.addEventListener(\"message\",async e=>{if(e.data&&\"CACHE_URLS\"===e.data.type){const{payload:t}=e.data,n=Promise.all(t.urlsToCache.map(e=>{\"string\"==typeof e&&(e=[e]);const t=new Request(...e);return this.handleRequest({request:t})}));e.waitUntil(n),e.ports&&e.ports[0]&&(await n,e.ports[0].postMessage(!0))}})}handleRequest({request:e,event:t}){const n=new URL(e.url,location.href);if(!n.protocol.startsWith(\"http\"))return;let s,{params:a,route:r}=this.findMatchingRoute({url:n,request:e,event:t}),o=r&&r.handler;if(!o&&this._defaultHandler&&(o=this._defaultHandler),o){try{s=o.handle({url:n,request:e,event:t,params:a})}catch(e){s=Promise.reject(e)}return s&&this._catchHandler&&(s=s.catch(s=>this._catchHandler.handle({url:n,request:e,event:t}))),s}}findMatchingRoute({url:e,request:t,event:n}){const s=this._routes.get(t.method)||[];for(const a of s){let s,r=a.match({url:e,request:t,event:n});if(r)return s=r,Array.isArray(r)&&0===r.length?s=void 0:r.constructor===Object&&0===Object.keys(r).length?s=void 0:\"boolean\"==typeof r&&(s=void 0),{route:a,params:s}}return{}}setDefaultHandler(e){this._defaultHandler=P(e)}setCatchHandler(e){this._catchHandler=P(e)}registerRoute(e){this._routes.has(e.method)||this._routes.set(e.method,[]),this._routes.get(e.method).push(e)}unregisterRoute(e){if(!this._routes.has(e.method))throw new t(\"unregister-route-but-not-found-with-method\",{method:e.method});const n=this._routes.get(e.method).indexOf(e);if(!(n>-1))throw new t(\"unregister-route-route-not-registered\");this._routes.get(e.method).splice(n,1)}}let W;const k=()=>(W||((W=new S).addFetchListener(),W.addCacheListener()),W),A=(e,n,s)=>{let a;if(\"string\"==typeof e){const t=new URL(e,location.href);a=new O(({url:e})=>e.href===t.href,n,s)}else if(e instanceof RegExp)a=new M(e,n,s);else if(\"function\"==typeof e)a=new O(e,n,s);else{if(!(e instanceof O))throw new t(\"unsupported-route-type\",{moduleName:\"workbox-routing\",funcName:\"registerRoute\",paramName:\"capture\"});a=e}return k().registerRoute(a),a},H=\"media\";!async function(){const e=await caches.open(H),t=await e.keys();Promise.all(t.reverse().map(async t=>{return{contentType:(await e.match(t)).headers.get(\"content-type\"),src:t.url}}))}();const I={href:\"/audio\",mimePrefix:\"audio/\"},D={href:\"/images\",mimePrefix:\"image/\"},B={href:\"/videos\",mimePrefix:\"video/\"},F=BroadcastChannel?new BroadcastChannel(\"messages\"):null,j=new class{constructor(e={}){this._cacheName=a.getRuntimeName(e.cacheName),this._plugins=e.plugins||[],this._matchOptions=e.matchOptions}async handle({event:e,request:n}){const s=await u.match({cacheName:this._cacheName,request:n,event:e,matchOptions:this._matchOptions,plugins:this._plugins});if(!s)throw new t(\"no-response\",{url:n.url});return s}}({cacheName:H,plugins:[new class{constructor(){this.cachedResponseWillBeUsed=async({request:e,cachedResponse:t})=>t&&e.headers.has(\"range\")?await C(e,t):t}}]});var z;addEventListener(\"install\",()=>self.skipWaiting()),addEventListener(\"activate\",()=>self.clients.claim()),(e=>{U().addToCacheList(e),e.length>0&&(addEventListener(\"install\",K),addEventListener(\"activate\",N))})(self.__WB_MANIFEST),T(z),A(\"/_share-target\",async({event:e})=>{F&&F.postMessage(\"Saving media locally...\");const t=(await e.request.formData()).getAll(\"media\"),n=await caches.open(H);for(const e of t)e.name?await n.put(`/_media/${Date.now()}-${e.name}`,new Response(e,{headers:{\"content-length\":e.size,\"content-type\":e.type}})):F&&F.postMessage(\"Sorry! No name found on incoming media.\");const s=[I,D,B].find(e=>t[0].type.startsWith(e.mimePrefix)),a=s?`/#${s.href}`:\"/\";return Response.redirect(a,303)},\"POST\"),A(new RegExp(\"/_media/\"),j)}();\n//# sourceMappingURL=basic-with-sourcemap.js.map\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/basic.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/custom-injection-point.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nworkbox.precaching.precacheAndRoute(self.__custom_injection_point);\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/multiple-calls.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('./sample-import.js');\n\nconst precache = (input) => {\n  // no-op\n};\n\nprecache([]);\n\n// The automatic injection will happen here:\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\n\n// Then, call precache again:\nworkbox.precaching.precacheAndRoute([\n  '/extra-assets/example.1234.css',\n  '/extra-assets/example-2.1234.js',\n]);\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/precache-and-route-options.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST, {\n  cleanURLs: true,\n});\n"
  },
  {
    "path": "test/workbox-build/static/sw-injections/sample-import.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n"
  },
  {
    "path": "test/workbox-cacheable-response/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\nconst waitUntil = require('../../../infra/testing/wait-until');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-cacheable-response]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-cacheable-response/sw/');\n  });\n});\n\ndescribe(`[workbox-cacheable-response] Plugin`, function () {\n  const baseURL = `${server.getAddress()}/test/workbox-cacheable-response/static/cacheable-response-plugin/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should load a page and cache entries`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    const error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-1.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has some content.\n    await waitUntil(async () => {\n      const keys = await runInSW('cachesKeys');\n      return keys.length > 0;\n    });\n\n    const keys = await runInSW('cachesKeys');\n    expect(keys).to.deep.equal(['cacheable-response-cache']);\n\n    const cachedRequests = await runInSW('cacheURLs', keys[0]);\n    expect(cachedRequests).to.eql([`${baseURL}example-1.txt`]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cacheable-response/static/cacheable-response-plugin/example-1.txt",
    "content": "example-1.txt\n"
  },
  {
    "path": "test/workbox-cacheable-response/static/cacheable-response-plugin/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-cacheable-response');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.routing.registerRoute(\n  /.*.txt/,\n  new workbox.strategies.CacheFirst({\n    cacheName: 'cacheable-response-cache',\n    plugins: [\n      new workbox.cacheableResponse.CacheableResponsePlugin({\n        statuses: [0, 200],\n      }),\n    ],\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-cacheable-response/sw/test-CacheableResponse.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheableResponse} from 'workbox-cacheable-response/CacheableResponse.mjs';\n\ndescribe(`CacheableResponse`, function () {\n  const VALID_STATUS = 418;\n  const INVALID_STATUS = 500;\n  const VALID_STATUSES = [VALID_STATUS];\n  const VALID_HEADERS = {\n    'x-test': 'true',\n  };\n\n  describe(`constructor`, function () {\n    it(`should throw with no config`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      await expectError(\n        () => {\n          new CacheableResponse();\n        },\n        'statuses-or-headers-required',\n        (err) => {\n          expect(err.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-cacheable-response');\n          expect(err.details)\n            .to.have.property('className')\n            .that.eql('CacheableResponse');\n          expect(err.details)\n            .to.have.property('funcName')\n            .that.eql('constructor');\n        },\n      );\n    });\n\n    it(`should throw with bad config.statuses`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      await expectError(\n        () => {\n          new CacheableResponse({statuses: 'bad input'});\n        },\n        'not-an-array',\n        (err) => {\n          expect(err.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-cacheable-response');\n          expect(err.details)\n            .to.have.property('className')\n            .that.eql('CacheableResponse');\n          expect(err.details)\n            .to.have.property('funcName')\n            .that.eql('constructor');\n          expect(err.details)\n            .to.have.property('paramName')\n            .that.eql('config.statuses');\n        },\n      );\n    });\n\n    it(`should throw with bad config.headers`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      await expectError(\n        () => {\n          new CacheableResponse({headers: 'bad input'});\n        },\n        'incorrect-type',\n        (err) => {\n          expect(err.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-cacheable-response');\n          expect(err.details)\n            .to.have.property('className')\n            .that.eql('CacheableResponse');\n          expect(err.details)\n            .to.have.property('funcName')\n            .that.eql('constructor');\n          expect(err.details)\n            .to.have.property('paramName')\n            .that.eql('config.headers');\n        },\n      );\n    });\n\n    it(`should be able to construct with config.statuses`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n      });\n\n      expect(cacheableResponse._statuses).to.eql(VALID_STATUSES);\n    });\n\n    it(`should be able to construct with config.headers`, function () {\n      const cacheableResponse = new CacheableResponse({headers: VALID_HEADERS});\n\n      expect(cacheableResponse._headers).to.eql(VALID_HEADERS);\n    });\n\n    it(`should be able to construct with config.statuses and config.headers`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n        headers: VALID_HEADERS,\n      });\n\n      expect(cacheableResponse._statuses).to.eql(VALID_STATUSES);\n      expect(cacheableResponse._headers).to.eql(VALID_HEADERS);\n    });\n  });\n\n  describe(`isResponseCacheable`, function () {\n    it(`should throw when passed bad input`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n      });\n      await expectError(\n        () => {\n          cacheableResponse.isResponseCacheable(null);\n        },\n        'incorrect-class',\n        (err) => {\n          expect(err.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-cacheable-response');\n          expect(err.details)\n            .to.have.property('className')\n            .that.eql('CacheableResponse');\n          expect(err.details)\n            .to.have.property('funcName')\n            .that.eql('isResponseCacheable');\n          expect(err.details)\n            .to.have.property('paramName')\n            .that.eql('response');\n        },\n      );\n    });\n\n    it(`should return true when one of the statuses match the response`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n      });\n      const response = new Response('', {status: VALID_STATUS});\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.true;\n    });\n\n    it(`should return false when none of the statuses match the response`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n      });\n      const response = new Response('', {status: INVALID_STATUS});\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.false;\n    });\n\n    it(`should return true when one of the headers match the response`, function () {\n      const cacheableResponse = new CacheableResponse({headers: VALID_HEADERS});\n      const response = new Response('', {headers: VALID_HEADERS});\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.true;\n    });\n\n    it(`should return false when none of the headers match the response`, function () {\n      const cacheableResponse = new CacheableResponse({headers: VALID_HEADERS});\n      const response = new Response('', {\n        headers: {\n          key: 'value',\n        },\n      });\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.false;\n    });\n\n    it(`should return false when one of the statuses match the response, but none of the headers match`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n        headers: VALID_HEADERS,\n      });\n      const response = new Response('', {status: VALID_STATUS});\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.false;\n    });\n\n    it(`should return false when one of the headers match the response, but none of the statuses match`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n        headers: VALID_HEADERS,\n      });\n      const response = new Response('', {\n        headers: VALID_HEADERS,\n        status: INVALID_STATUS,\n      });\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.false;\n    });\n\n    it(`should return true when both the headers and statuses match the response`, function () {\n      const cacheableResponse = new CacheableResponse({\n        statuses: VALID_STATUSES,\n        headers: VALID_HEADERS,\n      });\n      const response = new Response('', {\n        headers: VALID_HEADERS,\n        status: VALID_STATUS,\n      });\n\n      expect(cacheableResponse.isResponseCacheable(response)).to.be.true;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-cacheable-response/sw/test-CacheableResponsePlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheableResponse} from 'workbox-cacheable-response/CacheableResponse.mjs';\nimport {CacheableResponsePlugin} from 'workbox-cacheable-response/CacheableResponsePlugin.mjs';\n\ndescribe(`CacheableResponsePlugin`, function () {\n  const STATUSES = [200];\n\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should construct a properly-configured internal CacheableResponse instance`, function () {\n      const cacheableResponsePlugin = new CacheableResponsePlugin({\n        statuses: STATUSES,\n      });\n      expect(cacheableResponsePlugin._cacheableResponse).to.be.instanceOf(\n        CacheableResponse,\n      );\n      expect(cacheableResponsePlugin._cacheableResponse._statuses).to.eql(\n        STATUSES,\n      );\n    });\n\n    it(`should expose cacheWillUpdate, which calls cacheableResponse.isResponseCacheable()`, function () {\n      const cacheableResponsePlugin = new CacheableResponsePlugin({\n        statuses: STATUSES,\n      });\n      const isResponseCacheableSpy = sandbox.spy(\n        cacheableResponsePlugin._cacheableResponse,\n        'isResponseCacheable',\n      );\n      const response = new Response('');\n      cacheableResponsePlugin.cacheWillUpdate({response});\n\n      expect(isResponseCacheableSpy.calledOnce).to.be.true;\n      expect(isResponseCacheableSpy.calledWithExactly(response)).to.be.true;\n    });\n  });\n\n  describe(`cacheWillUpdate`, function () {\n    it(`should return null for non-cachable response`, async function () {\n      const cacheableResponsePlugin = new CacheableResponsePlugin({\n        statuses: STATUSES,\n      });\n      sandbox\n        .stub(cacheableResponsePlugin._cacheableResponse, 'isResponseCacheable')\n        .callsFake(() => false);\n      expect(\n        await cacheableResponsePlugin.cacheWillUpdate({\n          response: new Response(),\n        }),\n      ).to.equal(null);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/app.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst chai = require('chai');\nconst chaiAsPromised = require('chai-as-promised');\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\nconst upath = require('upath');\n\nconst {\n  constants,\n} = require('../../../packages/workbox-cli/build/lib/constants');\nconst {errors} = require('../../../packages/workbox-cli/build/lib/errors');\nconst {\n  WorkboxConfigError,\n} = require('../../../packages/workbox-build/build/lib/validate-options');\n\nchai.use(chaiAsPromised);\nconst {expect} = chai;\n\ndescribe(`[workbox-cli] app.js`, function () {\n  const MODULE_PATH = '../../../packages/workbox-cli/build/app';\n  const PROXIED_CONFIG_FILE = upath.resolve(process.cwd(), '/will/be/proxied');\n  const PROXIED_DEST_DIR = upath.resolve(process.cwd(), 'build');\n  const PROXIED_ERROR = new Error('proxied error message');\n  const PROXIED_CONFIG = {\n    globDirectory: '.',\n    swDest: 'sw.js',\n  };\n  const INVALID_CONFIG_FILE = upath.resolve(\n    process.cwd(),\n    upath.join('does', 'not', 'exist'),\n  );\n  const UNKNOWN_COMMAND = 'unknown-command';\n  const WORKBOX_BUILD_COMMANDS = ['generateSW', 'injectManifest'];\n  const WORKBOX_BUILD_NO_WARNINGS_RETURN_VALUE = {\n    count: 1,\n    filePaths: ['ignored1', 'ignored2'],\n    size: 2,\n    warnings: [],\n  };\n  const WORKBOX_BUILD_WITH_WARNINGS_RETURN_VALUE = {\n    count: 1,\n    filePaths: ['ignored'],\n    size: 2,\n    warnings: ['warning'],\n  };\n\n  describe(`failures due to bad parameters`, function () {\n    const {app} = require(MODULE_PATH);\n\n    it(`should reject when both parameters are missing`, async function () {\n      await expect(app()).to.eventually.be.rejectedWith(\n        errors['missing-input'],\n      );\n    });\n\n    it(`should reject when the command is unknown and options is present`, async function () {\n      await expect(\n        app({input: [UNKNOWN_COMMAND, PROXIED_CONFIG_FILE]}),\n      ).to.eventually.be.rejectedWith(errors['unknown-command']);\n    });\n\n    it(`should reject when the command parameter is copyLibraries and options is missing`, async function () {\n      await expect(\n        app({input: ['copyLibraries']}),\n      ).to.eventually.be.rejectedWith(errors['missing-dest-dir-param']);\n    });\n\n    for (const command of WORKBOX_BUILD_COMMANDS) {\n      it(`should reject when the command parameter is ${command} and options does not exist`, async function () {\n        const loggerErrorStub = sinon.stub();\n        const {app} = proxyquire(MODULE_PATH, {\n          './lib/logger.js': {\n            logger: {error: loggerErrorStub},\n          },\n        });\n\n        try {\n          await app({input: [command, INVALID_CONFIG_FILE]});\n          throw new Error('Unexpected success.');\n        } catch (error) {\n          expect(loggerErrorStub.calledOnce).to.be.true;\n          expect(\n            loggerErrorStub.alwaysCalledWithExactly(\n              errors['invalid-common-js-module'],\n            ),\n          ).to.be.true;\n\n          expect(error.message).to.have.string(INVALID_CONFIG_FILE);\n        }\n      });\n    }\n  });\n\n  describe(`failures due to workbox-build runtime errors`, function () {\n    for (const command of WORKBOX_BUILD_COMMANDS) {\n      // TODO: Expand this list.\n      const badConfigs = [\n        {},\n        {swDest: null},\n        {globPatterns: null, swDest: '/path/to/sw.js'},\n        {foo: 'bar'},\n      ];\n\n      for (const config of badConfigs) {\n        it(`should reject with a WorkboxConfigError when workbox-build.${command}(${JSON.stringify(\n          config,\n        )}) is called`, async function () {\n          const {app} = proxyquire(MODULE_PATH, {\n            './lib/logger': {\n              logger: {\n                error: sinon.stub(),\n                log: sinon.stub(),\n              },\n            },\n            './lib/read-config': {\n              readConfig: (options) => {\n                expect(options).to.eql(PROXIED_CONFIG_FILE);\n                return config;\n              },\n            },\n          });\n\n          await expect(\n            app({input: [command, PROXIED_CONFIG_FILE]}),\n          ).to.eventually.be.rejectedWith(WorkboxConfigError);\n        });\n      }\n\n      it(`should reject with a generic runtime error when the workbox-build.${command}() rejects for any other reason`, async function () {\n        const {app} = proxyquire(MODULE_PATH, {\n          './lib/logger': {\n            logger: {\n              log: sinon.stub(),\n            },\n          },\n          './lib/read-config': {\n            readConfig: (options) => {\n              expect(options).to.eql(PROXIED_CONFIG_FILE);\n              return PROXIED_CONFIG;\n            },\n          },\n          'workbox-build': {\n            [command]: (config) => {\n              expect(config).to.eql(PROXIED_CONFIG);\n              throw PROXIED_ERROR;\n            },\n          },\n        });\n\n        await expect(\n          app({input: [command, PROXIED_CONFIG_FILE]}),\n        ).to.eventually.be.rejectedWith(PROXIED_ERROR);\n      });\n    }\n  });\n\n  describe(`successful calls`, function () {\n    for (const command of WORKBOX_BUILD_COMMANDS) {\n      it(`should call logger.log() upon successfully running workbox-build.${command}()`, async function () {\n        const loggerLogStub = sinon.stub();\n        const {app} = proxyquire(MODULE_PATH, {\n          './lib/read-config': {\n            readConfig: (options) => {\n              expect(options).to.eql(PROXIED_CONFIG_FILE);\n              return PROXIED_CONFIG;\n            },\n          },\n          './lib/logger': {\n            logger: {\n              log: loggerLogStub,\n            },\n          },\n          'workbox-build': {\n            [command]: () => {\n              return WORKBOX_BUILD_NO_WARNINGS_RETURN_VALUE;\n            },\n          },\n        });\n\n        await app({input: [command, PROXIED_CONFIG_FILE]});\n        expect(loggerLogStub.callCount).to.eql(3);\n      });\n\n      it(`should call logger.warn() to report warnings, and then logger.log() upon successfully running workbox-build.${command}()`, async function () {\n        const loggerLogStub = sinon.stub();\n        const loggerWarningStub = sinon.stub();\n        const {app} = proxyquire(MODULE_PATH, {\n          './lib/read-config': {\n            readConfig: (options) => {\n              expect(options).to.eql(PROXIED_CONFIG_FILE);\n              return PROXIED_CONFIG;\n            },\n          },\n          './lib/logger': {\n            logger: {\n              log: loggerLogStub,\n              warn: loggerWarningStub,\n            },\n          },\n          'workbox-build': {\n            [command]: () => {\n              return WORKBOX_BUILD_WITH_WARNINGS_RETURN_VALUE;\n            },\n          },\n        });\n\n        await app({input: [command, PROXIED_CONFIG_FILE]});\n        expect(loggerWarningStub.calledOnce).to.be.true;\n        expect(loggerLogStub.callCount).to.eql(3);\n      });\n\n      it(`should call logger.log() upon successfully running workbox-build.${command}() using the default config file location`, async function () {\n        const loggerLogStub = sinon.stub();\n        const {app} = proxyquire(MODULE_PATH, {\n          './lib/read-config': {\n            readConfig: (options) => {\n              const defaultConfigPath = upath.join(\n                process.cwd(),\n                constants.defaultConfigFile,\n              );\n              expect(options).to.eql(defaultConfigPath);\n              return PROXIED_CONFIG;\n            },\n          },\n          './lib/logger': {\n            logger: {\n              log: loggerLogStub,\n              error: loggerLogStub,\n            },\n          },\n          'workbox-build': {\n            [command]: () => {\n              return WORKBOX_BUILD_NO_WARNINGS_RETURN_VALUE;\n            },\n          },\n        });\n\n        await app({input: [command]});\n        expect(loggerLogStub.callCount).to.eql(3);\n      });\n    }\n\n    it(`should call logger.log() upon successfully running workbox-build.copyWorkboxLibraries()`, async function () {\n      const loggerLogStub = sinon.stub();\n      const {app} = proxyquire(MODULE_PATH, {\n        './lib/logger': {\n          logger: {\n            log: loggerLogStub,\n          },\n        },\n        'workbox-build': {\n          copyWorkboxLibraries: (destDir) => {\n            expect(destDir).to.eql(PROXIED_DEST_DIR);\n            return upath.join(destDir, 'workbox');\n          },\n        },\n      });\n\n      await app({input: ['copyLibraries', PROXIED_DEST_DIR]});\n      expect(loggerLogStub.callCount).to.eql(3);\n    });\n\n    it(`should call params.showHelp() when passed 'help'`, async function () {\n      const {app} = require(MODULE_PATH);\n\n      const params = {\n        input: ['help'],\n        showHelp: sinon.stub(),\n      };\n\n      await app(params);\n      expect(params.showHelp.calledOnce).to.be.true;\n    });\n\n    it(`should call params.showHelp() when not passed any command`, async function () {\n      const {app} = require(MODULE_PATH);\n\n      const params = {\n        input: [],\n        showHelp: sinon.stub(),\n      };\n\n      await app(params);\n      expect(params.showHelp.calledOnce).to.be.true;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/dependency-check.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst depcheck = require('depcheck');\nconst upath = require('upath');\n\ndescribe(`[workbox-cli] package.json`, function () {\n  it(`should have required dependencies`, function () {\n    return new Promise((resolve, reject) => {\n      depcheck(\n        upath.join(__dirname, '..', '..', '..', 'packages', 'workbox-cli'),\n        {\n          ignoreDirs: ['build'],\n          ignoreMatches: ['@babel/runtime'],\n        },\n        (unusedDeps) => {\n          if (unusedDeps.dependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.dependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (unusedDeps.devDependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.devDependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (Object.keys(unusedDeps.missing).length > 0) {\n            return reject(\n              new Error(\n                `Dependencies missing from package.json: ${JSON.stringify(\n                  unusedDeps.missing,\n                )}`,\n              ),\n            );\n          }\n\n          resolve();\n        },\n      );\n    });\n  });\n\n  it(`should have no devDependencies`, function () {\n    const pkg = require('../../../packages/workbox-cli/package.json');\n    if (pkg.devDependencies && Object.keys(pkg.devDependencies) > 0) {\n      throw new Error('There should not be devDependencies in this module.');\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/cleanup-stack-trace.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\n\nconst {\n  cleanupStackTrace,\n} = require('../../../../packages/workbox-cli/build/lib/cleanup-stack-trace');\n\ndescribe(`[workbox-cli] lib/cleanup-stack-trace.js`, function () {\n  const CURRENT_MODULE_NAME = upath.basename(__filename);\n\n  it(`should return an empty string when passed an error with no stack`, function () {\n    const error = new Error();\n    error.stack = '';\n    expect(cleanupStackTrace(error, CURRENT_MODULE_NAME)).to.eql('');\n  });\n\n  it(`should exclude every stack frame prior to the first frame of ${CURRENT_MODULE_NAME}`, function () {\n    const error = new Error();\n    const cleanStackTrace = cleanupStackTrace(error, CURRENT_MODULE_NAME);\n    const frameCount = cleanStackTrace.split(`\\n`).length;\n    expect(frameCount).to.eql(1);\n  });\n\n  it(`should exclude every stack frame prior to the first frame of ${CURRENT_MODULE_NAME}, when the error is nested`, function () {\n    let error;\n    (() => {\n      error = new Error();\n    })();\n    const cleanStackTrace = cleanupStackTrace(error, CURRENT_MODULE_NAME);\n    const frameCount = cleanStackTrace.split(`\\n`).length;\n    expect(frameCount).to.eql(2);\n  });\n\n  it(`should exclude error.message, and just return the stack frames`, function () {\n    const error = new Error(`line 1\\nline 2\\nline 3`);\n    const cleanStackTrace = cleanupStackTrace(error, CURRENT_MODULE_NAME);\n    const frameCount = cleanStackTrace.split(`\\n`).length;\n    expect(frameCount).to.eql(1);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/help-text.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\ndescribe(`[workbox-cli] lib/help-text.js`, function () {\n  it(`should be a string`, function () {\n    const {\n      helpText,\n    } = require('../../../../packages/workbox-cli/build/lib/help-text');\n    expect(helpText).to.be.a('string');\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/logger.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst sinon = require('sinon');\n\nconst {logger} = require('../../../../packages/workbox-cli/build/lib/logger');\n\ndescribe(`[workbox-cli] lib/logger.js`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  it(`should call console.log() when logger.debug() is used`, function () {\n    const stub = sandbox.stub(console, 'log');\n    logger.debug('Test');\n    sandbox.restore();\n    expect(stub.calledOnce).to.be.true;\n  });\n\n  it(`should call console.log() when logger.log() is used`, function () {\n    const stub = sandbox.stub(console, 'log');\n    logger.log('Test');\n    sandbox.restore();\n    expect(stub.calledOnce).to.be.true;\n  });\n\n  it(`should call console.warn() when logger.warn() is used`, function () {\n    const stub = sandbox.stub(console, 'warn');\n    logger.warn('Test');\n    sandbox.restore();\n    expect(stub.calledOnce).to.be.true;\n  });\n\n  it(`should call console.error() when logger.error() is used`, function () {\n    const stub = sandbox.stub(console, 'error');\n    logger.error('Test');\n    sandbox.restore();\n    expect(stub.calledOnce).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-config-location.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../../packages/workbox-cli/build/lib/errors');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-config-location';\n// This is the hardcoded name of the question that's passed to inquirer.\n// It's used as the key to read the response from the answer.\nconst QUESTION_NAME = 'configLocation';\n\ndescribe(`[workbox-cli] lib/questions/ask-config-location.js`, function () {\n  it(`should reject with a 'invalid-config-location' error when the answer is an empty string`, async function () {\n    const {askConfigLocation} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: ''}),\n      },\n    });\n\n    try {\n      await askConfigLocation();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['invalid-config-location']);\n    }\n  });\n\n  it(`should resolve with the valid answer to the question`, async function () {\n    const expectedAnswer = 'expected answer';\n    const {askConfigLocation} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: expectedAnswer}),\n      },\n    });\n\n    const answer = await askConfigLocation();\n    expect(answer).to.eql(expectedAnswer);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-extensions-to-cache.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../../packages/workbox-cli/build/lib/errors');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-extensions-to-cache';\n// This is the hardcoded name of the question that's passed to inquirer.\n// It's used as the key to read the response from the answer.\nconst QUESTION_NAME = 'globPatterns';\nconst GLOB_DIRECTORY = '/path/to/fake';\nconst SINGLE_EXTENSION = 'js';\nconst MULTIPLE_EXTENSIONS = ['html', 'js'];\n\ndescribe(`[workbox-cli] lib/questions/ask-extensions-to-cache.js`, function () {\n  it(`should reject with a 'no-file-extensions-found' error when the globDirectory doesn't contain any matching files`, async function () {\n    const {askExtensionsToCache} = proxyquire(MODULE_PATH, {\n      glob: {\n        glob: async () => [],\n      },\n      ora: () => {\n        return {\n          start: () => {\n            return {stop: () => {}};\n          },\n        };\n      },\n    });\n\n    try {\n      await askExtensionsToCache(GLOB_DIRECTORY);\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['no-file-extensions-found']);\n    }\n  });\n\n  it(`should reject with a 'no-file-extensions-selected' error when the answer is an empty array`, async function () {\n    const {askExtensionsToCache} = proxyquire(MODULE_PATH, {\n      glob: {\n        glob: async () => [`file.${SINGLE_EXTENSION}`],\n      },\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: []}),\n      },\n      ora: () => {\n        return {\n          start: () => {\n            return {stop: () => {}};\n          },\n        };\n      },\n    });\n\n    try {\n      await askExtensionsToCache(GLOB_DIRECTORY);\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['no-file-extensions-selected']);\n    }\n  });\n\n  it(`should resolve with the expected value when the answer is a single extension`, async function () {\n    const {askExtensionsToCache} = proxyquire(MODULE_PATH, {\n      glob: {\n        glob: () => [`file.${SINGLE_EXTENSION}`],\n      },\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: [SINGLE_EXTENSION]}),\n      },\n      ora: () => {\n        return {\n          start: () => {\n            return {stop: () => {}};\n          },\n        };\n      },\n    });\n\n    const answer = await askExtensionsToCache(GLOB_DIRECTORY);\n    expect(answer).to.eql([`**/*.${SINGLE_EXTENSION}`]);\n  });\n\n  it(`should resolve with the expected value when the answer is multiple extensions`, async function () {\n    const {askExtensionsToCache} = proxyquire(MODULE_PATH, {\n      glob: {\n        glob: () => MULTIPLE_EXTENSIONS.map((extension) => `file.${extension}`),\n      },\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: MULTIPLE_EXTENSIONS}),\n      },\n      ora: () => {\n        return {\n          start: () => {\n            return {stop: () => {}};\n          },\n        };\n      },\n    });\n\n    const answer = await askExtensionsToCache(GLOB_DIRECTORY);\n    expect(answer).to.eql([`**/*.{${MULTIPLE_EXTENSIONS.join(',')}}`]);\n  });\n\n  it(`should ignore the expected directories and extensions`, async function () {\n    const {askExtensionsToCache} = proxyquire(MODULE_PATH, {\n      glob: {\n        glob: (pattern, config) => {\n          expect(config.ignore).to.eql(['**/node_modules/**', '**/*.map']);\n\n          return MULTIPLE_EXTENSIONS.map((extension) => `file.${extension}`);\n        },\n      },\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: MULTIPLE_EXTENSIONS}),\n      },\n      ora: () => {\n        return {\n          start: () => {\n            return {stop: () => {}};\n          },\n        };\n      },\n    });\n\n    await askExtensionsToCache(GLOB_DIRECTORY);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-questions.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-questions';\n\ndescribe(`[workbox-cli] lib/questions/ask-questions.js`, function () {\n  it(`should ask all the expected questions in the correct order, and return the expected result in generateSW mode`, async function () {\n    // Using a stub that returns an increasing value for each call makes it\n    // easy to verify that all the stubs are called in the expected order,\n    // and to verify that the stub's responses are used to create the overall\n    // response in the expected fashion.\n    let count = 0;\n    const stub = sinon.stub();\n\n    const {askQuestions} = proxyquire(MODULE_PATH, {\n      './ask-root-of-web-app': {\n        askRootOfWebApp: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-extensions-to-cache': {\n        askExtensionsToCache: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-sw-dest': {\n        askSWDest: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-config-location': {\n        askConfigLocation: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-start_url-query-params': {\n        askQueryParametersInStartUrl: stub.callsFake(() =>\n          Promise.resolve(count++),\n        ),\n      },\n    });\n\n    const answer = await askQuestions();\n    expect(answer).to.eql({\n      config: {\n        globDirectory: 0,\n        globPatterns: 1,\n        swDest: 2,\n        ignoreURLParametersMatching: 4,\n      },\n      configLocation: 3,\n    });\n    expect(stub.callCount).to.eql(5);\n  });\n\n  it(`should ask all the expected questions in the correct order, and return the expected result in injectManifest mode`, async function () {\n    // Using a stub that returns an increasing value for each call makes it\n    // easy to verify that all the stubs are called in the expected order,\n    // and to verify that the stub's responses are used to create the overall\n    // response in the expected fashion.\n    let count = 0;\n    const stub = sinon.stub();\n\n    const {askQuestions} = proxyquire(MODULE_PATH, {\n      './ask-root-of-web-app': {\n        askRootOfWebApp: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-extensions-to-cache': {\n        askExtensionsToCache: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-sw-src': {\n        askSWSrc: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-sw-dest': {\n        askSWDest: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-config-location': {\n        askConfigLocation: stub.callsFake(() => Promise.resolve(count++)),\n      },\n      './ask-start_url-query-params': {\n        askQueryParametersInStartUrl: stub.callsFake(() =>\n          Promise.resolve(count++),\n        ),\n      },\n    });\n\n    const answer = await askQuestions({injectManifest: true});\n    expect(answer).to.eql({\n      config: {\n        globDirectory: 0,\n        globPatterns: 1,\n        swSrc: 2,\n        swDest: 3,\n      },\n      configLocation: 4,\n    });\n    expect(stub.callCount).to.eql(5);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-root-of-web-app.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../../packages/workbox-cli/build/lib/errors');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-root-of-web-app';\n// This is the hardcoded name of the question that's passed to inquirer.\n// It's used as the key to read the response from the answer.\nconst questionRootDirectory = 'globDirectory';\nconst questionManualInput = 'manualDirectoryInput';\nconst DIRECTORY = '/path/to/directory';\nconst CHILD_DIRECTORY = '/path/to/directory/child';\nconst CHILD_DIRECTORY_WHITE_SPACE = '/path/to/directory/   child';\nconst CHILD_DIRECTORY_BLANK = '  ';\n\ndescribe(`[workbox-cli] lib/questions/ask-root-of-web-app.js`, function () {\n  it(`should reject with a 'glob-directory-invalid' error when the answer isn't a valid directory`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.reject(new Error(null));\n      },\n      'inquirer': {\n        prompt: () => Promise.resolve({[questionRootDirectory]: DIRECTORY}),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              // This will return false when our injected DIRECTORY value is\n              // passed in.\n              return path !== DIRECTORY;\n            },\n          };\n        },\n      },\n    });\n\n    try {\n      await askRootOfWebApp();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['glob-directory-invalid']);\n    }\n  });\n\n  it(`should reject with a 'glob-directory-invalid' error when the manual input is provided (directory does not exist)`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.reject(new Error(null));\n      },\n      'inquirer': {\n        prompt: () =>\n          Promise.resolve({\n            [questionRootDirectory]: DIRECTORY,\n            [questionManualInput]: CHILD_DIRECTORY,\n          }),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              return path !== CHILD_DIRECTORY;\n            },\n          };\n        },\n      },\n    });\n\n    try {\n      await askRootOfWebApp();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['glob-directory-invalid']);\n    }\n  });\n\n  it(`should resolve with a valid answer to the question when no child directories are present (default: use current directory)`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.resolve([]);\n      },\n      'inquirer': {\n        prompt: () => Promise.resolve({[questionRootDirectory]: DIRECTORY}),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              // This will return true when our injected DIRECTORY value is\n              // passed in.\n              return path === DIRECTORY;\n            },\n          };\n        },\n      },\n    });\n\n    const answer = await askRootOfWebApp();\n    expect(answer).to.eql(DIRECTORY);\n  });\n\n  it(`should resolve with a valid answer to the question when manual input is provided (directory exists)`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.resolve([]);\n      },\n      'inquirer': {\n        prompt: () =>\n          Promise.resolve({\n            [questionRootDirectory]: DIRECTORY,\n            [questionManualInput]: CHILD_DIRECTORY,\n          }),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              return path === CHILD_DIRECTORY;\n            },\n          };\n        },\n      },\n    });\n\n    const answer = await askRootOfWebApp();\n    expect(answer).to.eql(CHILD_DIRECTORY);\n  });\n\n  it(`should resolve with a valid answer to the question when manual input is provided (directory exists and name contains white space)`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.resolve([]);\n      },\n      'inquirer': {\n        prompt: () =>\n          Promise.resolve({\n            [questionRootDirectory]: DIRECTORY,\n            [questionManualInput]: CHILD_DIRECTORY_WHITE_SPACE,\n          }),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              return path === CHILD_DIRECTORY_WHITE_SPACE;\n            },\n          };\n        },\n      },\n    });\n\n    const answer = await askRootOfWebApp();\n    expect(answer).to.eql(CHILD_DIRECTORY_WHITE_SPACE);\n  });\n\n  it(`should resolve with a valid answer to the question when manual input is provided (directory exists and name is composed of only white space)`, async function () {\n    const {askRootOfWebApp} = proxyquire(MODULE_PATH, {\n      'glob': () => {\n        return Promise.resolve([]);\n      },\n      'inquirer': {\n        prompt: () =>\n          Promise.resolve({\n            [questionRootDirectory]: DIRECTORY,\n            [questionManualInput]: CHILD_DIRECTORY_BLANK,\n          }),\n      },\n      'fs-extra': {\n        stat: (path) => {\n          return {\n            isDirectory: () => {\n              return path === CHILD_DIRECTORY_BLANK;\n            },\n          };\n        },\n      },\n    });\n\n    const answer = await askRootOfWebApp();\n    expect(answer).to.eql(CHILD_DIRECTORY_BLANK);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-start_url-query-params.js",
    "content": "/*\n  Copyright 2021 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../../packages/workbox-cli/build/lib/errors');\nconst {\n  constants,\n} = require('../../../../../packages/workbox-cli/build/lib/constants');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-start_url-query-params';\n\n// These are the hardcoded names of the question that are passed to inquirer.\n// They are used as the keys to read the response from the users answers.\nconst question_ignoreURLParametersMatching = 'ignoreURLParametersMatching';\nconst question_shouldAskForIgnoreURLParametersMatching =\n  'shouldAskForIgnoreURLParametersMatching';\n\nconst DEFAULT_IGNORED_URL_PARAMETERS = constants.ignoreURLParametersMatching;\n\n//  Helper method for creating RegExp from dynamic values.\nconst toRegex = (searchParam) => new RegExp(`^${searchParam}`);\n\ndescribe(`[workbox-cli] lib/questions/ask-start_url-query-params.js`, function () {\n  it(`should resolve with a default search parameters if answered no to the question`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = false;\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n          }),\n      },\n    });\n\n    const answer = await askQueryParametersInStartUrl();\n    expect(answer).to.eql(DEFAULT_IGNORED_URL_PARAMETERS);\n  });\n\n  it(`should throw 'no-search-parameters-supplied' if answered yes and no url search parameters are passed`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = true;\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n          }),\n      },\n    });\n\n    try {\n      await askQueryParametersInStartUrl();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['no-search-parameters-supplied']);\n    }\n  });\n\n  it(`should throw 'invalid-search-parameters-supplied' if url search parameter passed is prefixed with '?' or '/'`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = true;\n    const ignoreURLParametersMatching = '?source';\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n            [question_ignoreURLParametersMatching]: ignoreURLParametersMatching,\n          }),\n      },\n    });\n\n    try {\n      await askQueryParametersInStartUrl();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(\n        errors['invalid-search-parameters-supplied'],\n      );\n    }\n  });\n\n  it(`should throw 'invalid-search-parameters-supplied' if one of the provided url search parameters is prefixed with '?' or '/'`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = true;\n    const ignoreURLParametersMatching = 'search,version,?language';\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n            [question_ignoreURLParametersMatching]: ignoreURLParametersMatching,\n          }),\n      },\n    });\n\n    try {\n      await askQueryParametersInStartUrl();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(\n        errors['invalid-search-parameters-supplied'],\n      );\n    }\n  });\n\n  it(`should resolve with a list of search parameters when a valid url search parameter is passed`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = true;\n    const ignoreURLParametersMatching = 'search';\n    const expectedAnswer = DEFAULT_IGNORED_URL_PARAMETERS.concat(\n      toRegex(ignoreURLParametersMatching),\n    );\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n            [question_ignoreURLParametersMatching]: ignoreURLParametersMatching,\n          }),\n      },\n    });\n\n    const answer = await askQueryParametersInStartUrl();\n    expect(answer).to.eql(expectedAnswer);\n  });\n\n  it(`should resolve with a list of search parameters when a valid list of url search parameters is passed`, async function () {\n    const shouldAskForIgnoreURLParametersMatching = true;\n    const ignoreURLParametersMatching = 'search,version,language';\n    const expectedAnswer = DEFAULT_IGNORED_URL_PARAMETERS.concat(\n      ignoreURLParametersMatching.split(',').map(toRegex),\n    );\n    const {askQueryParametersInStartUrl} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () =>\n          Promise.resolve({\n            [question_shouldAskForIgnoreURLParametersMatching]:\n              shouldAskForIgnoreURLParametersMatching,\n            [question_ignoreURLParametersMatching]: ignoreURLParametersMatching,\n          }),\n      },\n    });\n\n    const answer = await askQueryParametersInStartUrl();\n    expect(answer).to.eql(expectedAnswer);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-sw-dest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst {\n  errors,\n} = require('../../../../../packages/workbox-cli/build/lib/errors');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-sw-dest';\n// This is the hardcoded name of the question that's passed to inquirer.\n// It's used as the key to read the response from the answer.\nconst QUESTION_NAME = 'swDest';\n\ndescribe(`[workbox-cli] lib/questions/ask-sw-dest.js`, function () {\n  it(`should reject with a 'invalid-sw-dest' error when the answer is empty`, async function () {\n    const {askSWDest} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: ''}),\n      },\n    });\n\n    try {\n      await askSWDest();\n      throw new Error('Unexpected success.');\n    } catch (error) {\n      expect(error.message).to.eql(errors['invalid-sw-dest']);\n    }\n  });\n\n  it(`should reject with a valid answer to the question`, async function () {\n    const expectedAnswer = 'expected answer';\n    const {askSWDest} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: expectedAnswer}),\n      },\n    });\n\n    const answer = await askSWDest();\n    expect(answer).to.eql(expectedAnswer);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/questions/ask-sw-src.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst proxyquire = require('proxyquire');\n\nconst MODULE_PATH =\n  '../../../../../packages/workbox-cli/build/lib/questions/ask-sw-src';\n// This is the hardcoded name of the question that's passed to inquirer.\n// It's used as the key to read the response from the answer.\nconst QUESTION_NAME = 'swSrc';\n\ndescribe(`[workbox-cli] lib/questions/ask-sw-src.js`, function () {\n  it(`should resolve with a valid answer to the question`, async function () {\n    const expectedAnswer = 'expected answer';\n    const {askSWSrc} = proxyquire(MODULE_PATH, {\n      inquirer: {\n        prompt: () => Promise.resolve({[QUESTION_NAME]: expectedAnswer}),\n      },\n    });\n\n    const answer = await askSWSrc();\n    expect(answer).to.eql(expectedAnswer);\n  });\n});\n"
  },
  {
    "path": "test/workbox-cli/node/lib/run-wizard.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst upath = require('upath');\nconst proxyquire = require('proxyquire');\nconst sinon = require('sinon');\n\nconst MODULE_PATH = '../../../../packages/workbox-cli/build/lib/run-wizard';\n\ndescribe(`[workbox-cli] lib/run-wizard.js`, function () {\n  it(`should write the configuration to the expected location based on the answers provided`, async function () {\n    const configLocation = upath.join('path', 'to', 'config.js');\n    const config = {dummy: 123, regExp: [/1/, /2/]};\n    const fseWriteFileStub = sinon.stub().resolves();\n    const loggerStub = sinon.stub();\n\n    const {runWizard} = proxyquire(MODULE_PATH, {\n      './logger': {\n        logger: {\n          log: loggerStub,\n        },\n      },\n      './questions/ask-questions': {\n        askQuestions: () => {\n          return {config, configLocation};\n        },\n      },\n      'fs-extra': {\n        writeFile: fseWriteFileStub,\n      },\n    });\n\n    await runWizard();\n    const fseArgs = fseWriteFileStub.firstCall.args;\n    expect(fseArgs[0]).to.eql(configLocation);\n    // See https://github.com/GoogleChrome/workbox/issues/2796\n    expect(fseArgs[1]).to.eql(\n      `module.exports = {\n\\tdummy: 123,\n\\tregExp: [\n\\t\\t/1/,\n\\t\\t/2/\n\\t]\n};`,\n    );\n    expect(loggerStub.calledTwice).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-core]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-core/sw/');\n  });\n});\n\ndescribe(`[workbox-core] Load core in the browser`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-core/static/core-in-browser/`;\n  const swURL = `${testingURL}sw.js`;\n\n  it(`should load workbox-core in a service worker.`, async function () {\n    await webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n\n    // If the service worker activated, it meant the assertions in sw.js were\n    // met and workbox-core exposes the expected API and defaults that were\n    // expected\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/static/core-in-browser/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n    <script src=\"/node_modules/sinon/pkg/sinon.js\"></script>\n    <script>\n      window.__test = {};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-core/static/core-in-browser/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\n\nif (!workbox.core.cacheNames.googleAnalytics) {\n  throw new Error(`cacheNames.googleAnalytics is not defined`);\n}\nif (!workbox.core.cacheNames.precache) {\n  throw new Error(`cacheNames.precache is not defined`);\n}\nif (!workbox.core.cacheNames.runtime) {\n  throw new Error(`cacheNames.runtime is not defined`);\n}\nif (!workbox.core.cacheNames.prefix) {\n  throw new Error(`cacheNames.prefix is not defined`);\n}\nif (!workbox.core.cacheNames.suffix) {\n  throw new Error(`cacheNames.suffix is not defined`);\n}\n\nif (!workbox.core.setCacheNameDetails) {\n  throw new Error('setCacheNameDetails() is not defined.');\n}\n\naddEventListener('install', (event) => event.waitUntil(skipWaiting()));\naddEventListener('activate', (event) => event.waitUntil(clients.claim()));\n"
  },
  {
    "path": "test/workbox-core/static/logger.html",
    "content": "<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n  </head>\n  <body>\n    <p>\n      IMPORTANT: Chrome DevTools recently changed to hide console.debug logs, so\n      if you do the debug logs, please check DevTools is set to show all levels.\n    </p>\n    <h1>Current Log Level: \"<span class=\"js-current-level\"></span>\"</h1>\n    <h3>logger.*()</h3>\n    <button class=\"js-debug\">logger.debug()</button>\n    <button class=\"js-log\">logger.log()</button>\n    <button class=\"js-warn\">logger.warn()</button>\n    <button class=\"js-error\">logger.error()</button>\n    <button class=\"js-group\">logger.groupCollapsed()</button>\n\n    <script>\n      // Fakery to make it load in the window\n      self.registration = {\n        scope: 'inject-scope',\n      };\n    </script>\n\n    <script src=\"/__WORKBOX/buildFile/workbox-core\"></script>\n    <script>\n      const logger = workbox.core._private.logger;\n\n      const SIMPLE_OUTPUT_TESTS = [\n        // String\n        'Hello from demo.',\n        // Objects\n        {\n          foo: {\n            bar: {\n              baz: 'Yo.',\n            },\n          },\n        },\n        ['Example of an Array', {with: 'objects'}, true, false],\n        true,\n        false,\n        1234,\n      ];\n      const MULTIPLE_ARGS_OUTPUT_TESTS = [\n        ['Testing', 'multiple', 'strings'],\n        ['Testing', {mixed: 'items'}, 'in', ['a', 'log']],\n        [{this: 'is'}, 'different', 'cos', {strings: 'are'}, 'not first'],\n      ];\n\n      const allLogBtns = [\n        {\n          className: '.js-log',\n          funcName: 'log',\n        },\n        {\n          className: '.js-debug',\n          funcName: 'debug',\n        },\n        {\n          className: '.js-warn',\n          funcName: 'warn',\n        },\n        {\n          className: '.js-error',\n          funcName: 'error',\n        },\n      ];\n      allLogBtns.forEach((logBtnDetails) => {\n        const btnElement = document.querySelector(logBtnDetails.className);\n        btnElement.addEventListener('click', () => {\n          SIMPLE_OUTPUT_TESTS.forEach((output) => {\n            logger[logBtnDetails.funcName](output);\n          });\n\n          MULTIPLE_ARGS_OUTPUT_TESTS.forEach((output) => {\n            logger[logBtnDetails.funcName](...output);\n          });\n\n          console.log('\\n\\n');\n        });\n      });\n\n      const groupBtn = document.querySelector('.js-group');\n      groupBtn.addEventListener('click', () => {\n        logger.groupCollapsed(`I'm the title for logger.groupCollapsed()`);\n\n        const innerLog = [`I'm inside the group`, {cool: true}];\n        logger.debug(...innerLog);\n        logger.log(...innerLog);\n        logger.warn(...innerLog);\n        logger.error(...innerLog);\n\n        logger.groupCollapsed(\n          `I'm the title for a nested logger.groupCollapsed() call`,\n        );\n        const nestedLog = [`I'm doubly inside`, {superCool: true}];\n        logger.debug(...nestedLog);\n        logger.log(...nestedLog);\n        logger.warn(...nestedLog);\n        logger.error(...nestedLog);\n        logger.groupEnd();\n\n        logger.groupEnd();\n\n        console.log('\\n\\n');\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-Deferred.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Deferred} from 'workbox-core/_private/Deferred.mjs';\n\ndescribe(`Deferred`, function () {\n  describe(`constructor`, function () {\n    it(`should create a promise and expose its resolve and reject functions as methods`, function () {\n      expect(new Deferred().promise).to.be.an.instanceof(Promise);\n    });\n  });\n\n  describe(`resolve`, function () {\n    it(`should resolve the Deferred's promise`, function () {\n      const deferred = new Deferred();\n      deferred.resolve();\n\n      return deferred.promise;\n    });\n  });\n\n  describe(`reject`, function () {\n    it(`should reject the Deferred's promise with the passed error`, function (done) {\n      const deferred = new Deferred();\n      const err1 = new Error('1');\n      deferred.reject(err1);\n\n      deferred.promise.catch((err2) => {\n        expect(err1).to.eql(err2);\n        done();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-assert.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {assert} from 'workbox-core/_private/assert';\n\ndescribe(`assert`, function () {\n  it(`should be null in production mode`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      expect(assert).to.equal(null);\n    }\n  });\n\n  describe(`isArray`, function () {\n    it(`shouldn't throw when given an array`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      assert.isArray([], {\n        moduleName: 'module',\n        className: 'class',\n        funcName: 'func',\n        paramName: 'param',\n      });\n    });\n\n    it(`should throw when value isn't an array`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        assert.isArray(\n          {},\n          {\n            moduleName: 'module',\n            className: 'class',\n            funcName: 'func',\n            paramName: 'param',\n          },\n        );\n      }, 'not-an-array');\n    });\n  });\n\n  describe(`isArrayOfClass`, function () {\n    it(`shouldn't throw when given an array same Class`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      class TestClass {}\n      assert.isArrayOfClass(\n        [new TestClass(), new TestClass(), new TestClass()],\n        TestClass,\n        {\n          moduleName: 'module',\n          className: 'class',\n          funcName: 'func',\n          paramName: 'param',\n        },\n      );\n    });\n\n    it(`should throw when value isn't an array of Class`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      class TestClass {}\n      class NotTestClass {}\n      return expectError(() => {\n        assert.isArrayOfClass(\n          [new TestClass(), new NotTestClass(), new TestClass()],\n          TestClass,\n          {\n            moduleName: 'module',\n            className: 'class',\n            funcName: 'func',\n            paramName: 'param',\n          },\n        );\n      }, 'not-array-of-class');\n    });\n\n    it(`should throw when value isn't an array`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      class TestClass {}\n      return expectError(() => {\n        assert.isArrayOfClass({}, TestClass, {\n          moduleName: 'module',\n          className: 'class',\n          funcName: 'func',\n          paramName: 'param',\n        });\n      }, 'not-array-of-class');\n    });\n  });\n\n  describe(`hasMethod`, function () {\n    it(`should throw when it has no method`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        assert.hasMethod({}, 'methodName', {\n          moduleName: 'module',\n          className: 'class',\n          funcName: 'func',\n          paramName: 'param',\n        });\n      }, 'missing-a-method');\n    });\n\n    it(`should throw when it has no method`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      assert.hasMethod({methodName: () => {}}, 'methodName', {\n        moduleName: 'module',\n        className: 'class',\n        funcName: 'func',\n        paramName: 'param',\n      });\n    });\n  });\n\n  describe(`isInstance`, function () {\n    it(`should throw when it is not an instance`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      class Example {}\n      return expectError(() => {\n        assert.isInstance({}, Example, {\n          moduleName: 'module',\n          className: 'class',\n          funcName: 'func',\n          paramName: 'param',\n        });\n      }, 'incorrect-class');\n    });\n\n    it(`should not throw when it is an instance`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      class Example {}\n      assert.isInstance(new Example(), Example, {\n        moduleName: 'module',\n        className: 'class',\n        funcName: 'func',\n        paramName: 'param',\n      });\n    });\n  });\n\n  describe(`isOneOf`, function () {\n    it(`should throw when it is not an instance`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        assert.isOneOf('not-ok', ['ok-value'], {\n          moduleName: 'module',\n          className: 'class',\n          funcName: 'func',\n          paramName: 'param',\n        });\n      }, 'invalid-value');\n    });\n\n    it(`should throw when it is not an instance`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      assert.isOneOf('ok-value', ['ok-value'], {\n        moduleName: 'module',\n        className: 'class',\n        funcName: 'func',\n        paramName: 'param',\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-cacheMatchIgnoreParams.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheMatchIgnoreParams} from 'workbox-core/_private/cacheMatchIgnoreParams.mjs';\n\ndescribe(`cacheMatchIgnoreParams()`, function () {\n  const sandbox = sinon.createSandbox();\n  let cache;\n  const urls = [\n    '/one',\n    '/one?a=1',\n    '/one?a=1&b=2',\n    '/one?a=1&b=2&c=3',\n    '/two',\n    '/two?a=1',\n    '/two?a=1&b=2',\n    '/two?a=1&b=2&c=3',\n  ];\n\n  beforeEach(async function () {\n    await caches.delete('test');\n    cache = await caches.open('test');\n\n    for (const url of urls) {\n      await cache.put(new Request(url), new Response(url));\n    }\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  it(`matches items in the cache ignoring the passed param(s)`, async function () {\n    const key1 = new Request('/one?a=MISS&b=2');\n    const match1 = await cacheMatchIgnoreParams(cache, key1, ['a']);\n    expect(await match1.text()).to.equal('/one?a=1&b=2');\n\n    const key2 = new Request('/one?a=1&b=MISS&c=3');\n    const match2 = await cacheMatchIgnoreParams(cache, key2, ['b']);\n    expect(await match2.text()).to.equal('/one?a=1&b=2&c=3');\n\n    const key3 = new Request('/one?a=1&b=MISS&c=MISS');\n    const match3 = await cacheMatchIgnoreParams(cache, key3, ['b', 'c']);\n    expect(await match3.text()).to.equal('/one?a=1');\n  });\n\n  it(`matches the first item if there's more than one match`, async function () {\n    const key = new Request('/two?a=1&b=2&c=MISS');\n    const match = await cacheMatchIgnoreParams(cache, key, ['c']);\n\n    // Event though `/two?a=1&b=2&c=3` is also a match, the URL with\n    // to `c` param appears in the cache first.\n    expect(await match.text()).to.equal('/two?a=1&b=2');\n  });\n\n  it(`matches as normal if the param is not present`, async function () {\n    const key = new Request('/two');\n\n    const match1 = await cacheMatchIgnoreParams(cache, key, ['c']);\n    expect(await match1.text()).to.equal('/two');\n\n    const match2 = await cacheMatchIgnoreParams(cache, key, ['a', 'b', 'c']);\n    expect(await match2.text()).to.equal('/two');\n  });\n\n  it(`returns undefined if no match is found`, async function () {\n    const key = new Request('/two?a=MISS&b=2');\n    const match = await cacheMatchIgnoreParams(cache, key, ['b']);\n    expect(match).to.equal(undefined);\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-executeQuotaErrorCallbacks.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {executeQuotaErrorCallbacks} from 'workbox-core/_private/executeQuotaErrorCallbacks.mjs';\nimport {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs';\n\ndescribe(`executeQuotaErrorCallbacks()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it('should call everything registered with registerQuotaErrorCallback()', async function () {\n    const callback1 = sandbox.stub();\n    registerQuotaErrorCallback(callback1);\n    const callback2 = sandbox.stub();\n    registerQuotaErrorCallback(callback2);\n\n    await executeQuotaErrorCallbacks();\n\n    expect(callback1.calledOnce).to.be.true;\n    expect(callback2.calledOnce).to.be.true;\n  });\n\n  it(`shouldn't have any effect if called multiple times with the same callback`, async function () {\n    const callback1 = sandbox.stub();\n    registerQuotaErrorCallback(callback1);\n    registerQuotaErrorCallback(callback1);\n    registerQuotaErrorCallback(callback1);\n\n    await executeQuotaErrorCallbacks();\n\n    expect(callback1.calledOnce).to.be.true;\n  });\n\n  it(`should call everything registered with registerQuotaErrorCallback(), each time it's called`, async function () {\n    const callback1 = sandbox.stub();\n    registerQuotaErrorCallback(callback1);\n    const callback2 = sandbox.stub();\n    registerQuotaErrorCallback(callback2);\n\n    await executeQuotaErrorCallbacks();\n    await executeQuotaErrorCallbacks();\n\n    expect(callback1.calledTwice).to.be.true;\n    expect(callback2.calledTwice).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-getFriendlyURL.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getFriendlyURL} from 'workbox-core/_private/getFriendlyURL.mjs';\n\ndescribe(`getFriendlyURL()`, function () {\n  it(`should return short URL for local origin '/'`, function () {\n    const url = getFriendlyURL('/');\n    expect(url).to.equal('/');\n  });\n\n  it(`should return short URL for local origin '<local origin>/hi'`, function () {\n    const fullURL = new URL('/hi', self.location).toString();\n    const url = getFriendlyURL(fullURL);\n    expect(url).to.equal('/hi');\n  });\n\n  it(`should include the URL's hash in the returned string`, function () {\n    const fullURL = new URL('/hi#test', self.location).toString();\n    const url = getFriendlyURL(fullURL);\n    expect(url).to.equal('/hi#test');\n  });\n\n  it(`should include the URL's search in the returned string`, function () {\n    const fullURL = new URL('/hi?one=two', self.location).toString();\n    const url = getFriendlyURL(fullURL);\n    expect(url).to.equal('/hi?one=two');\n  });\n\n  it(`should include the URL's search and hash in the returned string`, function () {\n    const fullURL = new URL('/hi?one=two#test', self.location).toString();\n    const url = getFriendlyURL(fullURL);\n    expect(url).to.equal('/hi?one=two#test');\n  });\n\n  it(`should return full URL for external origin 'https://external-example.com/example'`, function () {\n    const url = getFriendlyURL('https://external-example.com/example');\n    expect(url).to.equal('https://external-example.com/example');\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-logger.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\n\ndescribe(`logger`, function () {\n  const SAFARI_USER_AGENT = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.1.1 Safari/605.1.15`;\n  const CHROME_USER_AGENT = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36`;\n\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n\n    // Undo the logger stubs setup in infra/testing/auto-stub-logger.mjs\n    // But do this conditionally as logger will be `null` in production node.\n    if (logger) {\n      Object.keys(logger).forEach((key) => {\n        if (logger[key].restore) {\n          logger[key].restore();\n        }\n      });\n    }\n\n    self.__WB_DISABLE_DEV_LOGS = false;\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  const consoleLevels = ['debug', 'log', 'warn', 'error'];\n\n  const validateStub = (stub, expectedArgs, isPrefixed) => {\n    expect(stub.callCount).to.equal(1);\n\n    const calledArgs = stub.args[0];\n    // 'workbox' is our prefix and '%c' enables styling in the console.\n    if (isPrefixed) {\n      const prefix = calledArgs.splice(0, 2);\n\n      expect(prefix[0]).to.equal('%cworkbox');\n    }\n\n    expect(calledArgs).to.deep.equal(expectedArgs);\n  };\n\n  it(`should be null in production mode`, function () {\n    if (process.env.NODE_ENV !== 'production') this.skip();\n\n    expect(logger).to.equal(null);\n  });\n\n  it(`should toggle logging based on the value of __WB_DISABLE_DEV_LOGS`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    const logStub = sandbox.stub(console, 'log');\n\n    self.__WB_DISABLE_DEV_LOGS = true;\n    logger.log('');\n    expect(logStub.callCount).to.eql(0);\n\n    self.__WB_DISABLE_DEV_LOGS = false;\n    logger.log('');\n    expect(logStub.callCount).to.eql(1);\n  });\n\n  consoleLevels.forEach((consoleLevel) => {\n    describe(`.${consoleLevel}()`, function () {\n      it(`should work without input`, function () {\n        if (process.env.NODE_ENV === 'production') this.skip();\n\n        const stub = sandbox.stub(console, consoleLevel);\n\n        logger[consoleLevel]();\n\n        // Restore so mocha tests can properly log.\n        sandbox.restore();\n\n        validateStub(stub, [], true);\n      });\n\n      it(`should work with several inputs`, function () {\n        if (process.env.NODE_ENV === 'production') this.skip();\n\n        const stub = sandbox.stub(console, consoleLevel);\n\n        const args = ['', 'test', null, undefined, [], {}];\n        logger[consoleLevel](...args);\n\n        // Restore so mocha tests can properly log.\n        sandbox.restore();\n\n        validateStub(stub, args, true);\n      });\n    });\n  });\n\n  describe(`.groupCollapsed()`, function () {\n    it(`should work without input`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      const stub = sandbox.stub(console, 'groupCollapsed');\n      sandbox.stub(console, 'groupEnd');\n\n      logger.groupCollapsed();\n      logger.groupEnd();\n\n      // Restore so mocha tests can properly log.\n      sandbox.restore();\n\n      expect(stub.callCount).to.equal(1);\n    });\n\n    // There's User-Agent sniffing in the logger code, so we need to run\n    // two different test scenarios for Safari and non-Safari browsers.\n    // See https://github.com/GoogleChrome/workbox/issues/2149\n    for (const [userAgent, isPrefixed] of new Map([\n      [SAFARI_USER_AGENT, false],\n      [CHROME_USER_AGENT, true],\n    ])) {\n      it(`should work with several inputs (for ${userAgent})`, function () {\n        if (process.env.NODE_ENV === 'production') this.skip();\n\n        const stub = sandbox.stub(console, 'groupCollapsed');\n        sandbox.replaceGetter(navigator, 'userAgent', () => userAgent);\n        sandbox.stub(console, 'groupEnd');\n\n        const args = ['', 'test', null, undefined, [], {}];\n        logger.groupCollapsed(...args);\n        logger.groupEnd();\n\n        // Restore so mocha tests can properly log.\n        sandbox.restore();\n\n        validateStub(stub, args, isPrefixed);\n      });\n\n      it(`should not prefix log message until after .groupEnd() is called (for ${userAgent})`, function () {\n        if (process.env.NODE_ENV === 'production') this.skip();\n\n        sandbox.replaceGetter(navigator, 'userAgent', () => userAgent);\n\n        const debugStub = sandbox.stub(console, 'debug');\n        const logStub = sandbox.stub(console, 'log');\n        const warnStub = sandbox.stub(console, 'warn');\n        const errorStub = sandbox.stub(console, 'error');\n        sandbox.stub(console, 'groupCollapsed');\n        sandbox.stub(console, 'groupEnd');\n\n        logger.groupCollapsed();\n        logger.debug();\n        logger.log();\n        logger.warn();\n        logger.error();\n        logger.groupEnd();\n\n        // Restore so mocha tests can properly log.\n        sandbox.restore();\n\n        validateStub(debugStub, [], !isPrefixed);\n        validateStub(logStub, [], !isPrefixed);\n        validateStub(warnStub, [], !isPrefixed);\n        validateStub(errorStub, [], !isPrefixed);\n\n        // After `groupEnd()`, subsequent logs should be prefixed again.\n        const logStub2 = sandbox.stub(console, 'log');\n\n        logger.log();\n\n        // Restore so mocha tests can properly log.\n        sandbox.restore();\n\n        validateStub(logStub2, [], true);\n      });\n    }\n  });\n\n  describe(`.groupEnd()`, function () {\n    it(`should work without input`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      const stub = sandbox.stub(console, 'groupEnd');\n\n      logger.groupEnd();\n\n      // Restore so mocha tests can properly log.\n      sandbox.restore();\n\n      expect(stub.callCount).to.equal(1);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-resultingClientExists.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {resultingClientExists} from 'workbox-core/_private/resultingClientExists.mjs';\n\ndescribe(`resultingClientExists()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should resolve to a matching window client ID`, async function () {\n    sandbox\n      .stub(clients, 'matchAll')\n      .onFirstCall()\n      .resolves([{id: '1'}, {id: '2'}])\n      .onSecondCall()\n      .resolves([{id: '1'}, {id: '2'}])\n      .onThirdCall()\n      .resolves([{id: '1'}, {id: '3'}]);\n\n    const win = await resultingClientExists('3');\n    expect(win.id).to.equal('3');\n  });\n\n  it(`should resolve to undefined when not passed a value`, async function () {\n    sandbox\n      .stub(clients, 'matchAll')\n      .onFirstCall()\n      .resolves([{id: '1'}, {id: '2'}])\n      .onSecondCall()\n      .resolves([{id: '1'}, {id: '2'}])\n      .onThirdCall()\n      .resolves([{id: '1'}, {id: '3'}]);\n\n    const startTime = performance.now();\n    const win = await resultingClientExists();\n\n    expect(win).to.equal(undefined);\n    expect(performance.now() - startTime).to.be.below(2000);\n  });\n\n  it(`should resolve to undefined after 2 seconds of unsuccessful retrying`, async function () {\n    sandbox.stub(clients, 'matchAll').resolves([{id: '1'}, {id: '2'}]);\n\n    const startTime = performance.now();\n    const win = await resultingClientExists('3');\n\n    expect(win).to.equal(undefined);\n    expect(performance.now() - startTime).to.be.above(2000);\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-timeout.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {timeout} from 'workbox-core/_private/timeout.mjs';\n\ndescribe(`timeout()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should return a promise that resolves after the passed number of milliseconds`, function (done) {\n    const clock = sandbox.useFakeTimers();\n    const startTime = performance.now();\n\n    timeout(123).then(() => {\n      expect(performance.now() - startTime).to.equal(123);\n      clock.tick(456);\n    });\n\n    timeout(456).then(() => {\n      expect(performance.now() - startTime).to.equal(123 + 456);\n      done();\n    });\n\n    clock.tick(123);\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/_private/test-waitUntil.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {waitUntil} from 'workbox-core/_private/waitUntil.mjs';\nimport {spyOnEvent} from '../../../../infra/testing/helpers/extendable-event-utils.mjs';\n\ndescribe(`waitUntil()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n  });\n\n  after(async function () {\n    sandbox.restore();\n  });\n\n  it(`adds an async function's returned promise to an event and returns it`, async function () {\n    const event = new ExtendableEvent('install');\n    spyOnEvent(event);\n\n    const promise = new Promise((resolve) => resolve('test'));\n    const result = await waitUntil(event, () => promise);\n\n    expect(result).to.equal('test');\n    expect(event.waitUntil.args[0][0]).to.equal(promise);\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/models/messages/test-messageGenerator.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {messageGenerator} from 'workbox-core/models/messages/messageGenerator.mjs';\n\ndescribe(`messageGenerator`, function () {\n  const detailsObj = {\n    exampleDetail: 'With Value',\n  };\n  const detailsString = `${JSON.stringify([detailsObj])}`;\n\n  it(`should handle unknown codes`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      const message = messageGenerator('fake-code');\n\n      expect(message).to.equal('fake-code');\n    } else {\n      expect(() => {\n        messageGenerator('fake-code');\n      }).to.throw();\n    }\n  });\n\n  it(`should return the code with details if the code is unknown`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      const message = messageGenerator('fake-code', detailsObj);\n      expect(message).to.equal(`fake-code :: ${detailsString}`);\n    } else {\n      expect(() => {\n        messageGenerator('fake-code', detailsObj);\n      }).to.throw();\n    }\n  });\n\n  it(`should throw an error if the code is valid but no required details are defined`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      const message = messageGenerator('incorrect-type');\n      expect(message).to.equal(`incorrect-type`);\n    } else {\n      expect(() => {\n        messageGenerator('incorrect-type');\n      }).to.throw();\n    }\n  });\n\n  it(`should throw an error if the code is valid but the arguments are missing details`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      const message = messageGenerator('incorrect-type', detailsObj);\n      expect(message).to.equal(`incorrect-type :: ${detailsString}`);\n    } else {\n      expect(() => {\n        messageGenerator('incorrect-type', {random: 'details'});\n      }).to.throw();\n    }\n  });\n\n  it(`should return the message if the code and details are valid`, function () {\n    const invalidTypeDetails = {\n      moduleName: 'test',\n      className: 'test',\n      funcName: 'test',\n      paramName: 'Param',\n      expectedType: 'Type',\n    };\n\n    const message = messageGenerator('incorrect-type', invalidTypeDetails);\n    if (process.env.NODE_ENV === 'production') {\n      expect(message).to.equal(\n        `incorrect-type :: ${JSON.stringify([invalidTypeDetails])}`,\n      );\n    } else {\n      expect(message.indexOf('incorrect-type')).to.equal(-1);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/test-cacheNames.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/cacheNames.mjs';\nimport {setCacheNameDetails} from 'workbox-core/setCacheNameDetails.mjs';\nimport generateVariantTests from '../../../infra/testing/generate-variant-tests';\n\ndescribe(`cacheNames`, function () {\n  afterEach(function () {\n    // TODO(gauntface): there should be a way to get access to the current\n    // (or default) prefix and suffix values so they can be restored here.\n    setCacheNameDetails({\n      prefix: 'workbox',\n      suffix: self.registration.scope,\n      precache: 'precache',\n      runtime: 'runtime',\n      googleAnalytics: 'googleAnalytics',\n    });\n  });\n\n  it('should return expected defaults', function () {\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(\n      `workbox-precache-v2-${self.registration.scope}`,\n    );\n    expect(cacheNames.runtime).to.equal(\n      `workbox-runtime-${self.registration.scope}`,\n    );\n    expect(cacheNames.prefix).to.equal('workbox');\n    expect(cacheNames.suffix).to.equal(self.registration.scope);\n  });\n\n  it('should allow customising the prefix', function () {\n    setCacheNameDetails({prefix: 'test-prefix'});\n\n    // Scope by default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(\n      `test-prefix-precache-${self.registration.scope}`,\n    );\n    expect(cacheNames.runtime).to.equal(\n      `test-prefix-runtime-${self.registration.scope}`,\n    );\n    expect(cacheNames.prefix).to.equal('test-prefix');\n  });\n\n  it('should allow customising the suffix', function () {\n    setCacheNameDetails({suffix: 'test-suffix'});\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(`workbox-precache-test-suffix`);\n    expect(cacheNames.runtime).to.equal(`workbox-runtime-test-suffix`);\n    expect(cacheNames.suffix).to.equal('test-suffix');\n  });\n\n  it('should allow customising the precache name', function () {\n    setCacheNameDetails({precache: 'test-precache'});\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(\n      `workbox-test-precache-${self.registration.scope}`,\n    );\n  });\n\n  it('should allow customising the runtime name', function () {\n    setCacheNameDetails({runtime: 'test-runtime'});\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(\n      `workbox-precache-${self.registration.scope}`,\n    );\n    expect(cacheNames.runtime).to.equal(\n      `workbox-test-runtime-${self.registration.scope}`,\n    );\n  });\n\n  it('should allow customising the googleAnalytics name', function () {\n    setCacheNameDetails({googleAnalytics: 'test-ga'});\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.googleAnalytics).to.equal(\n      `workbox-test-ga-${self.registration.scope}`,\n    );\n  });\n\n  it('should allow customising all', function () {\n    setCacheNameDetails({\n      prefix: 'test-prefix',\n      suffix: 'test-suffix',\n      precache: 'test-precache',\n      runtime: 'test-runtime',\n      googleAnalytics: 'test-ga',\n    });\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(\n      `test-prefix-test-precache-test-suffix`,\n    );\n    expect(cacheNames.runtime).to.equal(`test-prefix-test-runtime-test-suffix`);\n    expect(cacheNames.googleAnalytics).to.equal(\n      `test-prefix-test-ga-test-suffix`,\n    );\n  });\n\n  it('should allow setting prefix and suffix to empty string', function () {\n    setCacheNameDetails({\n      prefix: '',\n      suffix: '',\n      precache: 'test-precache',\n      runtime: 'test-runtime',\n      googleAnalytics: 'test-ga',\n    });\n\n    // Scope be default is '/' from 'service-worker-mock'\n    expect(cacheNames.precache).to.equal(`test-precache`);\n    expect(cacheNames.runtime).to.equal(`test-runtime`);\n    expect(cacheNames.googleAnalytics).to.equal(`test-ga`);\n  });\n\n  it('should not allow precache to be an empty string in dev', function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    return expectError(() => {\n      setCacheNameDetails({\n        precache: '',\n      });\n    }, 'invalid-cache-name');\n  });\n\n  it('should not allow runtime to be an empty string in dev', function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    return expectError(() => {\n      setCacheNameDetails({\n        runtime: '',\n      });\n    }, 'invalid-cache-name');\n  });\n\n  it('should not allow googleAnalytics to be an empty string in dev', function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    return expectError(() => {\n      setCacheNameDetails({\n        googleAnalytics: '',\n      });\n    }, 'invalid-cache-name');\n  });\n\n  const badValues = [undefined, null, {}, [], true, false];\n  generateVariantTests(\n    `should handle bad prefix values in dev`,\n    badValues,\n    function (variant) {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      return expectError(() => {\n        setCacheNameDetails({\n          prefix: variant,\n        });\n      }, 'incorrect-type');\n    },\n  );\n\n  generateVariantTests(\n    `should handle bad suffix values in dev`,\n    badValues,\n    function (variant) {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      return expectError(() => {\n        setCacheNameDetails({\n          suffix: variant,\n        });\n      }, 'incorrect-type');\n    },\n  );\n\n  generateVariantTests(\n    `should handle bad precache values in dev`,\n    badValues,\n    function (variant) {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      return expectError(() => {\n        setCacheNameDetails({\n          precache: variant,\n        });\n      }, 'incorrect-type');\n    },\n  );\n\n  generateVariantTests(\n    `should handle bad runtime values in dev`,\n    badValues,\n    function (variant) {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      return expectError(() => {\n        setCacheNameDetails({\n          runtime: variant,\n        });\n      }, 'incorrect-type');\n    },\n  );\n\n  generateVariantTests(\n    `should not throw in prod`,\n    badValues,\n    function (variant) {\n      if (process.env.NODE_ENV !== 'production') return this.skip();\n\n      setCacheNameDetails({\n        prefix: variant,\n        suffix: variant,\n        precache: variant,\n        runtime: variant,\n      });\n    },\n  );\n});\n"
  },
  {
    "path": "test/workbox-core/sw/test-clientsClaim.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {clientsClaim} from 'workbox-core/clientsClaim.mjs';\n\ndescribe(`clientsClaim`, function () {\n  const sandbox = sinon.createSandbox();\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should add an activate event listener that calls clientsClaim`, function () {\n    const clientsClaimSpy = sandbox.stub(self.clients, 'claim');\n\n    sandbox.stub(self, 'addEventListener').callsFake((eventName, cb) => {\n      expect(eventName).to.equal('activate');\n      cb();\n      expect(clientsClaimSpy.callCount).to.equal(1);\n    });\n\n    clientsClaim();\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/test-copyResponse.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {copyResponse} from 'workbox-core/copyResponse.mjs';\n\ndescribe(`copyResponse`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  // In some browsers this is '' (Chrome) and in some it's 'OK' (Edge 18).\n  const defaultStatusText = new Response().statusText;\n\n  const makeResponse = (url) => {\n    const body = new Blob(['console.log()'], {type: 'text/javascript'});\n    const response = new Response(body, {headers: {'X-One': '1'}});\n\n    // Default to a \"real\" same-origin URL, unless there's one passed in.\n    if (url === undefined) {\n      url = new URL('/app.js', self.location.origin).href;\n    }\n    sandbox.replaceGetter(response, 'url', () => url);\n\n    return response;\n  };\n\n  it(`should throw the expected exception when passed a cross-origin response`, async function () {\n    const crossOriginResponse = makeResponse('https://cross-origin.com/app.js');\n    await expectError(\n      () => copyResponse(crossOriginResponse),\n      'cross-origin-copy-response',\n    );\n  });\n\n  it(`should throw the expected exception when passed an opaque response`, async function () {\n    const opaqueResponse = makeResponse('');\n    await expectError(\n      () => copyResponse(opaqueResponse),\n      'cross-origin-copy-response',\n    );\n  });\n\n  it(`should allow modifying a response via the modifier return value`, async function () {\n    const oldResponse = makeResponse();\n    const newResponse1 = await copyResponse(oldResponse, (init) => {\n      // Test modifying the existing headers.\n      init.headers.set('Content-Type', 'text/plain');\n      init.headers.set('X-Two', '2');\n      init.headers.append('X-One', 'another');\n      init.status = 203;\n      init.statusText = 'Really?';\n\n      return init;\n    });\n\n    expect(newResponse1.headers.get('Content-Type')).to.equal('text/plain');\n    expect(newResponse1.headers.get('X-One')).to.equal('1, another');\n    expect(newResponse1.headers.get('X-Two')).to.equal('2');\n    expect(newResponse1.status).to.equal(203);\n    expect(newResponse1.statusText).to.equal('Really?');\n    expect(await newResponse1.text()).to.equal('console.log()');\n\n    const newResponse2 = await copyResponse(oldResponse, (init) => {\n      // Test setting an object.\n      init.headers = {'X-Two': '2'};\n\n      return init;\n    });\n\n    // The `Content-Type` header comes from the body.\n    expect(newResponse2.headers.get('Content-Type')).to.equal(\n      'text/javascript',\n    );\n    expect(newResponse2.headers.get('X-One')).to.equal(null);\n    expect(newResponse2.headers.get('X-Two')).to.equal('2');\n    expect(newResponse2.status).to.equal(200);\n    expect(newResponse2.statusText).to.equal(defaultStatusText);\n    expect(await newResponse2.text()).to.equal('console.log()');\n\n    const newResponse3 = await copyResponse(oldResponse, (init) => {\n      // Test return an entirely new object\n      return {\n        headers: {'X-Two': '2', 'X-Three': '3'},\n      };\n    });\n\n    // The `Content-Type` header comes from the body.\n    expect(newResponse3.headers.get('Content-Type')).to.equal(\n      'text/javascript',\n    );\n    expect(newResponse3.headers.get('X-One')).to.equal(null);\n    expect(newResponse3.headers.get('X-Two')).to.equal('2');\n    expect(newResponse3.headers.get('X-Three')).to.equal('3');\n    expect(newResponse3.status).to.equal(200);\n    expect(newResponse3.statusText).to.equal(defaultStatusText);\n    expect(await newResponse3.text()).to.equal('console.log()');\n  });\n\n  it(`should copy a response as-is when no modifier is passed`, async function () {\n    const oldResponse = makeResponse();\n    const newResponse = await copyResponse(oldResponse);\n\n    expect(newResponse.headers.get('X-One')).to.equal('1');\n    expect(newResponse.headers.get('content-type')).to.equal('text/javascript');\n    expect(newResponse.status).to.equal(200);\n    expect(newResponse.statusText).to.equal(defaultStatusText);\n    expect(await newResponse.text()).to.equal('console.log()');\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/test-registerQuotaErrorCallback.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs';\n\ndescribe(`registerQuotaErrorCallback()`, function () {\n  it(`should throw when passed a non-function in dev mode`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    await expectError(() => registerQuotaErrorCallback(null), 'incorrect-type');\n  });\n});\n"
  },
  {
    "path": "test/workbox-core/sw/test-skipWaiting.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {skipWaiting} from 'workbox-core/skipWaiting.mjs';\n\ndescribe(`skipWaiting`, function () {\n  const sandbox = sinon.createSandbox();\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should call self.skipWaiting()`, function () {\n    const skipWaitingStub = sandbox.stub(self, 'skipWaiting');\n\n    skipWaiting();\n\n    expect(skipWaitingStub.callCount).to.eql(1);\n  });\n\n  it(`should log a warning message in development`, function () {\n    if (process.env.NODE_ENV === 'production') {\n      this.skip();\n    }\n\n    const warnStub = sandbox.stub(logger, 'warn');\n    const skipWaitingStub = sandbox.stub(self, 'skipWaiting');\n\n    skipWaiting();\n\n    expect(skipWaitingStub.callCount).to.eql(1);\n    expect(warnStub.callCount).to.eql(1);\n  });\n});\n"
  },
  {
    "path": "test/workbox-expiration/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\nconst waitUntil = require('../../../infra/testing/wait-until');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-expiration]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-expiration/sw/');\n  });\n});\n\ndescribe(`[workbox-expiration] Plugin`, function () {\n  const baseURL = `${server.getAddress()}/test/workbox-expiration/static/expiration-plugin/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should load a page with entries managed by maxEntries`, async function () {\n    const swURL = `${baseURL}sw-max-entries.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-1.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has some content.\n    await waitUntil(async () => {\n      const keys = await runInSW('cachesKeys');\n      return keys.length > 0;\n    });\n\n    const keys = await runInSW('cachesKeys');\n    expect(keys).to.deep.equal(['expiration-plugin-max-entries']);\n\n    let cachedRequests = await runInSW('cacheURLs', keys[0]);\n    expect(cachedRequests).to.eql([`${baseURL}example-1.txt`]);\n\n    error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-2.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has be cleaned up.\n    await waitUntil(async () => {\n      cachedRequests = await runInSW('cacheURLs', keys[0]);\n      return (\n        cachedRequests.length === 1 &&\n        cachedRequests[0] === `${baseURL}example-2.txt`\n      );\n    });\n  });\n\n  it(`should load a page with entries managed by maxAgeSeconds`, async function () {\n    const swURL = `${baseURL}sw-max-age-seconds.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-1.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has some content.\n    await waitUntil(async () => {\n      const keys = await runInSW('cachesKeys');\n      return keys.length > 0;\n    });\n\n    const keys = await runInSW('cachesKeys');\n    expect(keys).to.deep.equal(['expiration-plugin-max-age-seconds']);\n\n    let cachedRequests = await runInSW('cacheURLs', keys[0]);\n    expect(cachedRequests).to.eql([`${baseURL}example-1.txt`]);\n\n    // Wait 2 seconds to expire entry.\n    await new Promise((resolve) => {\n      setTimeout(resolve, 2000);\n    });\n\n    error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-2.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has be cleaned up.\n    await waitUntil(async () => {\n      cachedRequests = await runInSW('cacheURLs', keys[0]);\n      return (\n        cachedRequests.length === 1 &&\n        cachedRequests[0] === `${baseURL}example-2.txt`\n      );\n    });\n  });\n\n  it(`should clean up when deleteCacheAndMetadata() is called`, async function () {\n    const swURL = `${baseURL}sw-deletion.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let error = await webdriver.executeAsyncScript((cb) => {\n      fetch(`example-1.txt`)\n        .then(() => cb())\n        .catch((err) => cb(err.message));\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // Caching is done async from returning a response, so we may need\n    // to wait before the cache has some content.\n    await waitUntil(async () => {\n      const keys = await runInSW('cachesKeys');\n      return keys.length > 0;\n    });\n\n    let keys = await runInSW('cachesKeys');\n    expect(keys).to.deep.equal(['expiration-plugin-deletion']);\n\n    const existence = await runInSW('doesDbExist', 'workbox-expiration');\n    expect(existence).to.be.true;\n\n    error = await webdriver.executeAsyncScript((cb) => {\n      navigator.serviceWorker.addEventListener(\n        'message',\n        (event) => {\n          cb(event.data);\n        },\n        {once: true},\n      );\n      navigator.serviceWorker.controller.postMessage('delete');\n    });\n    if (error) {\n      throw new Error(error);\n    }\n\n    // After cleanup, there shouldn't be any cache keys or IndexedDB entries\n    // with the cacheName 'expiration-plugin-deletion'.\n    keys = await runInSW('cachesKeys');\n    expect(keys).to.deep.equal([]);\n\n    const entries = (\n      await runInSW(\n        'getObjectStoreEntries',\n        'workbox-expiration',\n        'cache-entries',\n      )\n    ).filter((entry) => {\n      return entry.cacheName === 'expiration-plugin-deletion';\n    });\n\n    expect(entries).to.deep.equal([]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-expiration/static/expiration-plugin/example-1.txt",
    "content": "example-1.txt\n"
  },
  {
    "path": "test/workbox-expiration/static/expiration-plugin/example-2.txt",
    "content": "example-2.txt\n"
  },
  {
    "path": "test/workbox-expiration/static/expiration-plugin/sw-deletion.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-expiration');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nconst expirationPlugin = new workbox.expiration.ExpirationPlugin({\n  maxEntries: 1,\n  purgeOnQuotaError: true,\n});\n\nconst cacheName = 'expiration-plugin-deletion';\n\nworkbox.routing.registerRoute(\n  /.*.txt/,\n  new workbox.strategies.CacheFirst({\n    cacheName,\n    plugins: [expirationPlugin],\n  }),\n);\n\nself.addEventListener('message', async (event) => {\n  let message;\n\n  if (event.data === 'delete') {\n    try {\n      await expirationPlugin.deleteCacheAndMetadata();\n    } catch (error) {\n      message = error.message;\n    }\n  }\n\n  // Send all open clients a message indicating that deletion is done.\n  const clients = await self.clients.matchAll();\n  for (const client of clients) {\n    client.postMessage(message);\n  }\n});\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-expiration/static/expiration-plugin/sw-max-age-seconds.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-expiration');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.routing.registerRoute(\n  /.*.txt/,\n  new workbox.strategies.CacheFirst({\n    cacheName: 'expiration-plugin-max-age-seconds',\n    plugins: [\n      new workbox.expiration.ExpirationPlugin({\n        maxAgeSeconds: 1,\n      }),\n    ],\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-expiration/static/expiration-plugin/sw-max-entries.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-expiration');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.routing.registerRoute(\n  /.*.txt/,\n  new workbox.strategies.CacheFirst({\n    cacheName: 'expiration-plugin-max-entries',\n    plugins: [\n      new workbox.expiration.ExpirationPlugin({\n        maxEntries: 1,\n      }),\n    ],\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-expiration/static/isURLExpired.html",
    "content": "<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n  </head>\n  <body>\n    <p>Log time of isURLExpired();</p>\n\n    <script>\n      // Fakery to make it load in the window\n      self.registration = {\n        scope: 'inject-scope',\n      };\n    </script>\n\n    <script src=\"../../../../packages/workbox-core/build/browser/workbox-core.dev.js\"></script>\n    <script src=\"../../../../packages/workbox-expiration/build/browser/workbox-expiration.dev.js\"></script>\n    <script>\n      const CacheExpiration = workbox.expiration.CacheExpiration;\n\n      const expirationManager = new CacheExpiration('cache-name', {\n        maxAgeSeconds: 2,\n      });\n      expirationManager.updateTimestamp('/', Date.now());\n\n      const test = async () => {\n        const t0 = performance.now();\n        const expired = await expirationManager.isURLExpired('/');\n        const t1 = performance.now();\n        console.log(\n          `Call to doSomething took \"${t1 - t0}\" milliseconds.`,\n          expired,\n        );\n      };\n\n      test();\n\n      setTimeout(test, 2000);\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-expiration/sw/test-CacheExpiration.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheTimestampsModel} from 'workbox-expiration/models/CacheTimestampsModel.mjs';\nimport {CacheExpiration} from 'workbox-expiration/CacheExpiration.mjs';\nimport {openDB} from 'idb';\n\ndescribe(`CacheExpiration`, function () {\n  const sandbox = sinon.createSandbox();\n  let db = null;\n\n  beforeEach(async function () {\n    db = await openDB('workbox-expiration', 1, {\n      upgrade: CacheTimestampsModel.prototype._upgradeDb,\n    });\n    await db.clear('cache-entries');\n\n    const cacheKeys = await caches.keys();\n    for (const cacheKey of cacheKeys) {\n      await caches.delete(cacheKey);\n    }\n\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should be able to construct with cacheName and maxEntries`, function () {\n      const expirationManager = new CacheExpiration('test-cache', {\n        maxEntries: 10,\n      });\n      expect(expirationManager._maxEntries).to.equal(10);\n    });\n\n    it(`should be able to construct with cacheName and maxAgeSeconds`, function () {\n      const expirationManager = new CacheExpiration('test-cache', {\n        maxAgeSeconds: 10,\n      });\n      expect(expirationManager._maxAgeSeconds).to.equal(10);\n    });\n\n    it(`should be able to construct with cacheName, maxEntries and maxAgeSeconds`, function () {\n      const expirationManager = new CacheExpiration('test-cache', {\n        maxEntries: 1,\n        maxAgeSeconds: 2,\n      });\n      expect(expirationManager._maxEntries).to.equal(1);\n      expect(expirationManager._maxAgeSeconds).to.equal(2);\n    });\n\n    it(`should be able to construct with cacheName, maxEntries and matchOptions`, function () {\n      const expirationManager = new CacheExpiration('test-cache', {\n        maxEntries: 1,\n        matchOptions: {\n          ignoreVary: true,\n        },\n      });\n\n      expect(expirationManager._maxEntries).to.eql(1);\n      expect(expirationManager._matchOptions).to.eql({ignoreVary: true});\n    });\n\n    it(`should throw with no config`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new CacheExpiration('my-cache');\n      }, 'max-entries-or-age-required');\n    });\n\n    // TODO Bad constructor input\n  });\n\n  describe(`expireEntries()`, function () {\n    it(`should expire and delete expired entries`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const cacheName = 'expire-and-delete';\n      const cache = await caches.open(cacheName);\n      const expirationManager = new CacheExpiration(cacheName, {\n        maxAgeSeconds: 10,\n      });\n\n      const timestampModel = new CacheTimestampsModel(cacheName);\n      await timestampModel.setTimestamp('/one', Date.now());\n      await cache.put(\n        `${location.origin}/one`,\n        new Response('Injected request'),\n      );\n\n      clock.tick(5000);\n\n      // Add another entry after 5 seconds.\n      await timestampModel.setTimestamp('/two', Date.now());\n      await cache.put(\n        `${location.origin}/two`,\n        new Response('Injected request'),\n      );\n\n      // Ensure both entries are still present after an initial expire.\n      await expirationManager.expireEntries();\n\n      let timestamps = await db.getAll('cache-entries');\n      expect(timestamps).to.have.lengthOf(2);\n      expect(timestamps[0].url).to.equal(`${location.origin}/one`);\n      expect(timestamps[1].url).to.equal(`${location.origin}/two`);\n\n      let cachedRequests = await cache.keys();\n      expect(cachedRequests).to.have.lengthOf(2);\n      expect(cachedRequests[0].url).to.equal(`${location.origin}/one`);\n      expect(cachedRequests[1].url).to.equal(`${location.origin}/two`);\n\n      // Tick the clock 6 seconds, so the first entry should now be expired.\n      clock.tick(6000);\n      await expirationManager.expireEntries();\n\n      timestamps = await db.getAll('cache-entries');\n      expect(timestamps).to.have.lengthOf(1);\n      expect(timestamps[0].url).to.equal(`${location.origin}/two`);\n\n      // Check cache is empty\n      cachedRequests = await cache.keys();\n      expect(cachedRequests).to.have.lengthOf(1);\n      expect(cachedRequests[0].url).to.equal(`${location.origin}/two`);\n\n      // Tick the clock 5 more seconds, so all entries should be expired.\n      clock.tick(5000);\n      await expirationManager.expireEntries();\n\n      // Check IDB is empty\n      timestamps = await db.getAll('cache-entries');\n      expect(timestamps).to.deep.equal([]);\n\n      // Check cache is empty\n      cachedRequests = await cache.keys();\n      expect(cachedRequests).to.deep.equal([]);\n    });\n\n    it(`should expire and delete entries beyond maximum entries`, async function () {\n      const cacheName = 'max-and-delete';\n      const maxEntries = 1;\n      const currentTimestamp = Date.now();\n      const cache = await caches.open(cacheName);\n\n      const timestampModel = new CacheTimestampsModel(cacheName);\n      await timestampModel.setTimestamp('/first', currentTimestamp);\n      await cache.put(\n        `${location.origin}/first`,\n        new Response('Injected request'),\n      );\n\n      const expirationManager = new CacheExpiration(cacheName, {maxEntries});\n\n      await expirationManager.expireEntries();\n\n      // Add entry and ensure it is removed\n      await timestampModel.setTimestamp('/second', currentTimestamp - 1000);\n      await cache.put(\n        `${location.origin}/second`,\n        new Response('Injected request'),\n      );\n\n      await expirationManager.expireEntries();\n\n      // Check that IDB has /first\n      let timestamps = await db.getAll('cache-entries');\n\n      expect(timestamps).to.have.lengthOf(1);\n      expect(timestamps[0].url).to.equal(`${location.origin}/first`);\n      expect(timestamps[0].timestamp).to.equal(currentTimestamp);\n\n      // Check cache has /first\n      let cachedRequests = await cache.keys();\n      expect(cachedRequests.map((req) => req.url)).to.deep.equal([\n        `${location.origin}/first`,\n      ]);\n\n      await timestampModel.setTimestamp('/third', currentTimestamp + 1000);\n      await cache.put(\n        `${location.origin}/third`,\n        new Response('Injected request'),\n      );\n\n      await expirationManager.expireEntries();\n\n      // Check that IDB has /third\n      timestamps = await db.getAll('cache-entries');\n\n      expect(timestamps).to.have.lengthOf(1);\n      expect(timestamps[0].url).to.equal(`${location.origin}/third`);\n      expect(timestamps[0].timestamp).to.equal(currentTimestamp + 1000);\n\n      // Check cache has /third\n      cachedRequests = await cache.keys();\n      expect(cachedRequests.map((req) => req.url)).to.deep.equal([\n        `${location.origin}/third`,\n      ]);\n    });\n\n    it(`should pass matchOptions to the underlying cache.delete() call`, async function () {\n      const cacheName = 'matchOptions-test';\n      const maxEntries = 1;\n      const currentTimestamp = Date.now();\n      const timestampModel = new CacheTimestampsModel(cacheName);\n\n      const cache = await caches.open(cacheName);\n      const cacheDeleteSpy = sandbox.spy(cache, 'delete');\n      sandbox.stub(self.caches, 'open').resolves(cache);\n\n      await timestampModel.setTimestamp('/first', currentTimestamp);\n      await cache.put(\n        `${location.origin}/first`,\n        new Response('Injected request'),\n      );\n\n      const expirationManager = new CacheExpiration(cacheName, {\n        maxEntries,\n        matchOptions: {\n          ignoreVary: true,\n        },\n      });\n      await expirationManager.expireEntries();\n\n      // Add entry and ensure it is removed\n      await timestampModel.setTimestamp('/second', currentTimestamp - 1000);\n      await cache.put(\n        `${location.origin}/second`,\n        new Response('Injected request'),\n      );\n\n      await expirationManager.expireEntries();\n      expect(cacheDeleteSpy.args).to.eql([\n        [`${location.origin}/second`, {ignoreVary: true}],\n      ]);\n    });\n\n    it(`should queue up expireEntries calls`, async function () {\n      const expirationManager = new CacheExpiration('test', {maxEntries: 10});\n\n      sandbox.spy(expirationManager, 'expireEntries');\n      sandbox.spy(CacheTimestampsModel.prototype, 'expireEntries');\n\n      const expireDone = expirationManager.expireEntries();\n      expirationManager.expireEntries();\n      expirationManager.expireEntries();\n\n      expect(expirationManager.expireEntries.callCount).to.equal(3);\n      expect(CacheTimestampsModel.prototype.expireEntries.callCount).to.equal(\n        1,\n      );\n\n      await expireDone;\n\n      expect(expirationManager.expireEntries.callCount).to.equal(4);\n      expect(CacheTimestampsModel.prototype.expireEntries.callCount).to.equal(\n        2,\n      );\n    });\n\n    it(`should expire multiple expired entries`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const cacheName = 'expire-and-delete';\n      const maxAgeSeconds = 10;\n      const currentTimestamp = Date.now();\n      const cache = await caches.open(cacheName);\n\n      const timestampModel = new CacheTimestampsModel(cacheName);\n      await timestampModel.setTimestamp('/1', currentTimestamp);\n      await timestampModel.setTimestamp('/2', currentTimestamp);\n      await cache.put(`${location.origin}/1`, new Response('Injected request'));\n      await cache.put(`${location.origin}/2`, new Response('Injected request'));\n\n      const expirationManager = new CacheExpiration(cacheName, {maxAgeSeconds});\n\n      await expirationManager.expireEntries();\n\n      clock.tick(maxAgeSeconds * 1000 + 1);\n\n      // The plus one is to ensure it expires\n      await expirationManager.expireEntries();\n\n      // Check that IDB is empty\n      const timestamps = await db.getAll('cache-entries');\n      expect(timestamps).to.deep.equal([]);\n\n      // Check cache is empty\n      const cachedRequests = await cache.keys();\n      expect(cachedRequests).to.deep.equal([]);\n    });\n  });\n\n  describe(`updateTimestamp()`, function () {\n    it(`should update the timestamp for a url`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const cacheName = 'update-timestamp';\n      const maxAgeSeconds = 10;\n      const currentTimestamp = Date.now();\n      const timestampModel = new CacheTimestampsModel(cacheName);\n      await timestampModel.setTimestamp('/', currentTimestamp);\n\n      clock.tick(1000);\n\n      const expirationManager = new CacheExpiration(cacheName, {maxAgeSeconds});\n      await expirationManager.updateTimestamp('/');\n\n      const timestamps = await db.getAll('cache-entries');\n\n      expect(timestamps).to.have.lengthOf(1);\n      expect(timestamps[0].url).to.equal(`${location.origin}/`);\n      expect(timestamps[0].timestamp).to.equal(currentTimestamp + 1000);\n    });\n  });\n\n  describe(`isURLExpired()`, function () {\n    it(`should throw when called without maxAgeSeconds`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      const expirationManager = new CacheExpiration('test-cache', {\n        maxEntries: 1,\n      });\n      return expectError(() => {\n        return expirationManager.isURLExpired();\n      }, 'expired-test-without-max-age');\n    });\n\n    it(`should return boolean`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const cacheName = 'update-timestamp';\n      const maxAgeSeconds = 10;\n      const currentTimestamp = Date.now();\n      const timestampModel = new CacheTimestampsModel(cacheName);\n      await timestampModel.setTimestamp('/', currentTimestamp);\n\n      const expirationManager = new CacheExpiration(cacheName, {maxAgeSeconds});\n      let isExpired = await expirationManager.isURLExpired('/');\n      expect(isExpired).to.equal(false);\n\n      clock.tick(maxAgeSeconds * 1000 + 1);\n\n      isExpired = await expirationManager.isURLExpired('/');\n      expect(isExpired).to.equal(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-expiration/sw/test-CacheTimestampsModel.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheTimestampsModel} from 'workbox-expiration/models/CacheTimestampsModel.mjs';\nimport {openDB} from 'idb';\n\ndescribe(`CacheTimestampsModel`, function () {\n  const sandbox = sinon.createSandbox();\n  let db = null;\n\n  beforeEach(async function () {\n    sandbox.restore();\n    db = await openDB('workbox-expiration', 1, {\n      upgrade: CacheTimestampsModel.prototype._upgradeDb,\n    });\n    await db.clear('cache-entries');\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  const populateDb = async () => {\n    await db.add('cache-entries', {\n      id: `cache-one|${location.origin}/1`,\n      url: `${location.origin}/1`,\n      cacheName: 'cache-one',\n      timestamp: 10,\n    });\n    await db.add('cache-entries', {\n      id: `cache-one|${location.origin}/2`,\n      url: `${location.origin}/2`,\n      cacheName: 'cache-one',\n      timestamp: 9,\n    });\n    await db.add('cache-entries', {\n      id: `cache-one|${location.origin}/3`,\n      url: `${location.origin}/3`,\n      cacheName: 'cache-one',\n      timestamp: 8,\n    });\n    await db.add('cache-entries', {\n      id: `cache-one|${location.origin}/4`,\n      url: `${location.origin}/4`,\n      cacheName: 'cache-one',\n      timestamp: 7,\n    });\n    await db.add('cache-entries', {\n      id: `cache-one|${location.origin}/5`,\n      url: `${location.origin}/5`,\n      cacheName: 'cache-one',\n      timestamp: 6,\n    });\n    await db.add('cache-entries', {\n      id: `cache-two|${location.origin}/1`,\n      url: `${location.origin}/1`,\n      cacheName: 'cache-two',\n      timestamp: 10,\n    });\n    await db.add('cache-entries', {\n      id: `cache-two|${location.origin}/2`,\n      url: `${location.origin}/2`,\n      cacheName: 'cache-two',\n      timestamp: 9,\n    });\n    await db.add('cache-entries', {\n      id: `cache-two|${location.origin}/3`,\n      url: `${location.origin}/3`,\n      cacheName: 'cache-two',\n      timestamp: 8,\n    });\n    await db.add('cache-entries', {\n      id: `cache-three|${location.origin}/4`,\n      url: `${location.origin}/4`,\n      cacheName: 'cache-three',\n      timestamp: 7,\n    });\n    await db.add('cache-entries', {\n      id: `cache-three|${location.origin}/5`,\n      url: `${location.origin}/5`,\n      cacheName: 'cache-three',\n      timestamp: 6,\n    });\n\n    return db;\n  };\n\n  describe(`constructor`, function () {\n    it(`should constructor with a cacheName`, function () {\n      new CacheTimestampsModel('test-cache');\n    });\n\n    // TODO Test bad input\n  });\n\n  describe(`expireEntries()`, function () {\n    it(`should remove and return entries with timestamps below minTimestamp`, async function () {\n      await populateDb();\n\n      const model1 = new CacheTimestampsModel('cache-one');\n      const removedEntries1 = await model1.expireEntries(8);\n      expect(removedEntries1).to.deep.equal([\n        `${location.origin}/4`,\n        `${location.origin}/5`,\n      ]);\n      expect(await db.count('cache-entries')).to.equal(8);\n\n      const model2 = new CacheTimestampsModel('cache-two');\n      const removedEntries2 = await model2.expireEntries(9);\n      expect(removedEntries2).to.deep.equal([`${location.origin}/3`]);\n      expect(await db.count('cache-entries')).to.equal(7);\n    });\n\n    it(`should remove and return the oldest entries greater than maxCount`, async function () {\n      await populateDb();\n\n      const model1 = new CacheTimestampsModel('cache-one');\n      const removedEntries1 = await model1.expireEntries(null, 2);\n      expect(removedEntries1).to.deep.equal([\n        `${location.origin}/3`,\n        `${location.origin}/4`,\n        `${location.origin}/5`,\n      ]);\n      expect(await db.count('cache-entries')).to.equal(7);\n\n      const model2 = new CacheTimestampsModel('cache-two');\n      const removedEntries2 = await model2.expireEntries(null, 1);\n      expect(removedEntries2).to.deep.equal([\n        `${location.origin}/2`,\n        `${location.origin}/3`,\n      ]);\n      expect(await db.count('cache-entries')).to.equal(5);\n    });\n\n    it(`should work with minTimestamp and maxCount`, async function () {\n      await populateDb();\n\n      const model1 = new CacheTimestampsModel('cache-one');\n\n      // This example tests minTimestamp being more restrictive.\n      const removedEntries1 = await model1.expireEntries(9, 4);\n      expect(removedEntries1).to.deep.equal([\n        `${location.origin}/3`,\n        `${location.origin}/4`,\n        `${location.origin}/5`,\n      ]);\n      expect(await db.count('cache-entries')).to.equal(7);\n\n      const model2 = new CacheTimestampsModel('cache-two');\n\n      // This example tests maxCount being more restrictive.\n      const removedEntries2 = await model2.expireEntries(5, 2);\n      expect(removedEntries2).to.deep.equal([`${location.origin}/3`]);\n      expect(await db.count('cache-entries')).to.equal(6);\n    });\n\n    it(`should return an empty array if nothing matches`, async function () {\n      await populateDb();\n\n      const model = new CacheTimestampsModel('cache-one');\n\n      // This example tests minTimestamp being more restrictive.\n      const removedEntries = await model.expireEntries(5, 5);\n      expect(removedEntries).to.deep.equal([]);\n      expect(await db.count('cache-entries')).to.equal(10);\n    });\n  });\n\n  describe(`setTimestamp`, async function () {\n    it(`should put entries in the database`, async function () {\n      const model1 = new CacheTimestampsModel('cache-one');\n      const model2 = new CacheTimestampsModel('cache-two');\n\n      await model1.setTimestamp('/1', 100);\n      await model1.setTimestamp('/2', 200);\n      await model2.setTimestamp('/1', 300);\n\n      expect(await db.getAll('cache-entries')).to.deep.equal([\n        {\n          id: `cache-one|${location.origin}/1`,\n          url: `${location.origin}/1`,\n          cacheName: 'cache-one',\n          timestamp: 100,\n        },\n        {\n          id: `cache-one|${location.origin}/2`,\n          url: `${location.origin}/2`,\n          cacheName: 'cache-one',\n          timestamp: 200,\n        },\n        {\n          id: `cache-two|${location.origin}/1`,\n          url: `${location.origin}/1`,\n          cacheName: 'cache-two',\n          timestamp: 300,\n        },\n      ]);\n    });\n  });\n\n  describe(`getTimestamp`, async function () {\n    it(`should get an entry from the database by 'url'`, async function () {\n      await populateDb();\n\n      const model1 = new CacheTimestampsModel('cache-one');\n      const model2 = new CacheTimestampsModel('cache-two');\n      const model3 = new CacheTimestampsModel('cache-three');\n\n      expect(await model1.getTimestamp('/2')).to.equal(9);\n      expect(await model2.getTimestamp('/1')).to.equal(10);\n      expect(await model3.getTimestamp('/5')).to.equal(6);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-expiration/sw/test-ExpirationPlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {ExpirationPlugin} from 'workbox-expiration/ExpirationPlugin.mjs';\nimport {CacheExpiration} from 'workbox-expiration/CacheExpiration.mjs';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {executeQuotaErrorCallbacks} from 'workbox-core/_private/executeQuotaErrorCallbacks.mjs';\n\ndescribe(`ExpirationPlugin`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should throw for no config`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new ExpirationPlugin();\n      }, 'max-entries-or-age-required');\n    });\n\n    it(`should throw for non-number maxEntries`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new ExpirationPlugin({\n          maxEntries: 'Hi',\n        });\n      }, 'incorrect-type');\n    });\n\n    it(`should throw for non-number maxAgeSeconds`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(() => {\n        new ExpirationPlugin({\n          maxAgeSeconds: 'Hi',\n        });\n      }, 'incorrect-type');\n    });\n\n    it(`should construct with just maxAgeSeconds`, function () {\n      const plugin = new ExpirationPlugin({\n        maxAgeSeconds: 10,\n      });\n      expect(plugin._maxAgeSeconds).to.equal(10);\n    });\n\n    it(`should construct with just maxEntries`, function () {\n      const plugin = new ExpirationPlugin({\n        maxEntries: 10,\n      });\n      expect(plugin._config.maxEntries).to.equal(10);\n    });\n\n    it(`should register a quota error callback when purgeOnQuotaError is true`, async function () {\n      const plugin = new ExpirationPlugin({\n        maxEntries: 10,\n        purgeOnQuotaError: true,\n      });\n      plugin.deleteCacheAndMetadata = sandbox.stub();\n\n      await executeQuotaErrorCallbacks();\n\n      expect(plugin.deleteCacheAndMetadata.calledOnce).to.be.true;\n    });\n\n    it(`should not register a quota error callback when purgeOnQuotaError is false`, async function () {\n      const plugin = new ExpirationPlugin({\n        maxEntries: 10,\n        purgeOnQuotaError: false,\n      });\n      plugin.deleteCacheAndMetadata = sandbox.stub();\n\n      await executeQuotaErrorCallbacks();\n\n      expect(plugin.deleteCacheAndMetadata.called).to.be.false;\n    });\n\n    it(`should pass matchOptions through to the CacheExpiration instance`, async function () {\n      const plugin = new ExpirationPlugin({\n        maxEntries: 10,\n        matchOptions: {\n          ignoreVary: true,\n        },\n      });\n\n      const cacheExpiration = plugin._getCacheExpiration('test-matchOptions');\n      expect(cacheExpiration._matchOptions).to.eql({ignoreVary: true});\n    });\n  });\n\n  describe(`cachedResponseWillBeUsed()`, function () {\n    it(`should expose a cachedResponseWillBeUsed() method`, function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n      expect(plugin).to.respondTo('cachedResponseWillBeUsed');\n    });\n\n    it(`should return cachedResponse when cachedResponseWillBeUsed() is called and Responses Data header it valid`, async function () {\n      // Just to ensure no timing flakiness in test.\n      sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const dateString = new Date().toUTCString();\n      const cachedResponse = new Response('', {headers: {date: dateString}});\n\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n\n      const expirationManager = plugin._getCacheExpiration('test-cache');\n      sandbox.spy(expirationManager, 'expireEntries');\n\n      expect(\n        await plugin.cachedResponseWillBeUsed({\n          request: new Request('/'),\n          cacheName: 'test-cache',\n          cachedResponse,\n        }),\n      ).to.eql(cachedResponse);\n      expect(expirationManager.expireEntries.callCount).to.equal(1);\n    });\n\n    it(`should return null when cachedResponseWillBeUsed() is called and Responses Date header is too old`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['Date'],\n      });\n\n      const dateString = new Date().toUTCString();\n      const cachedResponse = new Response('', {headers: {date: dateString}});\n\n      // Clock past the expiration of the Data header\n      clock.tick(1000 + 1);\n\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n\n      const expirationManager = plugin._getCacheExpiration('test-cache');\n      sandbox.spy(expirationManager, 'expireEntries');\n\n      expect(\n        await plugin.cachedResponseWillBeUsed({\n          request: new Request('/'),\n          cacheName: 'test-cache',\n          cachedResponse,\n        }),\n      ).to.eql(null);\n      expect(expirationManager.expireEntries.callCount).to.equal(1);\n    });\n\n    it(`should handle a null cachedResponse`, async function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n\n      const expirationManager = plugin._getCacheExpiration('test-cache');\n      sandbox.spy(expirationManager, 'expireEntries');\n\n      expect(\n        await plugin.cachedResponseWillBeUsed({\n          cacheName: 'test-cache',\n          cachedResponse: null,\n        }),\n      ).to.eql(null);\n    });\n\n    it(`should update the timestamp for the request URL`, async function () {\n      const plugin = new ExpirationPlugin({maxEntries: 10});\n\n      const expirationManager = plugin._getCacheExpiration('test-cache');\n      sandbox.spy(expirationManager, 'updateTimestamp');\n\n      await plugin.cachedResponseWillBeUsed({\n        request: new Request('/one'),\n        cacheName: 'test-cache',\n        cachedResponse: new Response(''),\n      });\n\n      expect(expirationManager.updateTimestamp.callCount).to.equal(1);\n      expect(expirationManager.updateTimestamp.args[0][0]).to.equal(\n        `${location.origin}/one`,\n      );\n    });\n  });\n\n  describe(`_isResponseDateFresh()`, function () {\n    it(`should return true when maxAgeSeconds is not set`, function () {\n      const plugin = new ExpirationPlugin({maxEntries: 1});\n      const isFresh = plugin._isResponseDateFresh(new Response('Hi'));\n      expect(isFresh).to.equal(true);\n    });\n\n    it(`should return true when there is no Date header`, function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n      const isFresh = plugin._isResponseDateFresh(\n        new Response('Hi', {\n          // TODO: Remove this when https://github.com/pinterest/service-workers/issues/72\n          // is fixed.\n          headers: {},\n        }),\n      );\n      expect(isFresh).to.equal(true);\n    });\n\n    it(`should return true when the Date header is invalid`, function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n      const isFresh = plugin._isResponseDateFresh(\n        new Response('Hi', {\n          headers: {date: 'invalid header'},\n        }),\n      );\n      expect(isFresh).to.equal(true);\n    });\n  });\n\n  describe(`cacheDidUpdate()`, function () {\n    it(`should expose a cacheDidUpdate() method`, function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n      expect(plugin).to.respondTo('cacheDidUpdate');\n    });\n\n    it(`should update timestamps and expire entries`, async function () {\n      const cacheName = 'test-cache';\n      const url = new URL('/test', self.location).toString();\n      const request = new Request(url);\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 10});\n\n      sandbox.spy(CacheExpiration.prototype, 'updateTimestamp');\n      sandbox.spy(CacheExpiration.prototype, 'expireEntries');\n\n      await plugin.cacheDidUpdate({cacheName, request});\n\n      expect(CacheExpiration.prototype.updateTimestamp.callCount).to.equal(1);\n      expect(CacheExpiration.prototype.updateTimestamp.args[0][0]).to.equal(\n        url,\n      );\n      expect(CacheExpiration.prototype.expireEntries.callCount).to.equal(1);\n    });\n  });\n\n  describe(`_getCacheExpiration()`, function () {\n    it(`should reject when called with the default runtime cache name`, async function () {\n      const plugin = new ExpirationPlugin({maxAgeSeconds: 1});\n      await expectError(\n        () => plugin._getCacheExpiration(cacheNames.getRuntimeName()),\n        'expire-custom-caches-only',\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-google-analytics/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst qs = require('qs');\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst waitUntil = require('../../../infra/testing/wait-until');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-google-analytics]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-google-analytics/sw/');\n  });\n});\n\ndescribe(`[workbox-google-analytics] initialize`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-google-analytics/static/basic-example/`;\n  const swURL = `${testingURL}sw.js`;\n\n  /**\n   * Sends a mesage to the service worker via postMessage and invokes the\n   * `done()` callback when the service worker responds, with any data value\n   * passed to the event.\n   *\n   * @param {Object} data An object to send to the service worker.\n   * @param {Function} done The callback automatically passed via webdriver's\n   *     `executeAsyncScript()` method.\n   */\n  const messageSW = (data, done) => {\n    const messageChannel = new MessageChannel();\n    messageChannel.port1.onmessage = (evt) => done(evt.data);\n    navigator.serviceWorker.controller.postMessage(data, [\n      messageChannel.port2,\n    ]);\n  };\n\n  before(async function () {\n    // Load the page and wait for the first service worker to activate.\n    await webdriver.get(testingURL);\n\n    await activateAndControlSW(swURL);\n  });\n\n  beforeEach(async function () {\n    // Reset the spied requests array.\n    await webdriver.executeAsyncScript(messageSW, {\n      action: 'clear-spied-requests',\n    });\n  });\n\n  it(`should load a page with service worker`, async function () {\n    const err = await webdriver.executeAsyncScript((done) => {\n      fetch('https://www.google-analytics.com/analytics.js', {\n        mode: 'no-cors',\n      }).then(\n        () => done(),\n        (err) => done(err.message),\n      );\n    });\n\n    expect(err).to.not.exist;\n  });\n\n  it(`replay failed Google Analytics hits`, async function () {\n    // Skip this test in browsers that don't support background sync.\n    // TODO(philipwalton): figure out a way to work around this.\n    const browserSupportsSync = await webdriver.executeScript(() => {\n      return 'SyncManager' in window;\n    });\n    if (!browserSupportsSync) this.skip();\n\n    // Send a hit while online to ensure regular requests work.\n    await webdriver.executeAsyncScript((done) => {\n      window.gtag('event', 'beacon', {\n        transport_type: 'beacon',\n        event_callback: () => done(),\n      });\n    });\n\n    let requests = await webdriver.executeAsyncScript(messageSW, {\n      action: 'get-spied-requests',\n    });\n    expect(requests).to.have.lengthOf(1);\n\n    // Reset the spied requests array.\n    await webdriver.executeAsyncScript(messageSW, {\n      action: 'clear-spied-requests',\n    });\n\n    // Switch the service worker into \"offline\" mode.\n    await webdriver.executeAsyncScript(messageSW, {\n      action: 'simulate-offline',\n      value: true,\n    });\n\n    await webdriver.executeAsyncScript((done) => {\n      window.gtag('event', 'beacon', {\n        transport_type: 'beacon',\n        event_label: Date.now(),\n        event_callback: () => {\n          // See https://github.com/GoogleChrome/workbox/issues/2168\n          setTimeout(done, 50);\n        },\n      });\n    });\n\n    await webdriver.executeAsyncScript((done) => {\n      window.gtag('event', 'pixel', {\n        transport_type: 'image',\n        event_label: Date.now(),\n        event_callback: () => {\n          // See https://github.com/GoogleChrome/workbox/issues/2168\n          setTimeout(done, 50);\n        },\n      });\n    });\n\n    // This request should not match GA routes, so it shouldn't be replayed.\n    await webdriver.executeAsyncScript((done) => {\n      fetch('https://httpbin.org/get').then(() => done());\n    });\n\n    // Get all spied requests and ensure there haven't been any (since we're\n    // offline).\n    requests = await webdriver.executeAsyncScript(messageSW, {\n      action: 'get-spied-requests',\n    });\n    expect(requests).to.have.lengthOf(0);\n\n    // Switch the service worker into \"online\" mode.\n    await webdriver.executeAsyncScript(messageSW, {\n      action: 'simulate-offline',\n      value: false,\n    });\n\n    // Dispatch a sync event.\n    await webdriver.executeAsyncScript(messageSW, {\n      action: 'dispatch-sync-event',\n    });\n\n    // Wait until all expected requests have replayed.\n    await waitUntil(\n      async () => {\n        requests = await webdriver.executeAsyncScript(messageSW, {\n          action: 'get-spied-requests',\n        });\n        return requests.length === 2;\n      },\n      25,\n      200,\n    );\n\n    // Parse the request bodies to set the params as an object and convert the\n    // qt param to a number.\n    for (const request of requests) {\n      request.params = qs.parse(request.body);\n      request.params.qt = Number(request.params.qt);\n      request.originalTime = request.timestamp - request.params.qt;\n    }\n\n    expect(requests[0].params.ea).to.equal('beacon');\n    expect(requests[1].params.ea).to.equal('pixel');\n\n    // Ensure the hit's qt params were present and greater than 0,\n    // and ensure those values reflect the original order of the hits.\n    expect(requests[0].params.qt > 0).to.be.true;\n    expect(requests[1].params.qt > 0).to.be.true;\n    expect(requests[0].originalTime < requests[1].originalTime).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-google-analytics/static/basic-example/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>Workbox Google Analytics</title>\n    <meta charset=\"utf-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <body>\n    <button id=\"send-ga-pixel\">Send GA pixel (GET)</button>\n    <button id=\"send-ga-beacon\">Send GA beacon (POST)</button>\n    <button id=\"send-non-ga-request\">Send non-GA request</button>\n    <button id=\"dispatch-sync-event\">Simulate sync event</button>\n    <button id=\"clear-spied-requests\">Clear requests</button>\n    <button id=\"log-spied-requests\">Log requests</button>\n    <label>\n      <input type=\"checkbox\" id=\"simulate-offline\" />\n      Simulate offline\n    </label>\n    <p><strong>Note:</strong> you need to manually register sw.js</p>\n\n    <script\n      async\n      src=\"https://www.googletagmanager.com/gtag/js?id=UA-12345-1\"\n    ></script>\n    <script>\n      window.dataLayer = window.dataLayer || [];\n      function gtag() {\n        dataLayer.push(arguments);\n      }\n      gtag('js', new Date());\n      gtag('config', 'UA-12345-1', {send_page_view: false});\n\n      const messageSW = (data) => {\n        return new Promise((resolve) => {\n          var messageChannel = new MessageChannel();\n          messageChannel.port1.onmessage = (evt) => resolve(evt.data);\n          navigator.serviceWorker.controller.postMessage(data, [\n            messageChannel.port2,\n          ]);\n        });\n      };\n\n      document.getElementById('send-ga-pixel').onclick = () => {\n        gtag('event', 'pixel', {\n          transport_type: 'image',\n        });\n      };\n\n      document.getElementById('send-ga-beacon').onclick = () => {\n        gtag('event', 'beacon', {\n          transport_type: 'beacon',\n        });\n      };\n\n      document.getElementById('send-non-ga-request').onclick = () => {\n        fetch('https://httpbin.org/get');\n      };\n\n      document.getElementById('dispatch-sync-event').onclick = () => {\n        messageSW({action: 'dispatch-sync-event'});\n      };\n\n      document.getElementById('simulate-offline').onclick = (evt) => {\n        messageSW({\n          action: 'simulate-offline',\n          value: evt.target.checked,\n        });\n      };\n\n      document.getElementById('clear-spied-requests').onclick = (evt) => {\n        messageSW({action: 'clear-spied-requests'});\n      };\n\n      document.getElementById('log-spied-requests').onclick = async (evt) => {\n        const requests = await messageSW({action: 'get-spied-requests'});\n        console.log(requests);\n      };\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-google-analytics/static/basic-example/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nconst sleep = async (ms) => {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\n// Spy on .fetch() calls from inside the service worker.\n// If `simulateOffline` is set to true, throw a network error.\nlet simulateOffline = false;\nlet spiedRequests = [];\nconst originalFetch = self.fetch;\nself.fetch = async (...args) => {\n  if (simulateOffline) {\n    // Simulate network latency, so the sync event doesn't just fire\n    // in an endless loop.\n    await sleep(100);\n    throw new Error('Simulated network error');\n  }\n  const clone =\n    args[0] instanceof Request ? args[0].clone() : new Request(args[0]);\n\n  spiedRequests.push({\n    url: clone.url,\n    timestamp: Date.now(),\n    body: await clone.text(),\n  });\n  return originalFetch(...args);\n};\n\n// Add a message listener for communication between the client and SW.\nself.addEventListener('message', (evt) => {\n  switch (evt.data.action) {\n    case 'simulate-offline':\n      simulateOffline = evt.data.value;\n      evt.ports[0].postMessage(null);\n      break;\n    case 'clear-spied-requests':\n      spiedRequests = [];\n      evt.ports[0].postMessage(null);\n      break;\n    case 'get-spied-requests':\n      evt.ports[0].postMessage(spiedRequests);\n      break;\n    case 'dispatch-sync-event': {\n      // Override `.waitUntil` so we can signal when the sync is done.\n      const originalSyncEventWaitUntil = SyncEvent.prototype.waitUntil;\n      SyncEvent.prototype.waitUntil = (promise) => {\n        return promise.then(() => evt.ports[0].postMessage(null));\n      };\n\n      self.dispatchEvent(\n        new SyncEvent('sync', {\n          tag: 'workbox-background-sync:workbox-google-analytics',\n        }),\n      );\n      SyncEvent.prototype.waitUntil = originalSyncEventWaitUntil;\n      break;\n    }\n  }\n});\n\nworkbox.googleAnalytics.initialize();\n\nself.addEventListener('install', (evt) => evt.waitUntil(self.skipWaiting()));\nself.addEventListener('activate', (evt) => evt.waitUntil(self.clients.claim()));\n"
  },
  {
    "path": "test/workbox-google-analytics/sw/test-initialize.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Queue} from 'workbox-background-sync/Queue.mjs';\nimport {QueueDb} from 'workbox-background-sync/lib/QueueDb.mjs';\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {openDB} from 'idb';\nimport {initialize} from 'workbox-google-analytics/initialize.mjs';\nimport {\n  GOOGLE_ANALYTICS_HOST,\n  GTM_HOST,\n  ANALYTICS_JS_PATH,\n  GTAG_JS_PATH,\n  GTM_JS_PATH,\n} from 'workbox-google-analytics/utils/constants.mjs';\nimport {NetworkFirst} from 'workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly} from 'workbox-strategies/NetworkOnly.mjs';\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nconst PAYLOAD = 'v=1&t=pageview&tid=UA-12345-1&cid=1&dp=%2F';\n\ndescribe(`initialize`, function () {\n  const sandbox = sinon.createSandbox();\n  let db = null;\n\n  beforeEach(async function () {\n    Queue._queueNames.clear();\n    db = await openDB('workbox-background-sync', 3, {\n      upgrade: QueueDb.prototype._upgradeDb,\n    });\n    sandbox.restore();\n    await db.clear('requests');\n\n    const usedCaches = await caches.keys();\n    await Promise.all(usedCaches.map((cacheName) => caches.delete(cacheName)));\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    // Don't actually register for a sync event in any test, as it could\n    // make them non-deterministic.\n    if ('sync' in registration) {\n      sandbox.stub(registration.sync, 'register');\n    }\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n\n    sandbox.restore();\n  });\n\n  it(`should register a handler to cache the analytics.js script`, async function () {\n    sandbox.spy(NetworkFirst.prototype, 'handle');\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}${ANALYTICS_JS_PATH}`,\n          {\n            mode: 'no-cors',\n          },\n        ),\n      }),\n    );\n\n    expect(NetworkFirst.prototype.handle.calledOnce).to.be.true;\n  });\n\n  it(`should register a handler to cache the gtag.js script`, async function () {\n    sandbox.spy(NetworkFirst.prototype, 'handle');\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GTM_HOST}${GTAG_JS_PATH}?id=UA-XXXXX-Y`,\n          {\n            mode: 'no-cors',\n          },\n        ),\n      }),\n    );\n\n    expect(NetworkFirst.prototype.handle.calledOnce).to.be.true;\n  });\n\n  it(`should register a handler to cache the gtm.js script`, async function () {\n    sandbox.spy(NetworkFirst.prototype, 'handle');\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GTM_HOST}${GTM_JS_PATH}?id=GTM-XXXX`, {\n          mode: 'no-cors',\n        }),\n      }),\n    );\n\n    expect(NetworkFirst.prototype.handle.calledOnce).to.be.true;\n  });\n\n  it(`should accept an optional cache name`, async function () {\n    initialize({cacheName: 'foobar'});\n\n    const analyticsJsRequest = new Request(\n      `https://${GOOGLE_ANALYTICS_HOST}${ANALYTICS_JS_PATH}`,\n      {\n        mode: 'no-cors',\n      },\n    );\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: analyticsJsRequest,\n      }),\n    );\n\n    const usedCaches = await caches.keys();\n    expect(usedCaches).to.have.lengthOf(1);\n    expect(usedCaches[0]).to.equal('foobar');\n\n    const cache = await caches.open('foobar');\n    const cachedResponse = await cache.match(analyticsJsRequest);\n\n    expect(cachedResponse).to.be.instanceOf(Response);\n  });\n\n  it(`should use the default cache name when not specified`, async function () {\n    initialize();\n\n    const analyticsJsRequest = new Request(\n      `https://${GOOGLE_ANALYTICS_HOST}${ANALYTICS_JS_PATH}`,\n      {\n        mode: 'no-cors',\n      },\n    );\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: analyticsJsRequest,\n      }),\n    );\n\n    const defaultCacheName = cacheNames.getGoogleAnalyticsName();\n    const usedCaches = await caches.keys();\n    expect(usedCaches).to.have.lengthOf(1);\n    expect(usedCaches[0]).to.equal(defaultCacheName);\n\n    const cache = await caches.open(defaultCacheName);\n    const cachedResponse = await cache.match(analyticsJsRequest);\n    expect(cachedResponse).to.be.instanceOf(Response);\n  });\n\n  it(`should register GET/POST routes for collect endpoints`, async function () {\n    // Stub out fetch(), since this test is about routing, and doesn't need to\n    // contact the production Google Analytics endpoints.\n    sandbox.stub(self, 'fetch').callsFake(() => new Response('ignored'));\n\n    sandbox.spy(NetworkOnly.prototype, 'handle');\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    expect(NetworkOnly.prototype.handle.callCount).to.equal(1);\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    expect(NetworkOnly.prototype.handle.callCount).to.equal(2);\n\n    // Test the experimental /r/collect endpoint\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/r/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    expect(NetworkOnly.prototype.handle.callCount).to.equal(3);\n\n    // Test the experimental /r/collect endpoint\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/r/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    expect(NetworkOnly.prototype.handle.callCount).to.equal(4);\n  });\n\n  it(`should not alter successful hit payloads`, async function () {\n    // Stub out fetch(), since this test is about routing, and doesn't need to\n    // contact the production Google Analytics endpoints.\n    sandbox.stub(self, 'fetch').callsFake(() => new Response('ignored'));\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    expect(self.fetch.calledOnce).to.be.true;\n    expect(self.fetch.firstCall.args[0].url).to.equal(\n      `https://` + `${GOOGLE_ANALYTICS_HOST}/collect?${PAYLOAD}`,\n    );\n\n    const request = new Request(`https://${GOOGLE_ANALYTICS_HOST}/collect`, {\n      method: 'POST',\n      body: PAYLOAD,\n    });\n    await dispatchAndWaitUntilDone(new FetchEvent('fetch', {request}));\n\n    expect(self.fetch.calledTwice).to.be.true;\n\n    // We can't compare payload bodies after the fetch has run, but if the\n    // fetch succeeds and sends the request, we know the body wasn't altered.\n    expect(self.fetch.secondCall.args[0]).to.equal(request);\n  });\n\n  it(`should not alter hit paths`, async function () {\n    // Stub out fetch(), since this test is about routing, and doesn't need to\n    // contact the production Google Analytics endpoints.\n    sandbox.stub(self, 'fetch').callsFake(() => new Response('ignored'));\n\n    initialize();\n\n    // Test the /r/collect endpoint\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/r/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    expect(self.fetch.calledOnce).to.be.true;\n    expect(self.fetch.firstCall.args[0].url).to.equal(\n      `https://` + `${GOOGLE_ANALYTICS_HOST}/r/collect?${PAYLOAD}`,\n    );\n\n    // Test the /r/collect endpoint\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/r/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    expect(self.fetch.calledTwice).to.be.true;\n    expect(self.fetch.secondCall.args[0].url).to.equal(\n      `https://` + `${GOOGLE_ANALYTICS_HOST}/r/collect`,\n    );\n  });\n\n  it(`should add failed hits to a background sync queue`, async function () {\n    sandbox.stub(self, 'fetch').rejects();\n    sandbox.spy(Queue.prototype, 'pushRequest');\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    const [call1Args, call2Args] = Queue.prototype.pushRequest.args;\n    expect(call1Args[0].request.url).to.equal(\n      `https://` + `${GOOGLE_ANALYTICS_HOST}/collect?${PAYLOAD}`,\n    );\n    expect(call2Args[0].request.url).to.equal(\n      `https://` + `${GOOGLE_ANALYTICS_HOST}/collect`,\n    );\n  });\n\n  it(`should add the qt param to replayed hits`, async function () {\n    sandbox.stub(self, 'fetch').rejects();\n    const pushRequestSpy = sandbox.spy(Queue.prototype, 'pushRequest');\n    const clock = sandbox.useFakeTimers({toFake: ['Date']});\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    clock.tick(100);\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/r/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    self.fetch.restore();\n    sandbox.stub(self, 'fetch').resolves(new Response('', {status: 200}));\n\n    clock.tick(100);\n\n    // Manually trigger the `onSync` callback in both sync and non-sync\n    // supporting browsers.\n    if ('sync' in registration) {\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: `workbox-background-sync:workbox-google-analytics`,\n        }),\n      );\n    } else {\n      // Get the `this` context of the underlying Queue instance in order\n      // to manually replay it.\n      const queue = pushRequestSpy.thisValues[0];\n      await queue._onSync({queue});\n    }\n\n    expect(self.fetch.callCount).to.equal(2);\n\n    const replay1 = self.fetch.firstCall.args[0];\n    const replay2 = self.fetch.secondCall.args[0];\n    expect(replay1.url).to.equal(`https://${GOOGLE_ANALYTICS_HOST}/collect`);\n    expect(replay2.url).to.equal(`https://${GOOGLE_ANALYTICS_HOST}/r/collect`);\n\n    const replayParams1 = new URLSearchParams(await replay1.text());\n    const replayParams2 = new URLSearchParams(await replay2.text());\n    const payloadParams = new URLSearchParams(PAYLOAD);\n\n    expect(parseInt(replayParams1.get('qt'))).to.equal(200);\n    expect(parseInt(replayParams2.get('qt'))).to.equal(100);\n\n    for (const [key, value] of payloadParams.entries()) {\n      expect(replayParams1.get(key)).to.equal(value);\n      expect(replayParams2.get(key)).to.equal(value);\n    }\n  });\n\n  it(`should update an existing qt param`, async function () {\n    sandbox.stub(self, 'fetch').rejects();\n    const pushRequestSpy = sandbox.spy(Queue.prototype, 'pushRequest');\n    const clock = sandbox.useFakeTimers({toFake: ['Date']});\n\n    initialize();\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}&qt=1000`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    clock.tick(100);\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/r/collect`, {\n          method: 'POST',\n          body: `${PAYLOAD}&qt=3000`,\n        }),\n      }),\n    );\n\n    self.fetch.restore();\n    sandbox.stub(self, 'fetch').resolves(new Response('', {status: 200}));\n\n    clock.tick(100);\n\n    // Manually trigger the `onSync` callback in both sync and non-sync\n    // supporting browsers.\n    if ('sync' in registration) {\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: `workbox-background-sync:workbox-google-analytics`,\n        }),\n      );\n    } else {\n      // Get the `this` context of the underlying Queue instance in order\n      // to manually replay it.\n      const queue = pushRequestSpy.thisValues[0];\n      await queue._onSync({queue});\n    }\n\n    expect(self.fetch.callCount).to.equal(2);\n\n    const replay1 = self.fetch.firstCall.args[0];\n    const replay2 = self.fetch.secondCall.args[0];\n    const replayParams1 = new URLSearchParams(await replay1.text());\n    const replayParams2 = new URLSearchParams(await replay2.text());\n\n    expect(parseInt(replayParams1.get('qt'))).to.equal(1200);\n    expect(parseInt(replayParams2.get('qt'))).to.equal(3100);\n  });\n\n  it(`should add parameterOverrides to replayed hits`, async function () {\n    sandbox.stub(self, 'fetch').rejects();\n    const pushRequestSpy = sandbox.spy(Queue.prototype, 'pushRequest');\n\n    initialize({\n      parameterOverrides: {\n        cd1: 'replay',\n        cm1: 1,\n      },\n    });\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/collect`, {\n          method: 'POST',\n          body: PAYLOAD,\n        }),\n      }),\n    );\n\n    self.fetch.restore();\n    sandbox.stub(self, 'fetch').resolves(new Response('', {status: 200}));\n\n    // Manually trigger the `onSync` callback in both sync and non-sync\n    // supporting browsers.\n    if ('sync' in registration) {\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: `workbox-background-sync:workbox-google-analytics`,\n        }),\n      );\n    } else {\n      // Get the `this` context of the underlying Queue instance in order\n      // to manually replay it.\n      const queue = pushRequestSpy.thisValues[0];\n      await queue._onSync({queue});\n    }\n\n    expect(self.fetch.callCount).to.equal(2);\n\n    const replay1 = self.fetch.firstCall.args[0];\n    const replay2 = self.fetch.secondCall.args[0];\n\n    const replayParams1 = new URLSearchParams(await replay1.text());\n    const replayParams2 = new URLSearchParams(await replay2.text());\n\n    expect(replayParams1.get('qt')).to.be.ok;\n    expect(replayParams1.get('cd1')).to.equal('replay');\n    expect(replayParams1.get('cm1')).to.equal('1');\n\n    expect(replayParams2.get('qt')).to.be.ok;\n    expect(replayParams2.get('cd1')).to.equal('replay');\n    expect(replayParams2.get('cm1')).to.equal('1');\n  });\n\n  it(`should apply the hitFilter to replayed hits`, async function () {\n    sandbox.stub(self, 'fetch').rejects();\n    sandbox.spy(Queue.prototype, 'pushRequest');\n\n    initialize({\n      hitFilter: (params) => {\n        if (params.get('foo') === '1') {\n          params.set('foo', 'bar');\n        }\n      },\n    });\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(\n          `https://${GOOGLE_ANALYTICS_HOST}` + `/collect?${PAYLOAD}&foo=1`,\n          {\n            method: 'GET',\n          },\n        ),\n      }),\n    );\n\n    await dispatchAndWaitUntilDone(\n      new FetchEvent('fetch', {\n        request: new Request(`https://${GOOGLE_ANALYTICS_HOST}/collect`, {\n          method: 'POST',\n          body: PAYLOAD + '&foo=2',\n        }),\n      }),\n    );\n\n    self.fetch.restore();\n    sandbox.stub(self, 'fetch').resolves(new Response('', {status: 200}));\n\n    // Manually trigger the `onSync` callback in both sync and non-sync\n    // supporting browsers.\n    if ('sync' in registration) {\n      await dispatchAndWaitUntilDone(\n        new SyncEvent('sync', {\n          tag: `workbox-background-sync:workbox-google-analytics`,\n        }),\n      );\n    } else {\n      const queue = Queue.prototype.pushRequest.thisValues[0];\n      await queue._onSync({queue});\n    }\n\n    expect(self.fetch.callCount).to.equal(2);\n\n    const replay1 = self.fetch.firstCall.args[0];\n    const replay2 = self.fetch.secondCall.args[0];\n\n    const replayParams1 = new URLSearchParams(await replay1.text());\n    const replayParams2 = new URLSearchParams(await replay2.text());\n\n    expect(replayParams1.get('qt')).to.be.ok;\n    expect(replayParams1.get('foo')).to.equal('bar');\n\n    expect(replayParams2.get('qt')).to.be.ok;\n    expect(replayParams2.get('foo')).to.equal('2');\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/integration/test-disable.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\n\ndescribe(`navigationPreload.disable`, function () {\n  const staticPath = `/test/workbox-navigation-preload/static/`;\n  const baseURL = global.__workbox.server.getAddress() + staticPath;\n  const integrationURL = `${baseURL}integration.html`;\n  const integrationURLPath = `${staticPath}integration.html`;\n\n  let requestCounter;\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, integrationURL);\n    requestCounter = global.__workbox.server.startCountingRequests(\n      'Service-Worker-Navigation-Preload',\n    );\n  });\n  afterEach(function () {\n    global.__workbox.server.stopCountingRequests(requestCounter);\n  });\n\n  it(`should support disabling previously enabled navigation preload`, async function () {\n    await activateAndControlSW(`${baseURL}sw-default-header.js`);\n\n    const isEnabled = await runInSW('isNavigationPreloadSupported');\n\n    if (!isEnabled) {\n      // Just bail early if navigation preload isn't supported, since testing\n      // the disable flow isn't meaningful.\n      return this.skip();\n    }\n\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(0);\n\n    await global.__workbox.webdriver.get(integrationURL);\n\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(1);\n\n    // Active the new service worker that has navigation preload disabled.\n    await activateAndControlSW(`${baseURL}sw-disable.js`);\n\n    await global.__workbox.webdriver.get(integrationURL);\n\n    // With navigation preload now disabled, the synthetic response from the\n    // service worker should fulfill the navigation request, and the server\n    // won't get another HTTP request.\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(1);\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/integration/test-enable.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\n\ndescribe(`navigationPreload.enable`, function () {\n  const staticPath = `/test/workbox-navigation-preload/static/`;\n  const baseURL = global.__workbox.server.getAddress() + staticPath;\n  const integrationURL = `${baseURL}integration.html`;\n  const integrationURLPath = `${staticPath}integration.html`;\n\n  let requestCounter;\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, integrationURL);\n    requestCounter = global.__workbox.server.startCountingRequests(\n      'Service-Worker-Navigation-Preload',\n    );\n  });\n  afterEach(function () {\n    global.__workbox.server.stopCountingRequests(requestCounter);\n  });\n\n  it(`should make a network request if navigation preload is supported`, async function () {\n    await activateAndControlSW(`${baseURL}sw-default-header.js`);\n\n    const isEnabled = await runInSW('isNavigationPreloadSupported');\n\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(0);\n\n    await global.__workbox.webdriver.get(integrationURL);\n\n    // If navigation preload is enabled, there should be 1 request. Otherwise,\n    // no requests.\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(\n      isEnabled ? 1 : 0,\n    );\n\n    // Check to make sure that the correct header value was sent if navigation\n    // preload is enabled.\n    expect(requestCounter.getHeaderCount('true')).to.eql(isEnabled ? 1 : 0);\n  });\n\n  it(`should make a network request if navigation preload is supported, with a custom header value`, async function () {\n    await activateAndControlSW(`${baseURL}sw-custom-header.js`);\n\n    const isEnabled = await runInSW('isNavigationPreloadSupported');\n\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(0);\n\n    await global.__workbox.webdriver.get(integrationURL);\n\n    // If navigation preload is enabled, there should be 1 request. Otherwise,\n    // no requests.\n    expect(requestCounter.getURLCount(integrationURLPath)).to.eql(\n      isEnabled ? 1 : 0,\n    );\n\n    // Check to make sure that the correct header value was sent if navigation\n    // preload is enabled.\n    expect(requestCounter.getHeaderCount('custom-value')).to.eql(\n      isEnabled ? 1 : 0,\n    );\n\n    // There shouldn't be any requests with the default header value, 'true'.\n    expect(requestCounter.getHeaderCount('true')).to.eql(0);\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/integration/test-sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\ndescribe(`[workbox-navigation-preload]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-navigation-preload/sw/');\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/static/sw-custom-header.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\n// The header will be `Service-Worker-Navigation-Preload: custom-value`.\nworkbox.navigationPreload.enable('custom-value');\n\n// Once the service worker's taken control, it responds to navigations with\n// a synthetic response.\n// If Navigation Preload is enabled, that request bypasses the service worker.\nconst navigationRoute = new workbox.routing.NavigationRoute(\n  () => new Response('Generated by the service worker.'),\n);\nworkbox.routing.registerRoute(navigationRoute);\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-navigation-preload/static/sw-default-header.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\n// The header will be `Service-Worker-Navigation-Preload: true`.\nworkbox.navigationPreload.enable();\n\n// Once the service worker's taken control, it responds to navigations with\n// a synthetic response.\n// If Navigation Preload is enabled, that request bypasses the service worker.\nconst navigationRoute = new workbox.routing.NavigationRoute(\n  () => new Response('Generated by the service worker.'),\n);\nworkbox.routing.registerRoute(navigationRoute);\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-navigation-preload/static/sw-disable.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.navigationPreload.disable();\n\n// Once the service worker's taken control, it responds to navigations with\n// a synthetic response.\n// Since navigation preload is disabled, this response *should* be used.\nconst navigationRoute = new workbox.routing.NavigationRoute(\n  () => new Response('Generated by the service worker.'),\n);\nworkbox.routing.registerRoute(navigationRoute);\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-navigation-preload/sw/test-disable.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {disable} from 'workbox-navigation-preload/disable.mjs';\nimport {isSupported} from 'workbox-navigation-preload/isSupported.mjs';\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\ndescribe(`disable`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    if (isSupported()) {\n      // Don't actually disable navigation preload.\n      sandbox.stub(self.registration.navigationPreload, 'disable').resolves();\n    }\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  // This is needed because we're skipping the last test, which for some\n  // reasons seems to be skipping the afterEach hook:\n  // https://github.com/mochajs/mocha/pull/2571#issuecomment-477407091\n  after(function () {\n    sandbox.restore();\n  });\n\n  it(`should call addEventListener iff navigation preload is supported`, async function () {\n    disable();\n\n    if (isSupported()) {\n      expect(self.addEventListener.callCount).to.equal(1);\n      expect(self.addEventListener.args[0][0]).to.equal('activate');\n    } else {\n      expect(self.addEventListener.callCount).to.equal(0);\n    }\n  });\n\n  it(`should disable navigation preload if supported`, async function () {\n    if (!isSupported()) this.skip();\n\n    disable();\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    // This method is stubbed in the beforeEach hook.\n    expect(self.registration.navigationPreload.disable.callCount).to.equal(1);\n  });\n\n  it(`should log a confirmation message in development`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    sandbox.spy(logger, 'log');\n\n    disable();\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    expect(logger.log.callCount).to.equal(1);\n    if (isSupported()) {\n      expect(logger.log.args[0][0]).to.match(/disabled/);\n    } else {\n      expect(logger.log.args[0][0]).to.match(/not supported/);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/sw/test-enable.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {enable} from 'workbox-navigation-preload/enable.mjs';\nimport {isSupported} from 'workbox-navigation-preload/isSupported.mjs';\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\ndescribe(`enable`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    if (isSupported()) {\n      // Don't actually enable navigation preload.\n      sandbox.stub(self.registration.navigationPreload, 'enable').resolves();\n    }\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  // This is needed because we're skipping the last test, which for some\n  // reasons seems to be skipping the afterEach hook:\n  // https://github.com/mochajs/mocha/pull/2571#issuecomment-477407091\n  after(function () {\n    sandbox.restore();\n  });\n\n  it(`should call addEventListener iff navigation preload is supported`, async function () {\n    enable();\n\n    if (isSupported()) {\n      expect(self.addEventListener.callCount).to.equal(1);\n      expect(self.addEventListener.args[0][0]).to.equal('activate');\n    } else {\n      expect(self.addEventListener.callCount).to.equal(0);\n    }\n  });\n\n  it(`should enable navigation preload if supported`, async function () {\n    if (!isSupported()) this.skip();\n\n    enable();\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    // This method is stubbed in the beforeEach hook.\n    expect(self.registration.navigationPreload.enable.callCount).to.equal(1);\n  });\n\n  it(`should use a custom header value if specified`, async function () {\n    if (!isSupported()) this.skip();\n\n    sandbox.spy(self.registration.navigationPreload, 'setHeaderValue');\n\n    enable('custom-header');\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    expect(\n      self.registration.navigationPreload.setHeaderValue.callCount,\n    ).to.equal(1);\n    expect(\n      self.registration.navigationPreload.setHeaderValue.args[0][0],\n    ).to.equal('custom-header');\n  });\n\n  it(`should log a confirmation message in development`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    sandbox.spy(logger, 'log');\n\n    enable();\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    expect(logger.log.callCount).to.equal(1);\n    if (isSupported()) {\n      expect(logger.log.args[0][0]).to.match(/enabled/);\n    } else {\n      expect(logger.log.args[0][0]).to.match(/not supported/);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-navigation-preload/sw/test-isSupported.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {isSupported} from 'workbox-navigation-preload/isSupported.mjs';\n\ndescribe(`isSupported`, function () {\n  it(`should return true iff navigation preload is supported`, async function () {\n    if (self.registration.navigationPreload) {\n      expect(isSupported()).to.equal(true);\n    } else {\n      expect(isSupported()).to.equal(false);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/integration/test-cleanup-outdated-caches.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\n\ndescribe(`[workbox-precaching] cleanupOutdatedCaches()`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-precaching/static/cleanup-outdated-caches/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should clean up outdated precached after activation`, async function () {\n    // Add an item to an outdated cache.\n    const preActivateKeys = await global.__workbox.webdriver.executeAsyncScript(\n      (cb) => {\n        // Opening a cache with a given name will cause it to \"exist\", even if it's empty.\n        caches\n          .open(\n            'workbox-precache-http://localhost:3004/test/workbox-precaching/static/cleanup-outdated-caches/',\n          )\n          .then(() => caches.keys())\n          .then((keys) => cb(keys))\n          .catch((err) => cb(err.message));\n      },\n    );\n\n    expect(preActivateKeys).to.include(\n      'workbox-precache-http://localhost:3004/test/workbox-precaching/static/cleanup-outdated-caches/',\n    );\n    expect(preActivateKeys).to.not.include(\n      'workbox-precache-v2-http://localhost:3004/test/workbox-precaching/static/cleanup-outdated-caches/',\n    );\n\n    // Register the first service worker.\n    await activateAndControlSW(`${baseURL}sw.js`);\n\n    const postActivateKeys = await runInSW('cachesKeys');\n    expect(postActivateKeys).to.not.include(\n      'workbox-precache-http://localhost:3004/test/workbox-precaching/static/cleanup-outdated-caches/',\n    );\n    expect(postActivateKeys).to.include(\n      'workbox-precache-v2-http://localhost:3004/test/workbox-precaching/static/cleanup-outdated-caches/',\n    );\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/integration/test-precache-and-update.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\n\ndescribe(`[workbox-precaching] Precache and Update`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-precaching/static/precache-and-update/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}index.html`);\n  });\n\n  it(`should load a page with service worker `, async function () {\n    const SW_1_URL = `${baseURL}sw-1.js`;\n    const SW_2_URL = `${baseURL}sw-2.js`;\n\n    const cacheName =\n      'workbox-precache-v2-http://localhost:3004/test/workbox-precaching/static/precache-and-update/';\n\n    let requestCounter = global.__workbox.server.startCountingRequests();\n\n    // Register the first service worker.\n    await activateAndControlSW(SW_1_URL);\n\n    // Check that only the precache cache was created.\n    const keys = await runInSW('cachesKeys');\n    expect(keys).to.eql([cacheName]);\n\n    // Check that the cached requests are what we expect for sw-1.js\n    let cachedRequests = await runInSW('cacheURLs', cacheName);\n    expect(cachedRequests).to.have.members([\n      'http://localhost:3004/test/workbox-precaching/static/precache-and-update/index.html?__WB_REVISION__=1',\n      'http://localhost:3004/test/workbox-precaching/static/precache-and-update/styles/index.css?__WB_REVISION__=1',\n    ]);\n\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/styles/index.css',\n      ),\n    ).to.equal(1);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/index.html',\n      ),\n    ).to.equal(1);\n\n    // Unregister the old counter, and start a new count.\n    global.__workbox.server.stopCountingRequests(requestCounter);\n    requestCounter = global.__workbox.server.startCountingRequests();\n\n    // Request the page and check that the precached assets weren't requested from the network.\n    // Include the default ignoreURLParametersMatching query parameters.\n    await global.__workbox.webdriver.get(`${baseURL}index.html`);\n    await global.__workbox.webdriver.get(\n      `${baseURL}index.html?utm_source=test`,\n    );\n    await global.__workbox.webdriver.get(`${baseURL}index.html?fbclid=test`);\n\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/',\n      ),\n    ).to.eql(0);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/index.html',\n      ),\n    ).to.eql(0);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/index.html?utm_source=test',\n      ),\n    ).to.eql(0);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/index.html?fbclid=test',\n      ),\n    ).to.eql(0);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/styles/index.css',\n      ),\n    ).to.eql(0);\n\n    // Unregister the old counter, and start a new count.\n    global.__workbox.server.stopCountingRequests(requestCounter);\n    requestCounter = global.__workbox.server.startCountingRequests();\n\n    // Activate the second service worker\n    await activateAndControlSW(SW_2_URL);\n    // Add a slight delay for the caching operation to complete.\n    await new Promise((resolve) => setTimeout(resolve, 1000));\n\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/index.html',\n      ),\n    ).to.equal(1);\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/hashed-file.abcd1234.txt',\n      ),\n    ).to.equal(1);\n\n    // Check that the cached entries were deleted / added as expected when\n    // updating from sw-1.js to sw-2.js\n    cachedRequests = await runInSW('cacheURLs', cacheName);\n    expect(cachedRequests).to.have.members([\n      'http://localhost:3004/test/workbox-precaching/static/precache-and-update/index.html?__WB_REVISION__=2',\n      'http://localhost:3004/test/workbox-precaching/static/precache-and-update/hashed-file.abcd1234.txt',\n    ]);\n\n    // Unregister the old counter, and start a new count.\n    global.__workbox.server.stopCountingRequests(requestCounter);\n    requestCounter = global.__workbox.server.startCountingRequests();\n\n    await global.__workbox.webdriver.get(baseURL);\n\n    // Ensure the HTML page is returned from cache and not network.\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/',\n      ),\n    ).to.eql(0);\n    // Ensure the now deleted index.css file is returned from network and not cache.\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-precaching/static/precache-and-update/styles/index.css',\n      ),\n    ).to.equal(1);\n\n    global.__workbox.server.stopCountingRequests(requestCounter);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/integration/test-sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\ndescribe(`[workbox-precaching]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-precaching/sw/');\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/static/addToCacheList.html",
    "content": "<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n  </head>\n  <body>\n    <h3>precache.addToCacheList()</h3>\n    <button class=\"js-index-unrevisioned\">Add Unrevisioned Assets</button>\n\n    <script src=\"../../../../packages/workbox-core/build/browser/workbox-core.dev.js\"></script>\n    <script src=\"../../../../packages/workbox-precaching/build/browser/workbox-precaching.dev.js\"></script>\n    <script>\n      const precacheController = new workbox.precaching.PrecacheController();\n\n      const indexStringBtn = document.querySelector('.js-index-unrevisioned');\n      indexStringBtn.addEventListener('click', () => {\n        precacheController.addToCacheList([\n          '/index.html',\n          {url: '/example.html'},\n        ]);\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-precaching/static/cleanup-outdated-caches/sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.precaching.precache([\n  {\n    url: 'test.txt',\n    revision: '1',\n  },\n]);\n\nworkbox.precaching.cleanupOutdatedCaches();\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-precaching/static/cleanup-outdated-caches/test.txt",
    "content": "Testing.\n"
  },
  {
    "path": "test/workbox-precaching/static/precache-and-update/hashed-file.abcd1234.txt",
    "content": "This file will be requested by sw-2.js.\n"
  },
  {
    "path": "test/workbox-precaching/static/precache-and-update/index.html",
    "content": "<html>\n  <head>\n    <title>Test page for precache-and-update</title>\n    <style>\n      body {\n        background: #c0392b;\n      }\n    </style>\n    <link rel=\"stylesheet\" href=\"styles/index.css\" />\n  </head>\n  <body>\n    <h1>Workbox Precaching - precache-and-update</h1>\n    <script src=\"/infra/testing/comlink/window-interface.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-precaching/static/precache-and-update/styles/index.css",
    "content": "body {\n  background-color: #2ecc71;\n}\n"
  },
  {
    "path": "test/workbox-precaching/static/precache-and-update/sw-1.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.precaching.addPlugins([\n  new workbox.cacheableResponse.CacheableResponsePlugin({\n    statuses: [200],\n  }),\n]);\n\nworkbox.precaching.precache([\n  {\n    url: 'styles/index.css',\n    revision: '1',\n  },\n  {\n    url: 'index.html',\n    revision: '1',\n  },\n]);\n\nworkbox.precaching.addRoute();\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-precaching/static/precache-and-update/sw-2.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.precaching.precache([\n  {\n    url: 'index.html',\n    revision: '2',\n  },\n  'hashed-file.abcd1234.txt',\n]);\n\nworkbox.precaching.addRoute();\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-precaching/static/precache.html",
    "content": "<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n  </head>\n  <body>\n    <h3>precache.install() &amp; precache.activate()</h3>\n    <button class=\"js-index-install\">Install</button>\n    <button class=\"js-index-activate\">Activate</button>\n\n    <script>\n      // Make sure there is scope available for core.\n      self.registration = {\n        scope: 'injected-scope',\n      };\n    </script>\n    <script src=\"../../../../packages/workbox-core/build/browser/workbox-core.dev.js\"></script>\n    <script src=\"../../../../packages/workbox-precaching/build/browser/workbox-precaching.dev.js\"></script>\n\n    <script>\n      let lastInstallValues = [];\n      let flipFile = 'example-a.html';\n      const installBtn = document.querySelector('.js-index-install');\n      installBtn.addEventListener('click', (event) => {\n        const precacheController = new workbox.precaching.PrecacheController();\n\n        lastInstallValues = [\n          './project/index.html',\n          `./project/${flipFile}`,\n          {url: './project/example.html'},\n          {url: './project/example-2.html', revision: '123'},\n          {url: './project/example-timestamp.html', revision: Date.now()},\n        ];\n        precacheController.addToCacheList(lastInstallValues);\n\n        precacheController.install(event);\n\n        if (flipFile === 'example-a.html') {\n          flipFile = 'example-b.html';\n        } else {\n          flipFile = 'example-a.html';\n        }\n      });\n\n      const activateBtn = document.querySelector('.js-index-activate');\n      activateBtn.addEventListener('click', (event) => {\n        const precacheController = new workbox.precaching.PrecacheController();\n        precacheController.addToCacheList(lastInstallValues);\n        precacheController.activate(event);\n      });\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-precaching/static/project/example-2.html",
    "content": "Text from example-2.html\n"
  },
  {
    "path": "test/workbox-precaching/static/project/example-a.html",
    "content": "example-a.html\n"
  },
  {
    "path": "test/workbox-precaching/static/project/example-b.html",
    "content": "example-b.html\n"
  },
  {
    "path": "test/workbox-precaching/static/project/example-timestamp.html",
    "content": "Text from example-timestamp.html\n"
  },
  {
    "path": "test/workbox-precaching/static/project/example.html",
    "content": "Text from example.html\n"
  },
  {
    "path": "test/workbox-precaching/static/project/index.html",
    "content": "Text from index.html\n"
  },
  {
    "path": "test/workbox-precaching/sw/resetDefaultPrecacheController.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getOrCreatePrecacheController} from 'workbox-precaching/utils/getOrCreatePrecacheController.mjs';\n\nexport function resetDefaultPrecacheController() {\n  const pc = getOrCreatePrecacheController();\n\n  pc._urlsToCacheKeys.clear();\n  pc._urlsToCacheModes.clear();\n  pc._cacheKeysToIntegrities.clear();\n\n  pc._installAndActiveListenersAdded = false;\n}\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-PrecacheController.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {PrecacheController} from 'workbox-precaching/PrecacheController.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\nimport {spyOnEvent} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport generateTestVariants from '../../../infra/testing/generate-variant-tests';\n\nfunction createFetchEvent(url) {\n  const event = new FetchEvent('fetch', {\n    request: new Request(url),\n  });\n  spyOnEvent(event);\n  return event;\n}\n\ndescribe(`PrecacheController`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n\n    if (logger) {\n      sandbox.spy(logger, 'log');\n    }\n\n    sandbox.stub(self, 'fetch').callsFake(() => {\n      return Promise.resolve(new Response('stub'));\n    });\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    // Clear all caches.\n    const cacheKeys = await caches.keys();\n    for (const cacheKey of cacheKeys) {\n      await caches.delete(cacheKey);\n    }\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should construct without any inputs`, async function () {\n      expect(() => {\n        new PrecacheController();\n      }).to.not.throw();\n    });\n\n    it(`should construct with a valid cache name`, async function () {\n      expect(() => {\n        new PrecacheController({cacheName: 'test-cache-name'});\n      }).to.not.throw();\n    });\n\n    it(`should pass the 'cacheName' option to the strategy`, async function () {\n      const cacheName = 'test-cache-name';\n      const pc = new PrecacheController({cacheName});\n      expect(pc.strategy.cacheName).to.equal(cacheName);\n    });\n\n    it(`should pass the 'plugins' option to the strategy`, async function () {\n      const plugin = {};\n      const plugins = [plugin];\n      const pc = new PrecacheController({plugins});\n\n      // We can't just compare because PrecacheController adds plugins,\n      expect(pc.strategy.plugins).to.include(plugin);\n    });\n\n    it(`should pass the 'fallbackToNetwork' option to the strategy`, async function () {\n      const pc = new PrecacheController({\n        cacheName: 'test',\n        fallbackToNetwork: false,\n      });\n\n      const cache = await caches.open('test');\n      await cache.put('/one', new Response('Cached Response'));\n\n      const response1 = await pc.strategy.handle(createFetchEvent('/one'));\n      expect(await response1.text()).to.equal('Cached Response');\n      expect(self.fetch.callCount).to.equal(0);\n\n      await expectError(() => {\n        return pc.strategy.handle(createFetchEvent('/two'));\n      }, 'missing-precache-entry');\n\n      expect(self.fetch.callCount).to.equal(0);\n    });\n  });\n\n  describe(`addToCacheList()`, function () {\n    const badTopLevelInputs = [{}, true, false, 123, '', null, undefined];\n    generateTestVariants(\n      `should throw when passing in non-array values in dev`,\n      badTopLevelInputs,\n      async function (variant) {\n        if (process.env.NODE_ENV === 'production') return this.skip();\n\n        const precacheController = new PrecacheController();\n        return expectError(() => {\n          precacheController.addToCacheList(variant);\n        }, 'not-an-array');\n      },\n    );\n\n    const badNestedInputs = [true, false, 123, null, undefined, [], '', {}];\n\n    generateTestVariants(\n      `should throw when passing in invalid inputs in the array in dev`,\n      badNestedInputs,\n      async function (variant) {\n        if (process.env.NODE_ENV == 'production') return this.skip();\n\n        const precacheController = new PrecacheController();\n        return expectError(\n          () => {\n            precacheController.addToCacheList([variant]);\n          },\n          'add-to-cache-list-unexpected-type',\n          (err) => {\n            expect(err.details.entry).to.deep.equal(variant);\n          },\n        );\n      },\n    );\n\n    const unrevisionedEntryGroups = {\n      'string entries': [\n        '/',\n        '/hello.html',\n        '/styles/hello.css',\n        '/scripts/controllers/hello.js',\n      ],\n      'url only object entries': [\n        {url: '/'},\n        {url: '/hello.html'},\n        {url: '/styles/hello.css'},\n        {url: '/scripts/controllers/hello.js'},\n      ],\n    };\n\n    Object.keys(unrevisionedEntryGroups).forEach((groupName) => {\n      const inputGroup = unrevisionedEntryGroups[groupName];\n\n      it(`should add ${groupName} to cache list in dev`, async function () {\n        if (process.env.NODE_ENV === 'production') this.skip();\n\n        const precacheController = new PrecacheController();\n\n        precacheController.addToCacheList(inputGroup);\n\n        expect(precacheController._urlsToCacheKeys.size).to.equal(\n          inputGroup.length,\n        );\n\n        inputGroup.forEach((inputValue) => {\n          const urlValue = new URL(inputValue.url || inputValue, location).href;\n\n          const cacheKey = precacheController._urlsToCacheKeys.get(urlValue);\n          expect(cacheKey).to.eql(urlValue);\n        });\n      });\n\n      it(`should remove duplicate ${groupName}`, async function () {\n        const precacheController = new PrecacheController();\n\n        const inputURLs = [...inputGroup, ...inputGroup];\n\n        precacheController.addToCacheList(inputURLs);\n\n        expect([...precacheController._urlsToCacheKeys.values()]).to.eql([\n          `${location.origin}/`,\n          `${location.origin}/hello.html`,\n          `${location.origin}/styles/hello.css`,\n          `${location.origin}/scripts/controllers/hello.js`,\n        ]);\n      });\n    });\n\n    it(`should log a warning unless an entry has a revision property`, function () {\n      const logObject =\n        process.env.NODE_ENV === 'production' ? console : logger;\n      const warnStub = sandbox.stub(logObject, 'warn');\n\n      const precacheController = new PrecacheController();\n\n      precacheController.addToCacheList(['/should-warn']);\n      expect(warnStub.calledOnce).to.be.true;\n      warnStub.resetHistory();\n\n      precacheController.addToCacheList([{url: '/also-should-warn'}]);\n      expect(warnStub.calledOnce).to.be.true;\n      warnStub.resetHistory();\n\n      precacheController.addToCacheList([\n        {url: '/should-not-warn', revision: null},\n        {url: '/also-should-not-warn', revision: '1234abcd'},\n      ]);\n      expect(warnStub.notCalled).to.be.true;\n    });\n\n    it(`should add url + revision objects to cache list`, async function () {\n      const precacheController = new PrecacheController();\n\n      const inputObjects = [\n        {url: '/', revision: '123'},\n        {url: '/hello.html', revision: '123'},\n        {url: '/styles/hello.css', revision: '123'},\n        {url: '/scripts/controllers/hello.js', revision: '123'},\n      ];\n      precacheController.addToCacheList(inputObjects);\n\n      expect([...precacheController._urlsToCacheKeys.values()]).to.eql([\n        `${location.origin}/?__WB_REVISION__=123`,\n        `${location.origin}/hello.html?__WB_REVISION__=123`,\n        `${location.origin}/styles/hello.css?__WB_REVISION__=123`,\n        `${location.origin}/scripts/controllers/hello.js?__WB_REVISION__=123`,\n      ]);\n    });\n\n    it(`should remove duplicate url + revision object entries`, async function () {\n      const precacheController = new PrecacheController();\n\n      const singleObject = {\n        url: new URL('/duplicate.html', location).href,\n        revision: '123',\n      };\n      const inputObjects = [singleObject, singleObject];\n      precacheController.addToCacheList(inputObjects);\n\n      expect(precacheController._urlsToCacheKeys.size).to.equal(1);\n      expect(precacheController._urlsToCacheKeys.has(singleObject.url)).to.be\n        .true;\n    });\n\n    it(`should throw on conflicting entries with different revisions`, async function () {\n      const firstEntry = {url: '/duplicate.html', revision: '123'};\n      const secondEntry = {url: '/duplicate.html', revision: '456'};\n      return expectError(\n        () => {\n          const precacheController = new PrecacheController();\n          const inputObjects = [firstEntry, secondEntry];\n          precacheController.addToCacheList(inputObjects);\n        },\n        'add-to-cache-list-conflicting-entries',\n        (err) => {\n          expect(err.details.firstEntry).to.eql(\n            `${location.origin}/duplicate.html?__WB_REVISION__=123`,\n          );\n          expect(err.details.secondEntry).to.eql(\n            `${location.origin}/duplicate.html?__WB_REVISION__=456`,\n          );\n        },\n      );\n    });\n  });\n\n  describe(`install()`, function () {\n    it(`should be fine when calling with empty precache list`, async function () {\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const precacheController = new PrecacheController();\n      return precacheController.install(event);\n    });\n\n    it(`should precache assets (with cache busting via search params)`, async function () {\n      const precacheController = new PrecacheController();\n      const cacheList = [\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '1234'},\n      ];\n      precacheController.addToCacheList(cacheList);\n\n      if (process.env.NODE_ENV !== 'production') {\n        // Reset as addToCacheList will log.\n        logger.log.resetHistory();\n      }\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const updateInfo = await precacheController.install(event);\n      expect(updateInfo.updatedURLs.length).to.equal(cacheList.length);\n      expect(updateInfo.notUpdatedURLs.length).to.equal(0);\n\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      const keys = await cache.keys();\n      expect(keys.length).to.equal(cacheList.length);\n\n      const expectedCacheKeys = [\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=1234`,\n      ];\n      for (const key of expectedCacheKeys) {\n        const cachedResponse = await cache.match(key);\n        expect(cachedResponse, `${key} is missing from the cache`).to.exist;\n      }\n\n      if (process.env.NODE_ENV !== 'production') {\n        // Make sure we print some debug info.\n        expect(logger.log.callCount).to.be.gt(0);\n      }\n    });\n\n    it(`should copy the response for redirected entries`, async function () {\n      self.fetch.restore();\n      sandbox.stub(self, 'fetch').callsFake((request) => {\n        const response = new Response('Redirected Response');\n        sandbox.replaceGetter(response, 'redirected', () => true);\n        sandbox.replaceGetter(\n          response,\n          'url',\n          () => new URL(request.url, self.location.href).href,\n        );\n        return response;\n      });\n\n      const precacheController = new PrecacheController();\n      precacheController.addToCacheList([\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n      ]);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n    });\n\n    it(`should use the desired cache name`, async function () {\n      const precacheController = new PrecacheController({\n        cacheName: 'test-cache-name',\n      });\n\n      const cacheList = [\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/index.html', revision: '1234'},\n      ];\n\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      const cache = await caches.open(`test-cache-name`);\n      const keys = await cache.keys();\n      expect(keys.length).to.equal(cacheList.length);\n\n      const expectedCacheKeys = [\n        `${location.origin}/index.html?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n      ];\n      for (const key of expectedCacheKeys) {\n        const cachedResponse = await cache.match(key);\n        expect(cachedResponse).to.exist;\n      }\n    });\n\n    it(`should update assets that have changed`, async function () {\n      const initialPrecacheController = new PrecacheController();\n\n      const initialCacheList = [\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '1234'},\n      ];\n\n      initialPrecacheController.addToCacheList(initialCacheList);\n\n      const installEvent = new ExtendableEvent('install');\n      spyOnEvent(installEvent);\n\n      const initialInstallInfo = await initialPrecacheController.install(\n        installEvent,\n      );\n\n      const initialExpectedCacheKeys = [\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=1234`,\n      ];\n\n      expect(initialInstallInfo.updatedURLs).to.have.members([\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar`,\n      ]);\n      expect(initialInstallInfo.notUpdatedURLs).to.be.empty;\n\n      let cache = await caches.open(cacheNames.getPrecacheName());\n      const initialCacheKeys = await cache.keys();\n      // Get the url field out of each Request object.\n      expect(initialCacheKeys.map((r) => r.url)).to.have.members(\n        initialExpectedCacheKeys,\n      );\n\n      const activateEvent = new ExtendableEvent('activate');\n      spyOnEvent(activateEvent);\n\n      const initialActivateInfo = await initialPrecacheController.activate(\n        activateEvent,\n      );\n      expect(initialActivateInfo.deletedURLs).to.be.empty;\n\n      // Make sure the files are cached after activation.\n      for (const key of initialExpectedCacheKeys) {\n        const cachedResponse = await cache.match(key);\n        expect(cachedResponse, `${key} is not cached`).to.exist;\n      }\n\n      const updatePrecacheController = new PrecacheController();\n\n      const updateCacheList = [\n        '/index.4321.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '4321'},\n      ];\n\n      updatePrecacheController.addToCacheList(updateCacheList);\n\n      const updateInstallInfo = await updatePrecacheController.install(\n        installEvent,\n      );\n\n      expect(updateInstallInfo.updatedURLs).to.have.members([\n        `${location.origin}/index.4321.html`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar`,\n      ]);\n\n      expect(updateInstallInfo.notUpdatedURLs).to.have.members([\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js`,\n      ]);\n\n      // The cache mock needs the cache to be re-opened to have up-to-date keys.\n      cache = await caches.open(cacheNames.getPrecacheName());\n      const updateCacheKeys = await cache.keys();\n      // Get the url field out of each Request object.\n      expect(updateCacheKeys.map((r) => r.url)).to.have.members([\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/index.4321.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=4321`,\n      ]);\n\n      if (process.env.NODE_ENV != 'production') {\n        // Make sure we print some debug info.\n        expect(logger.log.callCount).to.be.gt(0);\n      }\n\n      const updateActivateInfo = await updatePrecacheController.activate(\n        activateEvent,\n      );\n      expect(updateActivateInfo.deletedURLs).to.have.members([\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=1234`,\n      ]);\n\n      const expectedPostActivateUpdateCacheKeys = [\n        `${location.origin}/index.4321.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=4321`,\n      ];\n\n      // The cache mock needs the cache to be re-opened to have up-to-date keys.\n      cache = await caches.open(cacheNames.getPrecacheName());\n      const postActivateUpdateCacheKeys = await cache.keys();\n      // Get the url field out of each Request object.\n      expect(postActivateUpdateCacheKeys.map((r) => r.url)).to.have.members(\n        expectedPostActivateUpdateCacheKeys,\n      );\n\n      // Make sure the files are cached after activation.\n      for (const key of expectedPostActivateUpdateCacheKeys) {\n        const cachedResponse = await cache.match(key);\n        expect(cachedResponse, `${key} is not cached`).to.exist;\n      }\n    });\n\n    it(`should precache with plugins`, async function () {\n      const plugins = [\n        {\n          requestWillFetch: sandbox.stub().callsFake(({request}) => request),\n          cacheWillUpdate: sandbox.stub().callsFake(({response}) => response),\n        },\n        {\n          requestWillFetch: sandbox.stub().callsFake(({request}) => request),\n          cacheWillUpdate: sandbox.stub().callsFake(({response}) => response),\n        },\n      ];\n      const precacheController = new PrecacheController({plugins});\n\n      const cacheList = [\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '1234'},\n      ];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(plugins[0].requestWillFetch.callCount).to.equal(4);\n      expect(plugins[0].cacheWillUpdate.callCount).to.equal(4);\n      expect(plugins[1].requestWillFetch.callCount).to.equal(4);\n      expect(plugins[1].cacheWillUpdate.callCount).to.equal(4);\n    });\n\n    it(`should detect older precached versions in the cacheDidUpdate callback`, async function () {\n      // Restore fetch since this test depends on real fetch calls happening.\n      self.fetch.restore();\n\n      const precacheControllerV1 = new PrecacheController();\n      const cacheListV1 = [\n        {url: '/__WORKBOX/uniqueValue?one', revision: 'V1'},\n        {url: '/__WORKBOX/uniqueValue?two', revision: 'V1'},\n        {url: '/__WORKBOX/uniqueValue?three', revision: 'V1'},\n        {url: '/test/?rev=V1', revision: null},\n      ];\n      precacheControllerV1.addToCacheList(cacheListV1);\n\n      const installEventV1 = new ExtendableEvent('install');\n      spyOnEvent(installEventV1);\n\n      await precacheControllerV1.install(installEventV1);\n\n      const plugins = [\n        {\n          cacheDidUpdate: sandbox.stub().callsFake(({request}) => request),\n        },\n      ];\n\n      const precacheControllerV2 = new PrecacheController({plugins});\n      const cacheListV2 = [\n        {url: '/__WORKBOX/uniqueValue?one', revision: 'V2'},\n        {url: '/__WORKBOX/uniqueValue?two', revision: 'V2'},\n        {url: '/__WORKBOX/uniqueValue?four', revision: 'V2'},\n        {url: '/test/?rev=V2', revision: null},\n      ];\n      precacheControllerV2.addToCacheList(cacheListV2);\n\n      const installEventV2 = new ExtendableEvent('install');\n      spyOnEvent(installEventV2);\n\n      await precacheControllerV2.install(installEventV2);\n\n      const [[c1], [c2], [c3], [c4]] = plugins[0].cacheDidUpdate.args;\n\n      // For the resources with revision changes, the response URLs should\n      // match, but the bodies should be different.\n      expect(c1.oldResponse.url).to.equal(c1.newResponse.url);\n      expect(await c1.oldResponse.text()).to.not.equal(\n        await c1.newResponse.text(),\n      );\n      expect(c2.oldResponse.url).to.equal(c2.newResponse.url);\n      expect(await c2.oldResponse.text()).to.not.equal(\n        await c2.newResponse.text(),\n      );\n\n      // For other resources it shouldn't find an old response.\n      expect(c3.oldResponse).to.equal(undefined);\n      expect(c3.newResponse.url).to.equal(location.origin + cacheListV2[2].url);\n      expect(c4.oldResponse).to.equal(undefined);\n      expect(c4.newResponse.url).to.equal(location.origin + cacheListV2[3].url);\n    });\n\n    it(`should use the proper 'this' when calling a cacheWillUpdate plugin`, async function () {\n      class TestPlugin {\n        cacheWillUpdate({response}) {\n          return response;\n        }\n      }\n      const pluginInstance = new TestPlugin();\n      const cacheWillUpdateSpy = sandbox.spy(pluginInstance, 'cacheWillUpdate');\n\n      const precacheController = new PrecacheController({\n        plugins: [pluginInstance],\n      });\n\n      const cacheList = ['/index.1234.html'];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(cacheWillUpdateSpy.thisValues[0]).to.be.an.instanceof(TestPlugin);\n    });\n\n    it(`should set credentials: 'same-origin' on the precaching requests`, async function () {\n      const precacheController = new PrecacheController();\n      const cacheList = ['/index.1234.html'];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(self.fetch.calledOnce).to.be.true;\n\n      const request = self.fetch.args[0][0];\n      expect(request.credentials).to.eql('same-origin');\n    });\n\n    it(`should use cache: 'reload' on the precaching requests when there's a revision field`, async function () {\n      const precacheController = new PrecacheController();\n      const cacheList = [{url: '/test', revision: 'abcd'}];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(self.fetch.calledOnce).to.be.true;\n\n      const request = self.fetch.args[0][0];\n      expect(request.url).to.eql(`${location.origin}/test`);\n      expect(request.cache).to.eql('reload');\n    });\n\n    it(`should use cache: 'default' on the precaching requests when there's no revision field`, async function () {\n      const precacheController = new PrecacheController();\n      const cacheList = [{url: '/test'}];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(self.fetch.calledOnce).to.be.true;\n\n      const request = self.fetch.args[0][0];\n      expect(request.url).to.eql(`${location.origin}/test`);\n      expect(request.cache).to.eql('default');\n    });\n\n    it(`should pass in a request that includes the revision to cacheWrapper.put()`, async function () {\n      const putStub = sandbox.stub(Cache.prototype, 'put');\n\n      const precacheController = new PrecacheController();\n      const cacheList = [{url: '/test', revision: 'abcd'}];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(putStub.calledOnce).to.be.true;\n\n      const request = putStub.args[0][0];\n      expect(request.url).to.eql(\n        `${location.origin}/test?__WB_REVISION__=abcd`,\n      );\n    });\n\n    it(`should use the integrity value when making requests`, async function () {\n      const precacheController = new PrecacheController();\n      const cacheList = [\n        {url: '/first'},\n        {url: '/second', integrity: 'sha256-second'},\n      ];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      await precacheController.install(event);\n\n      expect(self.fetch.calledTwice).to.be.true;\n      expect(self.fetch.args[0][0].integrity).to.eql('');\n      expect(self.fetch.args[1][0].integrity).to.eql('sha256-second');\n    });\n\n    it(`should fail when entries have the same url but different integrity`, function () {\n      return expectError(\n        () => {\n          const precacheController = new PrecacheController();\n          const cacheList = [\n            {url: '/test', integrity: 'sha256-one'},\n            {url: '/test', integrity: 'sha256-two'},\n          ];\n          precacheController.addToCacheList(cacheList);\n        },\n        'add-to-cache-list-conflicting-integrities',\n        (err) => {\n          expect(err.details.url).to.eql(`${location.origin}/test`);\n        },\n      );\n    });\n\n    it(`should fail installation when a response with a status of 400 is received`, async function () {\n      self.fetch.restore();\n      sandbox.stub(self, 'fetch').resolves(\n        new Response('', {\n          status: 400,\n        }),\n      );\n\n      const precacheController = new PrecacheController();\n      const cacheList = ['/will-be-error.html'];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      return expectError(\n        () => precacheController.install(event),\n        'bad-precaching-response',\n      );\n    });\n\n    it(`should successfully install when an opaque response is received`, async function () {\n      self.fetch.restore();\n      sandbox.stub(self, 'fetch').callsFake(() => {\n        const response = new Response('opaque');\n        sandbox.stub(response, 'status').value(0);\n        return response;\n      });\n\n      const precacheController = new PrecacheController();\n      const cacheList = ['/will-be-opaque.html'];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      // This should succeed.\n      await precacheController.install(event);\n    });\n\n    it(`should successfully install when a response with a status of 400 is received, if a cacheWillUpdate plugin allows it`, async function () {\n      self.fetch.restore();\n      sandbox.stub(self, 'fetch').resolves(\n        new Response('', {\n          status: 400,\n        }),\n      );\n\n      const plugins = [\n        {\n          cacheWillUpdate: ({request, response}) => {\n            if (response.status === 400) {\n              return response;\n            }\n            return null;\n          },\n        },\n      ];\n\n      const precacheController = new PrecacheController({plugins});\n      const cacheList = ['/will-be-error.html'];\n      precacheController.addToCacheList(cacheList);\n\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      // This should succeed.\n      await precacheController.install(event);\n    });\n\n    it(`should automatically call event.waitUntil()`, async function () {\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      new PrecacheController().install(event);\n\n      expect(event.waitUntil.callCount).to.equal(1);\n    });\n  });\n\n  describe(`activate()`, function () {\n    it(`should remove out of date entry`, async function () {\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      const installEvent = new ExtendableEvent('install');\n      spyOnEvent(installEvent);\n\n      // First precache some entries.\n      const precacheControllerOne = new PrecacheController();\n      const cacheList1 = [{url: '/scripts/index.js', revision: '1234'}];\n      precacheControllerOne.addToCacheList(cacheList1);\n      await precacheControllerOne.install(installEvent);\n\n      const activateEvent = new ExtendableEvent('activate');\n      spyOnEvent(activateEvent);\n\n      const cleanupDetailsOne = await precacheControllerOne.activate(\n        activateEvent,\n      );\n      expect(cleanupDetailsOne.deletedURLs.length).to.equal(0);\n\n      const precacheControllerTwo = new PrecacheController();\n      const cacheListTwo = [];\n      precacheControllerTwo.addToCacheList(cacheListTwo);\n      await precacheControllerTwo.install(installEvent);\n\n      const cleanupDetailsTwo = await precacheControllerTwo.activate(\n        activateEvent,\n      );\n      expect(cleanupDetailsTwo.deletedURLs.length).to.equal(1);\n      expect(cleanupDetailsTwo.deletedURLs[0]).to.eql(\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n      );\n\n      const keysTwo = await cache.keys();\n      expect(keysTwo.length).to.equal(cacheListTwo.length);\n    });\n\n    it(`should remove out of date entries`, async function () {\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      const installEvent = new ExtendableEvent('install');\n      spyOnEvent(installEvent);\n\n      // First, precache some entries.\n      const precacheControllerOne = new PrecacheController();\n      const cacheList1 = [\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '1234'},\n      ];\n      precacheControllerOne.addToCacheList(cacheList1);\n      await precacheControllerOne.install(installEvent);\n\n      if (process.env.NODE_ENV !== 'production') {\n        // Reset, as addToCacheList and install will log.\n        logger.log.resetHistory();\n      }\n\n      const activateEvent = new ExtendableEvent('activate');\n      spyOnEvent(activateEvent);\n\n      const cleanupDetailsOne = await precacheControllerOne.activate(\n        activateEvent,\n      );\n      expect(cleanupDetailsOne.deletedURLs.length).to.equal(0);\n\n      // Then, precache the same URLs, but two with different revisions.\n      const precacheControllerTwo = new PrecacheController();\n      const cacheListTwo = [\n        '/index.4321.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '4321'},\n      ];\n      precacheControllerTwo.addToCacheList(cacheListTwo);\n      await precacheControllerTwo.install(installEvent);\n\n      if (process.env.NODE_ENV !== 'production') {\n        // Reset as addToCacheList and install will log.\n        logger.log.resetHistory();\n      }\n\n      const cleanupDetailsTwo = await precacheControllerTwo.activate(\n        activateEvent,\n      );\n      expect(cleanupDetailsTwo.deletedURLs).to.have.members([\n        `${location.origin}/index.1234.html`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=1234`,\n      ]);\n\n      const keysTwo = await cache.keys();\n      expect(keysTwo.length).to.equal(cacheListTwo.length);\n\n      const expectedCacheKeys2 = [\n        `${location.origin}/index.4321.html`,\n        `${location.origin}/example.1234.css`,\n        `${location.origin}/scripts/index.js?__WB_REVISION__=1234`,\n        `${location.origin}/scripts/stress.js?test=search&foo=bar&__WB_REVISION__=4321`,\n      ];\n      for (const key of expectedCacheKeys2) {\n        const cachedResponse = await cache.match(key);\n        expect(cachedResponse, `${key} is not cached`).to.exist;\n      }\n\n      // Make sure we print some log info.\n      if (process.env.NODE_ENV !== 'production') {\n        expect(logger.log.callCount).to.be.gt(0);\n      }\n    });\n\n    it(`should automatically call event.waitUntil()`, async function () {\n      const event = new ExtendableEvent('activate');\n      spyOnEvent(event);\n\n      new PrecacheController().activate(event);\n\n      expect(event.waitUntil.callCount).to.equal(1);\n    });\n  });\n\n  describe(`getCachedURLs()`, function () {\n    it(`should return the cached URLs`, function () {\n      const precacheController = new PrecacheController();\n      const cacheList = [\n        '/index.1234.html',\n        {url: '/example.1234.css'},\n        {url: '/scripts/index.js', revision: '1234'},\n        {url: '/scripts/stress.js?test=search&foo=bar', revision: '1234'},\n      ];\n      precacheController.addToCacheList(cacheList);\n\n      const urls = precacheController.getCachedURLs();\n      expect(urls).to.deep.equal([\n        new URL('/index.1234.html', location).toString(),\n        new URL('/example.1234.css', location).toString(),\n        new URL('/scripts/index.js', location).toString(),\n        new URL('/scripts/stress.js?test=search&foo=bar', location).toString(),\n      ]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-PrecacheFallbackPlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {precache} from 'workbox-precaching/precache.mjs';\nimport {PrecacheController} from 'workbox-precaching/PrecacheController.mjs';\nimport {PrecacheFallbackPlugin} from 'workbox-precaching/PrecacheFallbackPlugin.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`PrecacheFallbackPlugin`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.stub(self, 'addEventListener');\n    resetDefaultPrecacheController();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should construct a PrecacheFallbackPlugin instance with the default PrecacheController`, function () {\n      const fallbackURL = '/test/url';\n      const precacheFallbackPlugin = new PrecacheFallbackPlugin({fallbackURL});\n\n      expect(precacheFallbackPlugin._fallbackURL).to.eql(fallbackURL);\n      expect(precacheFallbackPlugin._precacheController).to.be.instanceOf(\n        PrecacheController,\n      );\n    });\n\n    it(`should construct a PrecacheFallbackPlugin instance with a non-default PrecacheController`, function () {\n      const fallbackURL = '/test/url';\n      const precacheController = new PrecacheController();\n      const precacheFallbackPlugin = new PrecacheFallbackPlugin({\n        fallbackURL,\n        precacheController,\n      });\n\n      expect(precacheFallbackPlugin._fallbackURL).to.eql(fallbackURL);\n      expect(precacheFallbackPlugin._precacheController).to.eql(\n        precacheController,\n      );\n    });\n\n    it(`should expose a handlerDidError method`, function () {\n      const fallbackURL = '/test/url';\n      const precacheFallbackPlugin = new PrecacheFallbackPlugin({fallbackURL});\n\n      expect(precacheFallbackPlugin).to.respondTo('handlerDidError');\n    });\n  });\n\n  describe(`handlerDidError`, function () {\n    it(`should return the matchPrecache value for the fallbackURL`, async function () {\n      const body = 'test body';\n      const fallbackURL = '/test/url';\n      const revision = 'abcd1234';\n\n      const matchStub = sandbox.stub().resolves(new Response(body));\n      sandbox.stub(self.caches, 'open').resolves({\n        match: matchStub,\n      });\n\n      precache([\n        {\n          revision,\n          url: fallbackURL,\n        },\n      ]);\n\n      const precacheFallbackPlugin = new PrecacheFallbackPlugin({fallbackURL});\n\n      const response = await precacheFallbackPlugin.handlerDidError();\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(body);\n\n      const expectedURL = new URL(fallbackURL, location.href);\n      expectedURL.searchParams.set('__WB_REVISION__', revision);\n      expect(matchStub.args).to.eql([[expectedURL.href]]);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-PrecacheRoute.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {PrecacheController} from 'workbox-precaching/PrecacheController.mjs';\nimport {PrecacheRoute} from 'workbox-precaching/PrecacheRoute.mjs';\n\nfunction createMatchParams(path) {\n  const request = new Request(path);\n  const url = new URL(request.url);\n  const event = new FetchEvent('event', {request});\n  return {request, url, event};\n}\n\ndescribe(`PrecacheRoute()`, function () {\n  describe(`.match`, function () {\n    it(`should only match precached urls`, async function () {\n      const pc = new PrecacheController();\n\n      pc.addToCacheList([\n        {url: '/one', revision: '123'},\n        {url: '/two', revision: '234'},\n      ]);\n\n      const pr = new PrecacheRoute(pc);\n\n      // Precached URLs\n      expect(pr.match(createMatchParams('/one'))).to.be.ok;\n      expect(pr.match(createMatchParams('/two'))).to.be.ok;\n\n      // Not precached URLs.\n      expect(pr.match(createMatchParams('/three'))).to.not.be.ok;\n    });\n\n    it(`matches precached urls with ignored params`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/?a=1&b=2`]);\n\n      const prWithIgnore = new PrecacheRoute(pc, {\n        ignoreURLParametersMatching: [/ignore/],\n      });\n      const prWithoutIgnore = new PrecacheRoute(pc);\n\n      expect(prWithIgnore.match(createMatchParams('/?a=1&ignore=me&b=2'))).to.be\n        .ok;\n      expect(prWithIgnore.match(createMatchParams('/?a=1&ignore=me&b=3'))).to\n        .not.be.ok;\n      expect(prWithoutIgnore.match(createMatchParams('/?a=1&ignore=me&b=2'))).to\n        .not.be.ok;\n    });\n\n    // Should we sort the search params to ensure that matches are consistent?\n    it.skip(`should match search params out of order`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/?a=1&b=2`]);\n\n      const prWithIgnore = new PrecacheRoute(pc, {\n        ignoreURLParametersMatching: [/ignore/],\n      });\n      const prWithoutIgnore = new PrecacheRoute(pc);\n\n      expect(prWithIgnore.match(createMatchParams('/?b=2&ignore=me&a=1'))).to.be\n        .ok;\n      expect(prWithoutIgnore.match(createMatchParams('/?b=2&ignore=me&a=1'))).to\n        .be.ok;\n    });\n\n    it(`should use the directoryIndex if the original request fails to match a cached URL`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/test-index.html`]);\n\n      const prWithIndex = new PrecacheRoute(pc, {\n        directoryIndex: 'test-index.html',\n      });\n      const prWithoutIndex = new PrecacheRoute(pc);\n\n      expect(prWithIndex.match(createMatchParams('/'))).to.be.ok;\n      expect(prWithoutIndex.match(createMatchParams('/'))).to.not.be.ok;\n    });\n\n    it(`should use the default directoryIndex of 'index.html'`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/index.html`]);\n\n      const pr = new PrecacheRoute(pc);\n\n      expect(pr.match(createMatchParams('/'))).to.be.ok;\n    });\n\n    it(`should use the cleanURLs of 'about.html'`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/about.html`]);\n\n      const pr = new PrecacheRoute(pc);\n\n      expect(pr.match(createMatchParams('/about'))).to.be.ok;\n    });\n\n    it(`should *not* use the cleanURLs of 'about.html' if set to false`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/about.html`]);\n\n      const pr = new PrecacheRoute(pc, {\n        cleanURLs: false,\n      });\n\n      expect(pr.match(createMatchParams('/about'))).to.not.be.ok;\n    });\n\n    it(`should use a custom urlManipulation function`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/123.html`]);\n\n      const pr = new PrecacheRoute(pc, {\n        urlManipulation: ({url}) => {\n          expect(url.pathname).to.equal('/');\n          const customURL = new URL(url);\n          customURL.pathname = '123.html';\n          return [customURL];\n        },\n      });\n\n      expect(pr.match(createMatchParams('/'))).to.be.ok;\n    });\n\n    it(`should return null if there is no match`, async function () {\n      const pc = new PrecacheController();\n      pc.addToCacheList([`/precached.html`]);\n\n      const pr = new PrecacheRoute(pc);\n\n      expect(pr.match(createMatchParams('/not-precached'))).to.not.be.ok;\n    });\n  });\n\n  describe('.handler', function () {\n    it(`should use the PrecacheController's strategy as the handler`, function () {\n      const pc = new PrecacheController();\n      const pr = new PrecacheRoute(pc);\n\n      expect(pc.strategy.handle).to.equal(pr.handler.handle);\n    });\n  });\n\n  describe('.method', function () {\n    it(`defaults to GET`, function () {\n      const pc = new PrecacheController();\n      const pr = new PrecacheRoute(pc);\n\n      expect(pr.method).to.equal('GET');\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-PrecacheStrategy.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {PrecacheStrategy} from 'workbox-precaching/PrecacheStrategy.mjs';\nimport {\n  eventDoneWaiting,\n  spyOnEvent,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nfunction createFetchEvent(url, requestInit) {\n  const event = new FetchEvent('fetch', {\n    request: new Request(url, requestInit),\n  });\n  spyOnEvent(event);\n  return event;\n}\n\ndescribe(`PrecacheStrategy()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`falls back to network by default on fetch`, async function () {\n      sandbox.stub(self, 'fetch').callsFake((request) => {\n        const response = new Response('Fetched Response');\n        sandbox.replaceGetter(response, 'url', () => request.url);\n        return response;\n      });\n\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      await cache.put(new Request('/one'), new Response('Cached Response'));\n\n      const ps = new PrecacheStrategy();\n\n      const response1 = await ps.handle(createFetchEvent('/one'));\n      expect(await response1.text()).to.equal('Cached Response');\n      expect(self.fetch.callCount).to.equal(0);\n\n      const response2 = await ps.handle(createFetchEvent('/two'));\n      expect(await response2.text()).to.equal('Fetched Response');\n      expect(self.fetch.callCount).to.equal(1);\n\n      // /two should not be there, since integrity isn't used.\n      const cachedUrls = (await cache.keys()).map((request) => request.url);\n      expect(cachedUrls).to.eql([`${location.origin}/one`]);\n    });\n\n    it(`falls back to network by default on fetch, and populates the cache if integrity is used`, async function () {\n      sandbox.stub(self, 'fetch').callsFake((request) => {\n        const response = new Response('Fetched Response');\n        sandbox.replaceGetter(response, 'url', () => request.url);\n        return response;\n      });\n\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      await cache.put(new Request('/one'), new Response('Cached Response'));\n\n      const ps = new PrecacheStrategy();\n\n      const response1 = await ps.handle(createFetchEvent('/one'));\n      expect(await response1.text()).to.equal('Cached Response');\n      expect(self.fetch.callCount).to.equal(0);\n\n      const integrity = 'some-hash';\n      const request = new Request('/two', {\n        integrity,\n      });\n      const event = createFetchEvent(request.url, request);\n      const response2 = await ps.handle({\n        event,\n        request,\n        params: {\n          integrity,\n        },\n      });\n      expect(await response2.text()).to.equal('Fetched Response');\n      expect(self.fetch.callCount).to.equal(1);\n\n      // No integrity is used, so it shouldn't populate cache.\n      const response3 = await ps.handle(createFetchEvent('/three'));\n      expect(await response3.text()).to.equal('Fetched Response');\n      expect(self.fetch.callCount).to.equal(2);\n\n      // This should not populate the cache, because the params.integrity\n      // doesn't match the request.integrity.\n      const request4 = new Request('/four', {\n        integrity,\n      });\n      const event4 = createFetchEvent(request4.url, request4);\n      const response4 = await ps.handle({\n        event: event4,\n        request: request4,\n        params: {\n          integrity: 'does-not-match',\n        },\n      });\n      expect(await response4.text()).to.equal('Fetched Response');\n      expect(self.fetch.callCount).to.equal(3);\n\n      // This should not populate the cache, because the request is no-cors\n      // so we won't use integrity and won't populate the cache\n      const request5 = new Request('/five', {\n        integrity,\n        mode: 'no-cors',\n      });\n      const event5 = createFetchEvent(request.url, request);\n      const response5 = await ps.handle({\n        event: event5,\n        request: request5,\n        params: {\n          integrity,\n        },\n      });\n\n      expect(await response5.text()).to.equal('Fetched Response');\n      expect(self.fetch.callCount).to.equal(4);\n\n      // /two should be there, since request.integrity matches params.integrity.\n      // /three and /four shouldn't.\n      const cachedUrls = (await cache.keys()).map((request) => request.url);\n      expect(cachedUrls).to.eql([\n        `${location.origin}/one`,\n        `${location.origin}/two`,\n      ]);\n    });\n\n    it(`just checks cache if fallbackToNetwork is false`, async function () {\n      sandbox.stub(self, 'fetch').callsFake((request) => {\n        const response = new Response('Fetched Response');\n        sandbox.replaceGetter(response, 'url', () => request.url);\n        return response;\n      });\n\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      await cache.put(new Request('/one'), new Response('Cached Response'));\n\n      const ps = new PrecacheStrategy({fallbackToNetwork: false});\n\n      const response1 = await ps.handle(createFetchEvent('/one'));\n      expect(await response1.text()).to.equal('Cached Response');\n      expect(self.fetch.callCount).to.equal(0);\n\n      await expectError(\n        () => ps.handle(createFetchEvent('/two')),\n        'missing-precache-entry',\n      );\n    });\n\n    it(`copies redirected responses`, async function () {\n      sandbox.stub(self, 'fetch').callsFake((request) => {\n        const response = new Response('Redirected Response');\n        sandbox.replaceGetter(response, 'redirected', () => true);\n        return Promise.resolve(response);\n      });\n\n      const request = new Request('/index.html');\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const ps = new PrecacheStrategy();\n      const strategyResponse = await ps.handle({event, request});\n\n      expect(strategyResponse.redirected).to.equal(true);\n      expect(await strategyResponse.text()).to.equal('Redirected Response');\n\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getPrecacheName());\n      const cachedResponse = await cache.match(request);\n\n      expect(cachedResponse.redirected).to.equal(false);\n      expect(await cachedResponse.text()).to.equal('Redirected Response');\n    });\n\n    it(`errors during install if the default plugin returns null`, async function () {\n      // Also ensure that we don't cache the bad response;\n      // see https://github.com/GoogleChrome/workbox/issues/2737\n      const putStub = sandbox.stub().resolves();\n      sandbox.stub(self.caches, 'open').resolves({put: putStub});\n\n      sandbox.stub(self, 'fetch').resolves(\n        new Response('Server Error', {\n          status: 400,\n        }),\n      );\n\n      const defaultPluginSpy = sandbox.spy(\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n        'cacheWillUpdate',\n      );\n\n      const request = new Request('/index.html');\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const ps = new PrecacheStrategy();\n      await expectError(\n        () => ps.handle({event, request}),\n        'bad-precaching-response',\n      );\n\n      await eventDoneWaiting(event);\n      expect(putStub.callCount).to.eql(0);\n      // Confirm that the default plugin was called.\n      expect(defaultPluginSpy.callCount).to.eql(1);\n    });\n\n    it(`doesn't error during install if the cacheWillUpdate plugin allows it`, async function () {\n      const errorResponse = new Response('Server Error', {\n        status: 400,\n      });\n\n      const putStub = sandbox.stub().resolves();\n      sandbox.stub(self.caches, 'open').resolves({put: putStub});\n\n      sandbox.stub(self, 'fetch').resolves(errorResponse);\n\n      // Returning any valid Response will allow caching to proceed.\n      const cacheWillUpdateStub = sandbox.stub().resolves(errorResponse);\n      const defaultPluginSpy = sandbox.spy(\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n        'cacheWillUpdate',\n      );\n\n      const request = new Request('/index.html');\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const ps = new PrecacheStrategy({\n        plugins: [\n          {\n            cacheWillUpdate: cacheWillUpdateStub,\n          },\n        ],\n      });\n\n      const response = await ps.handle({event, request});\n      // The return value should be whatever fetch() returned.\n      expect(response).to.eql(errorResponse);\n\n      await eventDoneWaiting(event);\n\n      expect(putStub.args).to.eql([[request, errorResponse]]);\n      expect(cacheWillUpdateStub.callCount).to.eql(1);\n      // The default plugin shouldn't be called if there's custom plugin(s).\n      expect(defaultPluginSpy.callCount).to.eql(0);\n    });\n\n    it(`errors during install if any of the cacheWillUpdate plugins return null`, async function () {\n      const errorResponse = new Response('Server Error', {\n        status: 400,\n      });\n\n      const putStub = sandbox.stub().resolves();\n      sandbox.stub(self.caches, 'open').resolves({put: putStub});\n\n      sandbox.stub(self, 'fetch').resolves(errorResponse);\n\n      const cacheWillUpdateAllowStub = sandbox.stub().resolves(errorResponse);\n      const cacheWillUpdateDenyStub = sandbox.stub().resolves(null);\n\n      const defaultPluginSpy = sandbox.spy(\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n        'cacheWillUpdate',\n      );\n\n      const request = new Request('/index.html');\n      const event = new ExtendableEvent('install');\n      spyOnEvent(event);\n\n      const ps = new PrecacheStrategy({\n        plugins: [\n          {\n            cacheWillUpdate: cacheWillUpdateAllowStub,\n          },\n          {\n            cacheWillUpdate: cacheWillUpdateDenyStub,\n          },\n        ],\n      });\n\n      await expectError(\n        () => ps.handle({event, request}),\n        'bad-precaching-response',\n      );\n\n      await eventDoneWaiting(event);\n\n      expect(putStub.callCount).to.eql(0);\n      expect(cacheWillUpdateAllowStub.callCount).to.eql(1);\n      expect(cacheWillUpdateDenyStub.callCount).to.eql(1);\n      expect(defaultPluginSpy.callCount).to.eql(0);\n    });\n  });\n\n  describe('_useDefaultCacheabilityPluginIfNeeded()', function () {\n    it(`should include the expected plugins by default`, async function () {\n      const ps = new PrecacheStrategy();\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n      ]);\n\n      // Confirm that calling it multiple times doesn't change anything.\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n      ]);\n    });\n\n    it(`should include the default plugin when the strategy has only non-cacheWillUpdate plugins`, async function () {\n      const cacheKeyWillBeUsedPlugin = {\n        cacheKeyWillBeUsed: sandbox.stub(),\n      };\n      const ps = new PrecacheStrategy({\n        plugins: [cacheKeyWillBeUsedPlugin],\n      });\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheKeyWillBeUsedPlugin,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n      ]);\n\n      // Confirm that calling it multiple times doesn't change anything.\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheKeyWillBeUsedPlugin,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n      ]);\n    });\n\n    it(`should not include the default plugin when the strategy has one cacheWillUpdate plugin`, async function () {\n      const cacheWillUpdatePlugin = {\n        cacheWillUpdate: sandbox.stub(),\n      };\n      const ps = new PrecacheStrategy({\n        plugins: [cacheWillUpdatePlugin],\n      });\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheWillUpdatePlugin,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n      ]);\n\n      // Confirm that calling it multiple times doesn't change anything.\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheWillUpdatePlugin,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n      ]);\n    });\n\n    it(`should not include the default plugin when the strategy has multiple cacheWillUpdate plugins`, async function () {\n      const cacheWillUpdatePlugin1 = {\n        cacheWillUpdate: sandbox.stub(),\n      };\n      const cacheWillUpdatePlugin2 = {\n        cacheWillUpdate: sandbox.stub(),\n      };\n      const cacheKeyWillBeUsedPlugin = {\n        cacheKeyWillBeUsed: sandbox.stub(),\n      };\n      const ps = new PrecacheStrategy({\n        plugins: [\n          cacheWillUpdatePlugin1,\n          cacheKeyWillBeUsedPlugin,\n          cacheWillUpdatePlugin2,\n        ],\n      });\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheWillUpdatePlugin1,\n        cacheKeyWillBeUsedPlugin,\n        cacheWillUpdatePlugin2,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n      ]);\n\n      // Confirm that calling it multiple times doesn't change anything.\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        cacheWillUpdatePlugin1,\n        cacheKeyWillBeUsedPlugin,\n        cacheWillUpdatePlugin2,\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n      ]);\n    });\n\n    it(`should remove the default plugin if a cacheWillUpdate plugin has been added after the initial call`, async function () {\n      const cacheWillUpdatePlugin = {\n        cacheWillUpdate: sandbox.stub(),\n      };\n      const ps = new PrecacheStrategy();\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        PrecacheStrategy.defaultPrecacheCacheabilityPlugin,\n      ]);\n\n      // Explicitly add a cacheWillUpdate plugin. Real users will likely do this\n      // via the addPlugins() method.\n      ps.plugins.push(cacheWillUpdatePlugin);\n\n      ps._useDefaultCacheabilityPluginIfNeeded();\n\n      expect(ps.plugins).to.eql([\n        PrecacheStrategy.copyRedirectedCacheableResponsesPlugin,\n        cacheWillUpdatePlugin,\n      ]);\n    });\n  });\n\n  describe('defaultPrecacheCacheabilityPlugin', function () {\n    it(`should return the same response when the status is 200`, async function () {\n      const response = new Response('', {status: 200});\n\n      const returnedResponse =\n        await PrecacheStrategy.defaultPrecacheCacheabilityPlugin.cacheWillUpdate(\n          {\n            response,\n          },\n        );\n\n      expect(returnedResponse).to.eql(response);\n    });\n\n    it(`should return the same response when the status is 0`, async function () {\n      // You can't construct opaque responses, so stub out the getter.\n      const response = new Response('', {status: 599});\n      sandbox.stub(response, 'status').get(() => 0);\n\n      const returnedResponse =\n        await PrecacheStrategy.defaultPrecacheCacheabilityPlugin.cacheWillUpdate(\n          {\n            response,\n          },\n        );\n\n      expect(returnedResponse).to.eql(response);\n    });\n\n    it(`should return null when the status is 404`, async function () {\n      const response = new Response('', {status: 404});\n\n      const returnedResponse =\n        await PrecacheStrategy.defaultPrecacheCacheabilityPlugin.cacheWillUpdate(\n          {\n            response,\n          },\n        );\n\n      expect(returnedResponse).to.be.null;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-addPlugins.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {addPlugins} from 'workbox-precaching/addPlugins.mjs';\nimport {precache} from 'workbox-precaching/precache.mjs';\nimport {getOrCreatePrecacheController} from 'workbox-precaching/utils/getOrCreatePrecacheController.mjs';\n\ndescribe(`addPlugins()`, function () {\n  it(`should add plugins to the strategy`, async function () {\n    const plugin1 = {};\n    const plugin2 = {};\n\n    precache([{url: '/', revision: null}]);\n    addPlugins([plugin1]);\n\n    const pc = getOrCreatePrecacheController();\n    expect(pc.strategy.plugins).to.include(plugin1);\n    expect(pc.strategy.plugins).not.to.include(plugin2);\n\n    addPlugins([plugin2]);\n\n    expect(pc.strategy.plugins).to.include(plugin1);\n    expect(pc.strategy.plugins).to.include(plugin2);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-addRoute.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {addRoute} from 'workbox-precaching/addRoute.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`addRoute()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  function getAddedFetchListeners() {\n    return self.addEventListener.args.filter(([type]) => type === 'fetch');\n  }\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should add at most 1 fetch listener`, async function () {\n    addRoute();\n\n    const callCountAfterFirstCall = getAddedFetchListeners().length;\n\n    // Depending on the order of when tests run, this will be 0 or 1.\n    expect(callCountAfterFirstCall).to.be.oneOf([0, 1]);\n\n    addRoute();\n    addRoute();\n    addRoute();\n\n    expect(getAddedFetchListeners().length).to.equal(callCountAfterFirstCall);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-cleanupOutdatedCaches.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cleanupOutdatedCaches} from 'workbox-precaching/cleanupOutdatedCaches.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`cleanupOutdatedCaches()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should add an activate listener`, async function () {\n    const addEventListenerSpy = sandbox.spy(self, 'addEventListener');\n    cleanupOutdatedCaches();\n\n    expect(addEventListenerSpy.calledOnce).to.be.true;\n    expect(addEventListenerSpy.firstCall.args[0]).to.eql('activate');\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-createHandlerBoundToURL.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\nimport {spyOnEvent} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nimport {createHandlerBoundToURL} from 'workbox-precaching/createHandlerBoundToURL.mjs';\nimport {precache} from 'workbox-precaching/precache.mjs';\n\ndescribe(`createHandlerBoundToURL()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.stub(self, 'addEventListener');\n    resetDefaultPrecacheController();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should throw when passed a URL that isn't precached`, function () {\n    precache([]);\n\n    return expectError(\n      () => {\n        createHandlerBoundToURL('/does-not-exist');\n      },\n      'non-precached-url',\n      (error) => expect(error.details.url).to.eql('/does-not-exist'),\n    );\n  });\n\n  it(`should return the expected handlerCallback for precached URLs`, async function () {\n    // Simulate the following: first two handlerCallbacks have cache.match()\n    // calls that return a hit. Third, and subsequent handlerCallback has a\n    // cache.match() call that's a miss, which will lead to a call to fetch().\n    const matchStub = sandbox\n      .stub(self.caches, 'match')\n      .onFirstCall()\n      .resolves(new Response('response 1'))\n      .onSecondCall()\n      .resolves(new Response('response 2'))\n      .resolves(undefined);\n\n    const fetchStub = sandbox\n      .stub(self, 'fetch')\n      .onFirstCall()\n      .resolves(new Response('response 3'))\n      .onSecondCall()\n      .resolves(new Response('response 4'));\n\n    precache([\n      '/url1',\n      {url: '/url2', revision: 'abc123'},\n      '/url3',\n      {url: '/url4', revision: 'def456'},\n    ]);\n\n    const event = new ExtendableEvent('fetch');\n    spyOnEvent(event);\n\n    const handler1 = createHandlerBoundToURL('/url1');\n    const response1 = await handler1({event});\n\n    expect(matchStub.calledOnce).to.be.true;\n    expect(matchStub.firstCall.args[0].url).to.eql(`${location.origin}/url1`);\n    expect(fetchStub.notCalled).to.be.true;\n    expect(await response1.text()).to.eql('response 1');\n\n    const handler2 = createHandlerBoundToURL('/url2');\n    const response2 = await handler2({event});\n\n    expect(matchStub.calledTwice).to.be.true;\n    expect(matchStub.secondCall.args[0].url).to.eql(\n      `${location.origin}/url2?__WB_REVISION__=abc123`,\n    );\n    expect(fetchStub.notCalled).to.be.true;\n    expect(await response2.text()).to.eql('response 2');\n\n    const handler3 = createHandlerBoundToURL('/url3');\n    const response3 = await handler3({event});\n\n    expect(matchStub.calledThrice).to.be.true;\n    expect(matchStub.thirdCall.args[0].url).to.eql(`${location.origin}/url3`);\n    expect(fetchStub.calledOnce).to.be.true;\n    expect(fetchStub.firstCall.args[0].url).to.eql(`${location.origin}/url3`);\n    expect(await response3.text()).to.eql('response 3');\n\n    const handler4 = createHandlerBoundToURL('/url4');\n    const response4 = await handler4({event});\n\n    expect(matchStub.callCount).to.eql(4);\n    // Call #3 is the fourth call due to zero-indexing.\n    expect(matchStub.getCall(3).args[0].url).to.eql(\n      `${location.origin}/url4?__WB_REVISION__=def456`,\n    );\n    expect(fetchStub.calledTwice).to.be.true;\n    expect(fetchStub.secondCall.args[0].url).to.eql(`${location.origin}/url4`);\n    expect(await response4.text()).to.eql('response 4');\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-getCacheKeyForURL.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {getCacheKeyForURL} from 'workbox-precaching/getCacheKeyForURL.mjs';\nimport {precache} from 'workbox-precaching/precache.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`getCacheKeyForURL()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should return the expected cache keys for various URLs`, async function () {\n    precache(['/one', {url: '/two', revision: '1234'}]);\n\n    expect(getCacheKeyForURL('/one')).to.eql(`${location.origin}/one`);\n    expect(getCacheKeyForURL(`${location.origin}/two`)).to.eql(\n      `${location.origin}/two?__WB_REVISION__=1234`,\n    );\n    expect(getCacheKeyForURL('/not-precached')).to.not.exist;\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-matchPrecache.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {matchPrecache} from 'workbox-precaching/matchPrecache.mjs';\nimport {precache} from 'workbox-precaching/precache.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`matchPrecache()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.stub(self, 'addEventListener');\n    resetDefaultPrecacheController();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  // This is all into one big test with multiple expects() as that plays nicer\n  // with precache()'s behavior.\n  it(`should behave as expected`, async function () {\n    const matchStub = sandbox\n      .stub()\n      .onFirstCall()\n      .resolves(new Response('response 1'))\n      .onSecondCall()\n      .resolves(new Response('response 2'))\n      .resolves(undefined);\n\n    sandbox.stub(self.caches, 'open').resolves({\n      match: matchStub,\n    });\n\n    precache(['/url1', {url: '/url2', revision: 'abc123'}]);\n\n    const noMatchResponse = await matchPrecache('does-not-match');\n    expect(noMatchResponse).to.be.undefined;\n    expect(matchStub.notCalled).to.be.true;\n\n    const response1 = await matchPrecache('/url1');\n\n    expect(matchStub.calledOnce).to.be.true;\n    expect(matchStub.firstCall.args).to.eql([`${location.origin}/url1`]);\n    expect(await response1.text()).to.eql('response 1');\n\n    const response2 = await matchPrecache(new Request('/url2'));\n\n    expect(matchStub.calledTwice).to.be.true;\n    expect(matchStub.secondCall.args).to.eql([\n      `${location.origin}/url2?__WB_REVISION__=abc123`,\n    ]);\n    expect(await response2.text()).to.eql('response 2');\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-precache.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {precache} from 'workbox-precaching/precache.mjs';\nimport {PrecacheController} from 'workbox-precaching/PrecacheController.mjs';\nimport {getOrCreatePrecacheController} from 'workbox-precaching/utils/getOrCreatePrecacheController.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\ndescribe(`precache()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n  });\n\n  // The `addFetchListener` method adds a listener only the first time it's invoked,\n  // so we can't remove that listener until all tests are run.\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should call install and activate on install and activate`, async function () {\n    const pc = getOrCreatePrecacheController();\n\n    sandbox.spy(pc, 'install');\n    sandbox.spy(pc, 'activate');\n\n    precache(['/']);\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('install'));\n\n    expect(pc.install.callCount).to.equal(1);\n    expect(pc.activate.callCount).to.equal(0);\n\n    await dispatchAndWaitUntilDone(new ExtendableEvent('activate'));\n\n    expect(pc.install.callCount).to.equal(1);\n    expect(pc.activate.callCount).to.equal(1);\n  });\n\n  it(`should add entries to the default PrecacheController cache list`, async function () {\n    sandbox.spy(PrecacheController.prototype, 'addToCacheList');\n\n    precache(['/one', '/two', '/three']);\n\n    expect(PrecacheController.prototype.addToCacheList.callCount).to.equal(1);\n    expect(\n      PrecacheController.prototype.addToCacheList.args[0][0],\n    ).to.deep.equal(['/one', '/two', '/three']);\n  });\n\n  it(`shouldn't throw when precaching assets`, function () {\n    precache([\n      'index.1234.html',\n      {\n        url: 'test.1234.html',\n      },\n      {\n        url: 'testing.html',\n        revision: '1234',\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/test-precacheAndRoute.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {precacheAndRoute} from 'workbox-precaching/precacheAndRoute.mjs';\nimport {PrecacheController} from 'workbox-precaching/PrecacheController.mjs';\nimport {PrecacheRoute} from 'workbox-precaching/PrecacheRoute.mjs';\nimport {Router} from 'workbox-routing/Router.mjs';\nimport {resetDefaultPrecacheController} from './resetDefaultPrecacheController.mjs';\n\ndescribe(`precacheAndRoute()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    resetDefaultPrecacheController();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n  });\n\n  // The `addFetchListener` method adds a listener only the first time it's invoked,\n  // so we can't remove that listener until all tests are run.\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should call precache() and addRoute() without args`, function () {\n    const precache = sandbox.stub(PrecacheController.prototype, 'precache');\n    const registerRoute = sandbox.stub(Router.prototype, 'registerRoute');\n\n    precacheAndRoute();\n    expect(precache.callCount).to.equal(1);\n    expect(precache.args[0]).to.deep.equal([undefined]);\n    expect(registerRoute.callCount).to.equal(1);\n    expect(registerRoute.args[0][0]).to.be.instanceOf(PrecacheRoute);\n  });\n\n  it(`should call precache() and addRoute() with args`, function () {\n    const precache = sandbox.stub(PrecacheController.prototype, 'precache');\n    const registerRoute = sandbox.stub(Router.prototype, 'registerRoute');\n\n    const precacheArgs = ['/'];\n    const routeOptions = {\n      ignoreURLParametersMatching: [/utm_/],\n      directoryIndex: 'example.html',\n    };\n\n    precacheAndRoute(precacheArgs, routeOptions);\n    expect(precache.callCount).to.equal(1);\n    expect(precache.args[0][0]).to.equal(precacheArgs);\n    expect(registerRoute.callCount).to.equal(1);\n    expect(registerRoute.args[0][0]).to.be.instanceOf(PrecacheRoute);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/utils/test-deleteOutdatedCaches.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {deleteOutdatedCaches} from 'workbox-precaching/utils/deleteOutdatedCaches.mjs';\n\ndescribe(`deleteOutdatedCaches()`, function () {\n  const CACHE_NAME = 'expected-precache-name';\n\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  after(function () {\n    sandbox.restore();\n  });\n\n  it(`should not do anything when there are no caches`, async function () {\n    sandbox.stub(caches, 'keys').resolves([]);\n    const cachesDeleteStub = sandbox.stub(caches, 'delete').resolves();\n\n    const cachesDeleted = await deleteOutdatedCaches(CACHE_NAME);\n\n    expect(cachesDeleted).to.be.empty;\n    expect(cachesDeleteStub.notCalled).to.be.true;\n  });\n\n  it(`should not do anything when there is only the expected cache`, async function () {\n    sandbox.stub(caches, 'keys').resolves([CACHE_NAME]);\n    const cachesDeleteStub = sandbox.stub(caches, 'delete').resolves();\n\n    const cachesDeleted = await deleteOutdatedCaches(CACHE_NAME);\n\n    expect(cachesDeleted).to.be.empty;\n    expect(cachesDeleteStub.notCalled).to.be.true;\n  });\n\n  it(`should delete everything that matches the deletion criteria`, async function () {\n    sandbox\n      .stub(caches, 'keys')\n      .resolves([\n        CACHE_NAME,\n        `this-precache-should-be-deleted1-${self.registration.scope}`,\n        `this-precache-should-be-deleted2-${self.registration.scope}`,\n      ]);\n    const cachesDeleteStub = sandbox.stub(caches, 'delete').resolves();\n\n    const cachesDeleted = await deleteOutdatedCaches(CACHE_NAME);\n\n    expect(cachesDeleted).to.have.members([\n      `this-precache-should-be-deleted1-${self.registration.scope}`,\n      `this-precache-should-be-deleted2-${self.registration.scope}`,\n    ]);\n    expect(cachesDeleteStub.calledTwice).to.be.true;\n    expect(cachesDeleteStub.firstCall.args).to.eql([\n      `this-precache-should-be-deleted1-${self.registration.scope}`,\n    ]);\n    expect(cachesDeleteStub.secondCall.args).to.eql([\n      `this-precache-should-be-deleted2-${self.registration.scope}`,\n    ]);\n  });\n\n  it(`should take SW scope into consideration as part of the criteria`, async function () {\n    sandbox\n      .stub(caches, 'keys')\n      .resolves([\n        CACHE_NAME,\n        `this-precache-should-not-be-deleted-no-scope-match`,\n        `this-precache-should-be-deleted-${self.registration.scope}`,\n      ]);\n    const cachesDeleteStub = sandbox.stub(caches, 'delete').resolves();\n\n    const cachesDeleted = await deleteOutdatedCaches(CACHE_NAME);\n\n    expect(cachesDeleted).to.have.members([\n      `this-precache-should-be-deleted-${self.registration.scope}`,\n    ]);\n    expect(cachesDeleteStub.calledOnce).to.be.true;\n    expect(cachesDeleteStub.firstCall.args).to.eql([\n      `this-precache-should-be-deleted-${self.registration.scope}`,\n    ]);\n  });\n\n  it(`should support overriding the default '-precache-' substring criteria`, async function () {\n    sandbox\n      .stub(caches, 'keys')\n      .resolves([\n        CACHE_NAME,\n        `this-precache-should-not-be-deleted-${self.registration.scope}`,\n        `this-PRECACHE-should-be-deleted1-${self.registration.scope}`,\n        `this-PRECACHE-should-be-deleted2-${self.registration.scope}`,\n      ]);\n    const cachesDeleteStub = sandbox.stub(caches, 'delete').resolves();\n\n    const cachesDeleted = await deleteOutdatedCaches(CACHE_NAME, '-PRECACHE-');\n\n    expect(cachesDeleted).to.have.members([\n      `this-PRECACHE-should-be-deleted1-${self.registration.scope}`,\n      `this-PRECACHE-should-be-deleted2-${self.registration.scope}`,\n    ]);\n    expect(cachesDeleteStub.calledTwice).to.be.true;\n    expect(cachesDeleteStub.firstCall.args).to.eql([\n      `this-PRECACHE-should-be-deleted1-${self.registration.scope}`,\n    ]);\n    expect(cachesDeleteStub.secondCall.args).to.eql([\n      `this-PRECACHE-should-be-deleted2-${self.registration.scope}`,\n    ]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/utils/test-printCleanupDetails.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {printCleanupDetails} from 'workbox-precaching/utils/printCleanupDetails.mjs';\n\ndescribe(`printCleanupDetails()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    if (logger) {\n      sandbox.spy(logger, 'log');\n    }\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`shouldn't print if nothing was deleted`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    printCleanupDetails([], []);\n\n    expect(logger.log.callCount).to.equal(0);\n  });\n\n  it(`should print at least one entry was delete`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    printCleanupDetails(['/'], ['/']);\n\n    expect(logger.log.callCount).to.be.gt(0);\n  });\n\n  it(`should print strings with multiple entries`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    printCleanupDetails(['/', '/2'], ['/', '/2']);\n\n    expect(logger.log.callCount).to.be.gt(0);\n  });\n});\n"
  },
  {
    "path": "test/workbox-precaching/sw/utils/test-printInstallDetails.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {printInstallDetails} from 'workbox-precaching/utils/printInstallDetails.mjs';\n\ndescribe(`printInstallDetails()`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    if (logger) {\n      sandbox.spy(logger, 'log');\n    }\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  it(`should print with single update`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    printInstallDetails([], ['/index.html']);\n\n    expect(logger.log.callCount).to.equal(1);\n  });\n});\n"
  },
  {
    "path": "test/workbox-range-requests/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-range-requests]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-range-requests/sw/');\n  });\n});\n\ndescribe(`[workbox-range-requests] Plugin`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-range-requests/static/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(webdriver, testingURL);\n  });\n\n  it(`should return a partial response that satisfies the request's Range: header, and an error response when it can't be satisfied`, async function () {\n    const swURL = `${testingURL}sw.js`;\n    await activateAndControlSW(swURL);\n\n    const dummyURL = `this-file-doesnt-exist.txt`;\n\n    const partialResponseBody = await webdriver.executeAsyncScript(\n      (dummyURL, cb) => {\n        // Prime the cache, and then make the Range: request.\n        fetch(new Request(dummyURL, {headers: {Range: `bytes=5-6`}}))\n          .then((response) => response.text())\n          .then((text) => cb(text))\n          .catch((error) => cb(error.message));\n      },\n      dummyURL,\n    );\n\n    // The values used for the byte range are inclusive, so we'll end up with\n    // 11 characters returned in the partial response.\n    expect(partialResponseBody).to.eql('56');\n\n    const errorResponseStatus = await webdriver.executeAsyncScript(\n      (dummyURL, cb) => {\n        // These are arbitrary large values that extend past the end of the file.\n        fetch(new Request(dummyURL, {headers: {Range: `bytes=100-101`}})).then(\n          (response) => cb(response.status),\n        );\n      },\n      dummyURL,\n    );\n\n    // The expected error status is 416 (Range Not Satisfiable)\n    expect(errorResponseStatus).to.eql(416);\n  });\n});\n"
  },
  {
    "path": "test/workbox-range-requests/static/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n    <script>\n      window.__test = {};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-range-requests/static/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-range-requests');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-strategies');\n\nconst cacheName = 'range-requests-integration-test';\nworkbox.routing.registerRoute(\n  new RegExp('this-file-doesnt-exist\\\\.txt$'),\n  new workbox.strategies.CacheOnly({\n    cacheName,\n    plugins: [new workbox.rangeRequests.RangeRequestsPlugin()],\n  }),\n);\n\nself.addEventListener('install', (event) => {\n  self.skipWaiting();\n  event.waitUntil(\n    caches.open(cacheName).then((cache) => {\n      return cache.put(\n        'this-file-doesnt-exist.txt',\n        new Response('0123456789'),\n      );\n    }),\n  );\n});\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-range-requests/sw/test-RangeRequestsPlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RangeRequestsPlugin} from 'workbox-range-requests/RangeRequestsPlugin.mjs';\n\ndescribe(`RangeRequestsPlugin`, function () {\n  it(`should construct with no values`, function () {\n    new RangeRequestsPlugin();\n  });\n\n  it(`should return an untouched response if there's no Range: request header`, async function () {\n    const response = new Response();\n\n    const plugin = new RangeRequestsPlugin();\n    const resultResponse = await plugin.cachedResponseWillBeUsed({\n      request: new Request('/'),\n      cachedResponse: response,\n    });\n    expect(resultResponse).to.equal(response);\n  });\n\n  it(`should return partial response response if there's a valid Range: request header`, async function () {\n    const response = new Response('hello, world.');\n\n    const plugin = new RangeRequestsPlugin();\n    const resultResponse = await plugin.cachedResponseWillBeUsed({\n      request: new Request('/', {\n        headers: {\n          range: 'bytes=1-4',\n        },\n      }),\n      cachedResponse: response,\n    });\n    expect(resultResponse).to.not.equal(response);\n\n    const responseBody = await resultResponse.text();\n    expect(responseBody).to.equal('ello');\n  });\n\n  it(`should return null when the cachedResponse is null`, async function () {\n    const cachedResponse = null;\n    const plugin = new RangeRequestsPlugin();\n    const resultResponse = await plugin.cachedResponseWillBeUsed({\n      request: new Request('/', {\n        headers: {\n          range: 'bytes=1-4',\n        },\n      }),\n      cachedResponse,\n    });\n    expect(resultResponse).to.eql(null);\n  });\n});\n"
  },
  {
    "path": "test/workbox-range-requests/sw/test-createPartialResponse.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {createPartialResponse} from 'workbox-range-requests/createPartialResponse.mjs';\n\ndescribe(`createPartialResponse()`, function () {\n  // This uses an interface that matches what our Blob mock currently supports.\n  // It's *not* the same way we'd use native browser implementation.\n  function constructBlob(length) {\n    let string = '';\n    for (let i = 0; i < length; i++) {\n      string += i % 10;\n    }\n    return new Blob([string]);\n  }\n\n  const SOURCE_BLOB_SIZE = 256;\n  const SOURCE_BLOB = constructBlob(SOURCE_BLOB_SIZE);\n\n  describe(`Tests for the createPartialResponse() function`, function () {\n    const VALID_REQUEST = new Request('/', {\n      headers: {\n        range: 'bytes=100-200',\n      },\n    });\n\n    it(`should return a Response with status 416 when the 'request' parameter isn't valid`, async function () {\n      const response = await createPartialResponse(\n        null,\n        new Response(SOURCE_BLOB),\n      );\n      expect(response.status).to.equal(416);\n    });\n\n    it(`should return a Response with status 416 when the 'response' parameter isn't valid`, async function () {\n      const response = await createPartialResponse(VALID_REQUEST, null);\n      expect(response.status).to.equal(416);\n    });\n\n    it(`should return a Response with status 416 when there's no Range: header in the request`, async function () {\n      const noRangeHeaderRequest = new Request('/');\n      const response = await createPartialResponse(\n        noRangeHeaderRequest,\n        new Response(SOURCE_BLOB),\n      );\n      expect(response.status).to.equal(416);\n    });\n\n    it(`should return the expected Response when it's called with valid parameters`, async function () {\n      const response = await createPartialResponse(\n        VALID_REQUEST,\n        new Response(SOURCE_BLOB),\n      );\n      expect(response.status).to.equal(206);\n      expect(response.headers.get('Content-Length')).to.equal('101');\n      expect(response.headers.get('Content-Range')).to.equal(\n        `bytes 100-200/${SOURCE_BLOB_SIZE}`,\n      );\n\n      const responseBlob = await response.blob();\n      const expectedBlob = constructBlob(101);\n\n      expect(await new Response(responseBlob).text()).to.equal(\n        await new Response(expectedBlob).text(),\n      );\n    });\n\n    it(`should return the full body when it's called with bytes=0-`, async function () {\n      const fullBodyRequest = new Request('/', {\n        headers: {\n          range: 'bytes=0-',\n        },\n      });\n      const response = await createPartialResponse(\n        fullBodyRequest,\n        new Response(SOURCE_BLOB),\n      );\n      expect(response.status).to.equal(206);\n      expect(response.headers.get('Content-Length')).to.equal(\n        `${SOURCE_BLOB_SIZE}`,\n      );\n      expect(response.headers.get('Content-Range')).to.equal(\n        `bytes 0-${SOURCE_BLOB_SIZE - 1}/${SOURCE_BLOB_SIZE}`,\n      );\n\n      const responseBlob = await response.blob();\n      const expectedBlob = constructBlob(SOURCE_BLOB_SIZE);\n\n      expect(await new Response(responseBlob).text()).to.equal(\n        await new Response(expectedBlob).text(),\n      );\n    });\n\n    it(`should handle being passed a Response with a status of 206 by returning it as-is`, async function () {\n      const originalPartialResponse = new Response('expected text', {\n        status: 206,\n      });\n      const createdPartialResponse = await createPartialResponse(\n        VALID_REQUEST,\n        originalPartialResponse,\n      );\n\n      // We should get back the exact same response.\n      expect(createdPartialResponse).to.equal(originalPartialResponse);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-range-requests/sw/utils/test-calculateEffectiveBoundaries.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {calculateEffectiveBoundaries} from 'workbox-range-requests/utils/calculateEffectiveBoundaries.mjs';\n\ndescribe(`calculateEffectiveBoundaries()`, function () {\n  function constructBlob(length) {\n    let string = '';\n    for (let i = 0; i < length; i++) {\n      string += i % 10;\n    }\n    return new Blob([string]);\n  }\n\n  const SOURCE_BLOB_SIZE = 256;\n  const SOURCE_BLOB = constructBlob(SOURCE_BLOB_SIZE);\n\n  it(`should throw when it's is called with an invalid 'blob' parameter`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    const invalidBlob = null;\n    await expectError(\n      () => calculateEffectiveBoundaries(invalidBlob),\n      'incorrect-class',\n      (error) => {\n        expect(error.details).to.have.property(\n          'moduleName',\n          'workbox-range-requests',\n        );\n        expect(error.details).to.have.property(\n          'funcName',\n          'calculateEffectiveBoundaries',\n        );\n        expect(error.details).to.have.property('paramName', 'blob');\n        expect(error.details).to.have.property('expectedClassName', 'Blob');\n      },\n    );\n  });\n\n  it(`should throw when it's is called with a 'start' parameter less than zero`, async function () {\n    const start = -1;\n    const end = 1;\n    await expectError(\n      () => calculateEffectiveBoundaries(SOURCE_BLOB, start, end),\n      'range-not-satisfiable',\n    );\n  });\n\n  it(`should throw when it's is called with an 'end' parameter larger than the blob's size`, async function () {\n    const start = 0;\n    const end = SOURCE_BLOB_SIZE + 1;\n    await expectError(\n      () => calculateEffectiveBoundaries(SOURCE_BLOB, start, end),\n      'range-not-satisfiable',\n    );\n  });\n\n  it(`should return the expected boundaries when it's called with valid parameters`, function () {\n    const testCases = [\n      [\n        {start: 100, end: 200},\n        {start: 100, end: 201},\n      ],\n      [\n        {start: undefined, end: 200},\n        {start: 56, end: 256},\n      ],\n      [\n        {start: 100, end: undefined},\n        {start: 100, end: 256},\n      ],\n    ];\n\n    for (const [sourceBoundaries, expectedBoundaries] of testCases) {\n      const calculatedBoundaries = calculateEffectiveBoundaries(\n        SOURCE_BLOB,\n        sourceBoundaries.start,\n        sourceBoundaries.end,\n      );\n\n      expect(expectedBoundaries).to.eql(\n        calculatedBoundaries,\n        `for test case '${sourceBoundaries.start}-${sourceBoundaries.end}'`,\n      );\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-range-requests/sw/utils/test-parseRangeHeader.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {parseRangeHeader} from 'workbox-range-requests/utils/parseRangeHeader.mjs';\n\ndescribe(`parseRangeHeader()`, function () {\n  it(`should throw when it's is called with an invalid 'rangeHeader' parameter`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    const rangeHeader = null;\n    await expectError(\n      () => parseRangeHeader(rangeHeader),\n      'incorrect-type',\n      (error) => {\n        expect(error.details).to.have.property(\n          'moduleName',\n          'workbox-range-requests',\n        );\n        expect(error.details).to.have.property('funcName', 'parseRangeHeader');\n        expect(error.details).to.have.property('paramName', 'rangeHeader');\n        expect(error.details).to.have.property('expectedType', 'string');\n      },\n    );\n  });\n\n  it(`should throw when it's is called with a rangeHeader that doesn't start with 'bytes='`, async function () {\n    const rangeHeader = 'not-bytes=';\n    await expectError(\n      () => parseRangeHeader(rangeHeader),\n      'unit-must-be-bytes',\n    );\n  });\n\n  it(`should throw when it's is called with a rangeHeader contains multiple ranges`, async function () {\n    const rangeHeader = 'bytes=1-2, 3-4';\n    await expectError(() => parseRangeHeader(rangeHeader), 'single-range-only');\n  });\n\n  it(`should throw when it's is called with a rangeHeader contains invalid ranges`, async function () {\n    const badRanges = ['invalid', '-', 'abc-def', '123 - 456'];\n\n    for (const badRange of badRanges) {\n      const rangeHeader = `bytes=${badRange}`;\n      await expectError(\n        () => parseRangeHeader(rangeHeader),\n        'invalid-range-values',\n      );\n    }\n  });\n\n  it(`should return the expected start and end values when it's is called with a valid rangeHeader`, function () {\n    const testCases = [\n      ['bytes=100-200', {start: 100, end: 200}],\n      ['bytes=-200', {start: undefined, end: 200}],\n      ['bytes=100-', {start: 100, end: undefined}],\n    ];\n\n    for (const [rangeHeader, expectedValue] of testCases) {\n      const boundaries = parseRangeHeader(rangeHeader);\n      expect(boundaries).to.eql(\n        expectedValue,\n        `for test case '${rangeHeader}'`,\n      );\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/integration/test-navigation-route.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\n\ndescribe(`[workbox-routing] Route via NavigationRoute`, function () {\n  const testServerAddress = global.__workbox.server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-routing/static/routing-navigation/`;\n  const swURL = `${testingURL}sw.js`;\n\n  it(`should load a page and route requests`, async function () {\n    // Load the page and wait for the first service worker to register and activate.\n    await global.__workbox.webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n\n    const nestedURL = `${testingURL}TestNavigationURL`;\n\n    await global.__workbox.webdriver.get(nestedURL);\n\n    const bodyText = await global.__workbox.webdriver.executeScript(() => {\n      return document.body.textContent;\n    });\n\n    expect(bodyText).to.equal(`NavigationRoute.${nestedURL}`);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/integration/test-routing-basic.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\n\ndescribe(`[workbox-routing] Basic Route`, function () {\n  const testServerAddress = global.__workbox.server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-routing/static/routing-basic/`;\n  const swURL = `${testingURL}sw.js`;\n\n  before(async function () {\n    await global.__workbox.webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n  });\n\n  it(`should honor a route created by a Route object`, async function () {\n    const testURL = `${testServerAddress}/routeObject`;\n    const responseBody = await global.__workbox.webdriver.executeAsyncScript(\n      (testURL, cb) => {\n        fetch(testURL)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testURL,\n    );\n\n    expect(responseBody).to.eql(testURL);\n  });\n\n  it(`should honor a same-origin route created by a string`, async function () {\n    const testURL = `${testServerAddress}/sameOrigin`;\n    const responseBody = await global.__workbox.webdriver.executeAsyncScript(\n      (testURL, cb) => {\n        fetch(testURL)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testURL,\n    );\n\n    expect(responseBody).to.eql(testURL);\n  });\n\n  it(`should honor a cross-origin route created by a string`, async function () {\n    const testURL = 'https://example.com/crossOrigin';\n    const responseBody = await global.__workbox.webdriver.executeAsyncScript(\n      (testURL, cb) => {\n        fetch(testURL)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testURL,\n    );\n\n    expect(responseBody).to.eql(testURL);\n  });\n\n  it(`should return a 404 when passed a URL that isn't routed and doesn't exist`, async function () {\n    const testURL = `${testServerAddress}/doesNotMatch`;\n    const responseStatus = await global.__workbox.webdriver.executeAsyncScript(\n      (testURL, cb) => {\n        fetch(testURL)\n          .then((response) => cb(response.status))\n          .catch((err) => cb(err.message));\n      },\n      testURL,\n    );\n\n    expect(responseStatus).to.eql(404);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/integration/test-routing-regex.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\n\ndescribe(`[workbox-routing] Route via RegExp`, function () {\n  const testServerAddress = global.__workbox.server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-routing/static/routing-regex/`;\n  const swURL = `${testingURL}sw.js`;\n\n  it(`should load a page and route requests`, async function () {\n    // Load the page and wait for the first service worker to register and activate.\n    await global.__workbox.webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n\n    let testCounter = 0;\n\n    let response = await global.__workbox.webdriver.executeAsyncScript(\n      (testCounter, cb) => {\n        fetch(new URL(`/RegExp/${testCounter}/`, location).href)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testCounter,\n    );\n\n    expect(response).to.equal(\n      `RegExp.${testServerAddress}/RegExp/${testCounter}/`,\n    );\n\n    testCounter += 1;\n\n    response = await global.__workbox.webdriver.executeAsyncScript(\n      (testCounter, cb) => {\n        fetch(new URL(`/regular-expression/${testCounter}/`, location).href)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testCounter,\n    );\n\n    expect(response).to.equal(\n      `regular-expression.${testServerAddress}/regular-expression/${testCounter}/`,\n    );\n\n    testCounter += 1;\n\n    response = await global.__workbox.webdriver.executeAsyncScript(\n      (testCounter, cb) => {\n        fetch(new URL(`/RegExpRoute/RegExp/${testCounter}/`, location).href)\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testCounter,\n    );\n\n    expect(response).to.equal(\n      `RegExpRoute.RegExp.${testServerAddress}/RegExpRoute/RegExp/${testCounter}/`,\n    );\n\n    testCounter += 1;\n\n    response = await global.__workbox.webdriver.executeAsyncScript(\n      (testCounter, cb) => {\n        fetch(\n          new URL(`/RegExpRoute/regular-expression/${testCounter}/`, location)\n            .href,\n        )\n          .then((response) => response.text())\n          .then((responseBody) => cb(responseBody))\n          .catch((err) => cb(err.message));\n      },\n      testCounter,\n    );\n\n    expect(response).to.equal(\n      `RegExpRoute.regular-expression.${testServerAddress}/RegExpRoute/regular-expression/${testCounter}/`,\n    );\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/integration/test-sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\ndescribe(`[workbox-routing]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-routing/sw/');\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/static/routing-basic/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-routing/static/routing-basic/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\n\n// Use the same handler for each route, which will create a new response whose\n// body contains the original request URL.\nconst handler = ({url}) => new Response(url);\n\nconst routeObject = new workbox.routing.Route(\n  ({url}) => url.pathname === '/routeObject',\n  handler,\n);\nworkbox.routing.registerRoute(routeObject);\n\nworkbox.routing.registerRoute('/sameOrigin', handler);\n\nworkbox.routing.registerRoute('https://example.com/crossOrigin', handler);\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n"
  },
  {
    "path": "test/workbox-routing/static/routing-navigation/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-routing/static/routing-navigation/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\n\nworkbox.routing.registerRoute(\n  new workbox.routing.NavigationRoute(({url}) => {\n    return new Response(`NavigationRoute.${url.href}`);\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-routing/static/routing-regex/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <h1>Workbox Routing - routing-regex</h1>\n    <p>You need to manually register sw.js</p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-routing/static/routing-regex/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\n\nworkbox.routing.registerRoute(\n  new workbox.routing.RegExpRoute(\n    new RegExp('/RegExpRoute/RegExp/.*/'),\n    ({url}) => {\n      return new Response(`RegExpRoute.RegExp.${url.href}`);\n    },\n  ),\n);\n\nworkbox.routing.registerRoute(\n  new workbox.routing.RegExpRoute(\n    new RegExp('/RegExpRoute/regular-expression/.*/'),\n    ({url}) => {\n      return new Response(`RegExpRoute.regular-expression.${url.href}`);\n    },\n  ),\n);\n\nworkbox.routing.registerRoute(new RegExp('/RegExp/.*/'), ({url}) => {\n  return new Response(`RegExp.${url.href}`);\n});\n\nworkbox.routing.registerRoute(/\\/regular-expression\\/.*\\//, ({url}) => {\n  return new Response(`regular-expression.${url.href}`);\n});\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-routing/static/routing.html",
    "content": "<html>\n  <head>\n    <meta charset=\"UTF-8\" />\n    <!-- Third Party Resource -->\n    <link\n      href=\"https://fonts.googleapis.com/css?family=Open+Sans\"\n      rel=\"stylesheet\"\n    />\n  </head>\n  <body>\n    <p>Get Routing Replacing an Image.</p>\n\n    <!-- Local Image -->\n    <img src=\"demo-img.png\" />\n\n    <script>\n      navigator.serviceWorker.register('sw.js');\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-routing/static/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts(\n  '../../../../packages/workbox-core/build/browser/workbox-core.dev.js',\n);\nimportScripts(\n  '../../../../packages/workbox-routing/build/browser/workbox-routing.dev.js',\n);\n\nconst routing = self.workbox.routing;\nconst Route = self.workbox.routing.Route;\n\nconst specialImgURL = new URL(\n  '/test/workbox-routing/static/demo-img.png',\n  location,\n).toString();\nconst specialImgRoute = new Route(\n  ({event}) => {\n    return event.request.url === specialImgURL;\n  },\n  () => {\n    return fetch(\n      'http://via.placeholder.com/300x300/ffffff/F57C00?text=Hello+from+Workbox',\n      {mode: 'no-cors'},\n    );\n  },\n);\nrouting.registerRoute(specialImgRoute);\n"
  },
  {
    "path": "test/workbox-routing/sw/test-NavigationRoute.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {NavigationRoute} from 'workbox-routing/NavigationRoute.mjs';\n\nconst handler = {\n  handle: () => {},\n};\nconst functionHandler = () => {};\n\nconst invalidHandlerObject = {};\n\ndescribe(`NavigationRoute`, function () {\n  it(`should throw when called without a valid handler parameter in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    await expectError(\n      () => new NavigationRoute(),\n      'incorrect-type',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n\n    await expectError(\n      () => new NavigationRoute(invalidHandlerObject),\n      'missing-a-method',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n  });\n\n  it(`should not throw when called with valid handler in dev`, function () {\n    expect(() => new NavigationRoute(handler)).not.to.throw();\n  });\n\n  it(`should not throw when called with a valid function handler`, function () {\n    expect(() => new NavigationRoute(functionHandler)).not.to.throw();\n  });\n\n  it(`should have a HTTP method of 'GET'`, async function () {\n    const route = new NavigationRoute(handler);\n    expect(route.method).to.equal('GET');\n  });\n\n  it(`should match all navigation requests by default`, function () {\n    const urls = [\n      new URL('/', self.location).toString(),\n      new URL('/testing/path.html', self.location).toString(),\n    ];\n    const navigationRoute = new NavigationRoute(handler);\n    urls.forEach((url) => {\n      const request = new Request(url);\n      Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n      expect(navigationRoute.match({request, url})).to.equal(true);\n    });\n  });\n\n  it(`should not match non- navigation requests by default`, function () {\n    const urls = [\n      new URL('/', self.location),\n      new URL('/testing/path.html', self.location),\n    ];\n    const navigationRoute = new NavigationRoute(handler);\n    urls.forEach((url) => {\n      const request = new Request(url);\n      expect(navigationRoute.match({request, url})).to.equal(false);\n    });\n  });\n\n  it(`should not include urls in denylist that completely match`, function () {\n    const url = new URL('/testing/path.html', self.location);\n    const request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    const navigationRoute = new NavigationRoute(handler, {\n      denylist: [/\\/testing\\/.*/],\n    });\n\n    expect(navigationRoute.match({request, url})).to.equal(false);\n  });\n\n  it(`should denylist urls with search params that result in partial match with regex`, function () {\n    const url = new URL('/testing/path.html?test=hello', self.location);\n    const request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    const navigationRoute = new NavigationRoute(handler, {\n      denylist: [/\\/testing\\/path.html/],\n    });\n\n    expect(navigationRoute.match({request, url})).to.equal(false);\n  });\n\n  it(`should only match urls in custom allowlist`, function () {\n    let url = new URL('/testing/path.html?test=hello', self.location);\n    let request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    const navigationRoute = new NavigationRoute(handler, {\n      allowlist: [/\\/testing\\/path.html/],\n    });\n\n    expect(navigationRoute.match({request, url})).to.equal(true);\n\n    url = new URL('/other/path.html?test=hello', self.location);\n    request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    expect(navigationRoute.match({request, url})).to.equal(false);\n  });\n\n  it(`should take denylist as priority`, function () {\n    let url = new URL('/testing/path.html?test=hello', self.location);\n    let request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    const navigationRoute = new NavigationRoute(handler, {\n      allowlist: [/\\/testing\\/.*/],\n      denylist: [/\\/testing\\/path.html/],\n    });\n\n    expect(navigationRoute.match({request, url})).to.equal(false);\n\n    url = new URL('/testing/index.html?test=hello', self.location);\n    request = new Request(url);\n    Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n    expect(navigationRoute.match({request, url})).to.equal(true);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-RegExpRoute.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {RegExpRoute} from 'workbox-routing/RegExpRoute.mjs';\n\ndescribe(`RegExpRoute`, function () {\n  const SAME_ORIGIN_URL = new URL('https://example.com');\n  const CROSS_ORIGIN_URL = new URL('https://cross-origin-example.com');\n  const PATH = '/test/path';\n  const HANDLER = {handle: () => {}};\n\n  const sandbox = sinon.createSandbox();\n  beforeEach(function () {\n    sandbox.restore();\n    sandbox.stub(self, 'location').value(SAME_ORIGIN_URL);\n  });\n  after(function () {\n    sandbox.restore();\n  });\n\n  for (const badRegExp of [undefined, null, 123, '123', {}]) {\n    it(`should throw when called with a regExp parameter of ${JSON.stringify(\n      badRegExp,\n    )} in dev`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      await expectError(\n        () => new RegExpRoute(),\n        'incorrect-class',\n        (error) => {\n          expect(error.details)\n            .to.have.property('moduleName')\n            .that.equals('workbox-routing');\n          expect(error.details)\n            .to.have.property('className')\n            .that.equals('RegExpRoute');\n          expect(error.details)\n            .to.have.property('funcName')\n            .that.equals('constructor');\n          expect(error.details)\n            .to.have.property('paramName')\n            .that.equals('pattern');\n        },\n      );\n    });\n  }\n\n  it(`should not throw when called with valid parameters`, function () {\n    expect(() => new RegExpRoute(new RegExp('/test/'), HANDLER)).not.to.throw();\n  });\n\n  it(`should properly match URLs`, function () {\n    const matchingURL = new URL(PATH, SAME_ORIGIN_URL);\n    const nonMatchingURL = new URL('/does/not/match', SAME_ORIGIN_URL);\n    const crossOriginURL = new URL(PATH, CROSS_ORIGIN_URL);\n    const regExp = new RegExp(PATH);\n\n    const route = new RegExpRoute(regExp, HANDLER);\n    expect(route.match({url: matchingURL})).to.be.ok;\n    expect(route.match({url: nonMatchingURL})).not.to.be.ok;\n    // This route will not match because while the RegExp matches, the match\n    // doesn't occur at the start of the cross-origin URL.\n    expect(route.match({url: crossOriginURL})).not.to.be.ok;\n  });\n\n  it(`should properly match cross-origin URLs with wildcards`, function () {\n    const matchingURL = new URL(\n      'https://fonts.googleapis.com/icon?family=Material+Icons',\n    );\n    const matchingURL2 = new URL(\n      'https://code.getmdl.io/1.2.1/material.indigo-pink.min.css',\n    );\n\n    const route = new RegExpRoute(\n      /.*\\.(?:googleapis|getmdl)\\.(?:com|io)\\/.*/,\n      HANDLER,\n    );\n    expect(route.match({url: matchingURL})).to.be.ok;\n    expect(route.match({url: matchingURL2})).to.be.ok;\n  });\n\n  it(`should properly match cross-origin URLs without wildcards`, function () {\n    const matchingURL = new URL(PATH, CROSS_ORIGIN_URL);\n    const nonMatchingURL = new URL('/does/not/match', CROSS_ORIGIN_URL);\n    const crossOriginRegExp = new RegExp(matchingURL.href);\n\n    const route = new RegExpRoute(crossOriginRegExp, HANDLER);\n    expect(route.match({url: matchingURL})).to.be.ok;\n    expect(route.match({url: nonMatchingURL})).not.to.be.ok;\n  });\n\n  it(`should properly match URLs with capture groups`, function () {\n    const value1 = 'value1';\n    const value2 = 'value2';\n\n    const captureGroupRegExp = new RegExp('/(\\\\w+)/dummy/(\\\\w+)');\n    const captureGroupMatchingURL = new URL(\n      `/${value1}/dummy/${value2}`,\n      SAME_ORIGIN_URL,\n    );\n    const captureGroupNonMatchingURL = new URL(\n      `/${value1}/${value2}`,\n      SAME_ORIGIN_URL,\n    );\n\n    const route = new RegExpRoute(captureGroupRegExp, HANDLER);\n\n    const match = route.match({url: captureGroupMatchingURL});\n    expect(match.length).to.equal(2);\n    expect(match[0]).to.equal(value1);\n    expect(match[1]).to.equal(value2);\n\n    expect(route.match({url: captureGroupNonMatchingURL})).not.to.be.ok;\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-Route.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Route} from 'workbox-routing/Route.mjs';\n\nconst match = () => {};\nconst handler = {\n  handle: () => {},\n};\nconst functionHandler = () => {};\nconst method = 'POST';\n\nconst invalidHandlerObject = {};\nconst invalidMethod = 'INVALID';\n\ndescribe(`Route`, function () {\n  it(`should throw when called without any parameters in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => new Route(),\n      'incorrect-type',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n      },\n    );\n  });\n\n  it(`should throw when called without a valid handler parameter in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => new Route(match),\n      'incorrect-type',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n\n    await expectError(\n      () => new Route(match, invalidHandlerObject),\n      'missing-a-method',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n  });\n\n  it(`should throw when called without a valid match parameter in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => new Route(null, handler),\n      'incorrect-type',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('match');\n      },\n    );\n  });\n\n  it(`should not throw when called with valid handler.handle and match parameters in dev`, function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    expect(() => new Route(match, handler)).not.to.throw();\n  });\n\n  it(`should not throw when called with a valid function handler and match parameters in dev`, function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    expect(() => new Route(match, functionHandler)).not.to.throw();\n  });\n\n  it(`should throw when called with an invalid method in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => new Route(match, handler, invalidMethod),\n      'invalid-value',\n      (error) =>\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('method'),\n    );\n  });\n\n  it(`should use the method provided when called with a valid method in dev`, function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    const route = new Route(match, handler, method);\n    expect(route.method).to.equal(method);\n  });\n\n  it(`should use a default of GET when called without a method in dev`, function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    const route = new Route(match, handler);\n    expect(route.method).to.equal('GET');\n  });\n\n  it(`should not throw when called with valid handler.handle and match parameters in production`, function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    expect(() => new Route(match, handler)).not.to.throw();\n  });\n\n  it(`should not throw when called with a valid function handler and match parameters in production`, function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    expect(() => new Route(match, functionHandler)).not.to.throw();\n  });\n\n  it(`should use the method provided when called with a valid method in production`, function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    const route = new Route(match, handler, method);\n    expect(route.method).to.equal(method);\n  });\n\n  it(`should use a default of GET when called without a method in production`, function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    const route = new Route(match, handler);\n    expect(route.method).to.equal('GET');\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-Router.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Route} from 'workbox-routing/Route.mjs';\nimport {Router} from 'workbox-routing/Router.mjs';\nimport {logger} from 'workbox-core/_private/logger.mjs';\n\nimport {dispatchAndWaitUntilDone} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport generateTestVariants from '../../../infra/testing/generate-variant-tests';\n\ndescribe(`Router`, function () {\n  const sandbox = sinon.createSandbox();\n  const MATCH = () => {};\n  const HANDLER = {handle: () => {}};\n  const METHOD = 'POST';\n  const EXPECTED_RESPONSE_BODY = 'test body';\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`should construct without any inputs`, function () {\n      expect(() => {\n        new Router();\n      }).to.not.throw();\n    });\n  });\n\n  describe(`registerRoute()`, function () {\n    const invalidMatches = [\n      {},\n      true,\n      false,\n      123,\n      '123',\n      [123],\n      null,\n      undefined,\n    ];\n    generateTestVariants(\n      `should throw in dev when route.match is not a function`,\n      invalidMatches,\n      async function (variant) {\n        if (process.env.NODE_ENV === 'production') return this.skip();\n\n        const router = new Router();\n        await expectError(\n          () =>\n            router.registerRoute({\n              handler: HANDLER,\n              method: METHOD,\n              match: variant,\n            }),\n          'missing-a-method',\n          (error) => {\n            expect(error.details)\n              .to.have.property('moduleName')\n              .that.eql('workbox-routing');\n            expect(error.details)\n              .to.have.property('className')\n              .that.eql('Router');\n            expect(error.details)\n              .to.have.property('funcName')\n              .that.eql('registerRoute');\n            expect(error.details)\n              .to.have.property('paramName')\n              .that.eql('route');\n            expect(error.details)\n              .to.have.property('expectedMethod')\n              .that.eql('match');\n          },\n        );\n      },\n    );\n\n    const invalidHandlers = [() => {}, true, false, 123, '123', undefined];\n    generateTestVariants(\n      `should throw in dev when route.handler is not an object`,\n      invalidHandlers,\n      async function (variant) {\n        if (process.env.NODE_ENV == 'production') return this.skip();\n\n        const router = new Router();\n        await expectError(\n          () =>\n            router.registerRoute({\n              match: MATCH,\n              method: METHOD,\n              handler: variant,\n            }),\n          'incorrect-type',\n          (error) => {\n            expect(error.details)\n              .to.have.property('moduleName')\n              .that.eql('workbox-routing');\n            expect(error.details)\n              .to.have.property('className')\n              .that.eql('Router');\n            expect(error.details)\n              .to.have.property('funcName')\n              .that.eql('registerRoute');\n            expect(error.details)\n              .to.have.property('paramName')\n              .that.eql('route');\n            expect(error.details)\n              .to.have.property('expectedType')\n              .that.eql('object');\n          },\n        );\n      },\n    );\n\n    const invalidMethods = [\n      () => {},\n      {},\n      true,\n      false,\n      123,\n      [123],\n      null,\n      undefined,\n    ];\n    generateTestVariants(\n      `should throw in dev when route.method is not a string`,\n      invalidMethods,\n      async function (variant) {\n        if (process.env.NODE_ENV == 'production') return this.skip();\n\n        const router = new Router();\n        await expectError(\n          () =>\n            router.registerRoute({\n              match: MATCH,\n              handler: HANDLER,\n              method: variant,\n            }),\n          'incorrect-type',\n          (error) => {\n            expect(error.details)\n              .to.have.property('moduleName')\n              .that.eql('workbox-routing');\n            expect(error.details)\n              .to.have.property('className')\n              .that.eql('Router');\n            expect(error.details)\n              .to.have.property('funcName')\n              .that.eql('registerRoute');\n            expect(error.details)\n              .to.have.property('paramName')\n              .that.eql('route.method');\n            expect(error.details)\n              .to.have.property('expectedType')\n              .that.eql('string');\n          },\n        );\n      },\n    );\n\n    it(`should throw in dev when route.handler.handle is not a function`, async function () {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      const router = new Router();\n      await expectError(\n        () => router.registerRoute({match: MATCH, method: METHOD, handler: {}}),\n        'missing-a-method',\n        (error) => {\n          expect(error.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-routing');\n          expect(error.details)\n            .to.have.property('className')\n            .that.eql('Router');\n          expect(error.details)\n            .to.have.property('funcName')\n            .that.eql('registerRoute');\n          expect(error.details)\n            .to.have.property('paramName')\n            .that.eql('route.handler');\n          expect(error.details)\n            .to.have.property('expectedMethod')\n            .that.eql('handle');\n        },\n      );\n    });\n\n    it(`should add the expected entries to the internal arrays of routes`, function () {\n      const router = new Router();\n\n      // Routes without an explicit method will default to GET.\n      const getRoute1 = new Route(MATCH, HANDLER);\n      const getRoute2 = new Route(MATCH, HANDLER, 'GET');\n      const putRoute1 = new Route(MATCH, HANDLER, 'PUT');\n      const putRoute2 = new Route(MATCH, HANDLER, 'PUT');\n      // We support passing in Objects that match the expected interface in addition to Routes.\n      const postRoute = {\n        match: MATCH,\n        handler: HANDLER,\n        method: 'POST',\n      };\n\n      for (const route of [\n        getRoute1,\n        getRoute2,\n        putRoute1,\n        putRoute2,\n        postRoute,\n      ]) {\n        router.registerRoute(route);\n      }\n\n      expect(router.routes.get('GET')).to.have.members([getRoute1, getRoute2]);\n      expect(router.routes.get('PUT')).to.have.members([putRoute1, putRoute2]);\n      expect(router.routes.get('POST')).to.have.members([postRoute]);\n    });\n  });\n\n  describe(`addFetchListener`, function () {\n    it(`should add a listener to respond to fetch events`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n      router.addFetchListener();\n\n      sandbox.spy(router, 'handleRequest');\n\n      const request = new Request(location);\n      const fetchEvent = new FetchEvent('fetch', {request});\n\n      await dispatchAndWaitUntilDone(fetchEvent);\n\n      expect(router.handleRequest.callCount).to.equal(1);\n      expect(router.handleRequest.args[0][0].request).to.equal(request);\n      expect(router.handleRequest.args[0][0].event).to.equal(fetchEvent);\n      expect(fetchEvent.respondWith.callCount).to.equal(1);\n\n      const response = fetchEvent.respondWith.args[0][0];\n      expect(await response.text()).to.equal(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should not call respondWith when no routes match`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => false,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n      router.addFetchListener();\n\n      sandbox.spy(router, 'handleRequest');\n\n      const request = new Request(location.href + '?foo');\n      const fetchEvent = new FetchEvent('fetch', {request});\n\n      await dispatchAndWaitUntilDone(fetchEvent);\n\n      expect(router.handleRequest.callCount).to.equal(1);\n      expect(fetchEvent.respondWith.callCount).to.equal(0);\n    });\n  });\n\n  describe(`addCacheListener`, function () {\n    it(`should add a listener to respond to cache message events`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n      router.addCacheListener();\n\n      sandbox.spy(router, 'handleRequest');\n\n      const messageEvent = new ExtendableMessageEvent('message', {\n        data: {\n          type: 'CACHE_URLS',\n          payload: {\n            urlsToCache: ['/one', '/two', '/three'],\n          },\n        },\n      });\n      sandbox.stub(messageEvent, 'ports').value([{postMessage: sinon.spy()}]);\n\n      await dispatchAndWaitUntilDone(messageEvent);\n\n      expect(router.handleRequest.callCount).to.equal(3);\n      expect(router.handleRequest.args[0][0].request.url).to.equal(\n        `${location.origin}/one`,\n      );\n      expect(router.handleRequest.args[1][0].request.url).to.equal(\n        `${location.origin}/two`,\n      );\n      expect(router.handleRequest.args[2][0].request.url).to.equal(\n        `${location.origin}/three`,\n      );\n      expect(messageEvent.waitUntil.callCount).to.equal(1);\n      expect(messageEvent.waitUntil.args[0][0]).to.be.instanceOf(Promise);\n      expect(messageEvent.ports[0].postMessage.callCount).to.equal(1);\n    });\n\n    it(`should accept URL strings or request URL+requestInit tuples`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n      router.addCacheListener();\n\n      sandbox.spy(router, 'handleRequest');\n\n      await dispatchAndWaitUntilDone(\n        new ExtendableMessageEvent('message', {\n          data: {\n            type: 'CACHE_URLS',\n            payload: {\n              urlsToCache: ['/one', ['/two', {mode: 'no-cors'}], '/three'],\n            },\n          },\n        }),\n      );\n\n      expect(router.handleRequest.callCount).to.equal(3);\n      expect(router.handleRequest.args[0][0].request.url).to.equal(\n        `${location.origin}/one`,\n      );\n      expect(router.handleRequest.args[1][0].request.url).to.equal(\n        `${location.origin}/two`,\n      );\n      expect(router.handleRequest.args[1][0].request.mode).to.equal('no-cors');\n      expect(router.handleRequest.args[2][0].request.url).to.equal(\n        `${location.origin}/three`,\n      );\n    });\n\n    it(`should do nothing for non CACHE_URLS message types`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n      router.addCacheListener();\n\n      sandbox.spy(router, 'handleRequest');\n\n      await dispatchAndWaitUntilDone(new ExtendableMessageEvent('message'));\n\n      expect(router.handleRequest.callCount).to.equal(0);\n    });\n  });\n\n  describe(`unregisterRoute()`, function () {\n    it(`should remove the expected entries from the internal arrays of routes`, function () {\n      const router = new Router();\n\n      // Routes without an explicit method will default to GET.\n      const getRoute1 = new Route(MATCH, HANDLER);\n      const getRoute2 = new Route(MATCH, HANDLER, 'GET');\n      const putRoute1 = new Route(MATCH, HANDLER, 'PUT');\n      const putRoute2 = new Route(MATCH, HANDLER, 'PUT');\n      // We support passing in Objects that match the expected interface in addition to Routes.\n      const postRoute = {\n        match: MATCH,\n        handler: HANDLER,\n        method: 'POST',\n      };\n\n      for (const route of [\n        getRoute1,\n        getRoute2,\n        putRoute1,\n        putRoute2,\n        postRoute,\n      ]) {\n        router.registerRoute(route);\n      }\n\n      router.unregisterRoute(getRoute2);\n      router.unregisterRoute(putRoute2);\n\n      expect(router.routes.get('GET')).to.have.members([getRoute1]);\n      expect(router.routes.get('PUT')).to.have.members([putRoute1]);\n      expect(router.routes.get('POST')).to.have.members([postRoute]);\n    });\n\n    it(`should throw when called with a route with a method for which there isn't an array of routes`, function () {\n      const router = new Router();\n\n      // Routes without an explicit method will default to GET.\n      const getRoute = new Route(MATCH, HANDLER, 'GET');\n      const putRoute = new Route(MATCH, HANDLER, 'PUT');\n\n      router.registerRoute(getRoute);\n      return expectError(\n        () => router.unregisterRoute(putRoute),\n        'unregister-route-but-not-found-with-method',\n        (error) => {\n          expect(error.details).to.have.property('method').that.eql('PUT');\n        },\n      );\n    });\n\n    it(`should throw when called with a route that wasn't previously registered`, function () {\n      const router = new Router();\n\n      // Routes without an explicit method will default to GET.\n      const getRoute1 = new Route(MATCH, HANDLER, 'GET');\n      const getRoute2 = new Route(MATCH, HANDLER, 'GET');\n\n      router.registerRoute(getRoute1);\n      return expectError(\n        () => router.unregisterRoute(getRoute2),\n        'unregister-route-route-not-registered',\n      );\n    });\n  });\n\n  describe(`setDefaultHandler()`, function () {\n    it(`should update the expected internal state, with the default method`, function () {\n      const router = new Router();\n      router.setDefaultHandler(HANDLER);\n\n      expect(router._defaultHandlerMap.get('GET')).to.eql(HANDLER);\n    });\n\n    it(`should update the expected internal state, with specific methods`, function () {\n      const router = new Router();\n      router.setDefaultHandler(HANDLER, 'POST');\n      router.setDefaultHandler(HANDLER, 'PUT');\n\n      expect(router._defaultHandlerMap.get('POST')).to.eql(HANDLER);\n      expect(router._defaultHandlerMap.get('PUT')).to.eql(HANDLER);\n      expect(router._defaultHandlerMap.get('GET')).to.not.exist;\n    });\n\n    it(`should return a response from the default handler when there's no matching route`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => false,\n        () => new Response(),\n      );\n      router.registerRoute(route);\n      router.setDefaultHandler(() => new Response(EXPECTED_RESPONSE_BODY));\n\n      // route.match() always returns false, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should return a response from the default handler when there's no matching route, for a custom method`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => false,\n        () => new Response(),\n      );\n      router.registerRoute(route);\n      router.setDefaultHandler(\n        () => new Response(EXPECTED_RESPONSE_BODY),\n        'POST',\n      );\n\n      const postRequest = new Request(location, {method: 'POST'});\n      const postEvent = new FetchEvent('fetch', {request: postRequest});\n      const postResponse = await router.handleRequest({\n        request: postRequest,\n        event: postEvent,\n      });\n      expect(postResponse).to.exist;\n      const postResponseBody = await postResponse.text();\n      expect(postResponseBody).to.eql(EXPECTED_RESPONSE_BODY);\n\n      const getRequest = new Request(location, {method: 'GET'});\n      const getEvent = new FetchEvent('fetch', {request: getRequest});\n      const getResponse = await router.handleRequest({\n        request: getRequest,\n        event: getEvent,\n      });\n      expect(getResponse).to.not.exist;\n    });\n  });\n\n  describe(`setCatchHandler()`, function () {\n    it(`should update the expected internal state`, function () {\n      const router = new Router();\n      router.setCatchHandler(HANDLER);\n\n      expect(router._catchHandler).to.deep.eql(HANDLER);\n    });\n\n    it(`should return a response from the catch handler when the matching route's handler rejects async`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => Promise.reject(new Error()),\n      );\n      router.registerRoute(route);\n      router.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));\n\n      // route.match() always returns false, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should return a response from the catch handler when the matching route's handler throws sync`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => {\n          throw new Error(`Injected sync error`);\n        },\n      );\n      router.registerRoute(route);\n      router.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));\n\n      // route.match() always returns false, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n  });\n\n  describe(`handleRequest()`, function () {\n    it(`should throw in dev when not passed a request`, async function () {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      const router = new Router();\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n\n      await expectError(\n        () => router.handleRequest({event}),\n        'incorrect-class',\n        (error) => {\n          expect(error.details)\n            .to.have.property('moduleName')\n            .that.eql('workbox-routing');\n          expect(error.details)\n            .to.have.property('className')\n            .that.eql('Router');\n          expect(error.details)\n            .to.have.property('funcName')\n            .that.eql('handleRequest');\n          expect(error.details)\n            .to.have.property('paramName')\n            .that.eql('options.request');\n        },\n      );\n    });\n\n    it(`should return a response from the Route's handler when there's a matching route`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should fall back to the Route's catch handler if there's an error in the Route's handler, if set`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => Promise.reject(new Error()),\n      );\n      route.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));\n      router.registerRoute(route);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should fall back to the global catch handler if there's an error in the Route's catch handler`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => Promise.reject(new Error()),\n      );\n      route.setCatchHandler(() => Promise.reject(new Error()));\n      router.setCatchHandler(() => new Response(EXPECTED_RESPONSE_BODY));\n      router.registerRoute(route);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should return a response from the first matching route when there are multiple potential matches`, async function () {\n      const router = new Router();\n      const response1 = 'response1';\n      const response2 = 'response2';\n      const route1 = new Route(\n        () => true,\n        () => new Response(response1),\n      );\n      router.registerRoute(route1);\n      const route2 = new Route(\n        () => true,\n        () => new Response(response2),\n      );\n      router.registerRoute(route2);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(response1);\n    });\n\n    it(`should work when no event is passed`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        () => new Response(EXPECTED_RESPONSE_BODY),\n      );\n      router.registerRoute(route);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request = new Request(location);\n      const response = await router.handleRequest({request});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should work when request and event.request are different`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => true,\n        ({url}) => {\n          // Ensure request (not event.request) is passed to the handler.\n          if (url.href !== 'https://unexpected.com') {\n            return new Response(EXPECTED_RESPONSE_BODY);\n          }\n        },\n      );\n      router.registerRoute(route);\n\n      // route.match() always returns true, so the Request details don't matter.\n      const request1 = new Request(location);\n      const request2 = new Request('https://unexpected.com');\n\n      const event = new FetchEvent('fetch', {request: request2});\n      const response = await router.handleRequest({request: request1, event});\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql(EXPECTED_RESPONSE_BODY);\n    });\n\n    it(`should not return a response when there's no matching route and no default handler`, async function () {\n      const router = new Router();\n      const route = new Route(\n        () => false,\n        () => new Response(),\n      );\n      router.registerRoute(route);\n\n      // route.match() always returns false, so the Request details don't matter.\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      const response = await router.handleRequest({request, event});\n\n      expect(response).not.to.exist;\n    });\n\n    it(`should not respond to non-http requests`, function () {\n      const router = new Router();\n\n      // route.match() always returns false, so the Request details don't matter.\n      const request = new Request(`example://test.com`);\n      const event = new FetchEvent('fetch', {request});\n      const response = router.handleRequest({request, event});\n\n      expect(response).not.to.exist;\n    });\n\n    it(`should invoke route handlers with the correct arguments`, async function () {\n      const router = new Router();\n      const match = sandbox\n        .stub()\n        .onCall(0)\n        .returns(true)\n        .onCall(1)\n        .returns([1, 2, 3]);\n\n      const handler = sandbox.stub().returns(new Response());\n\n      const route = new Route(match, handler);\n      router.registerRoute(route);\n\n      const url = new URL('/one', location);\n      const request = new Request(url);\n      const event = new FetchEvent('fetch', {request});\n\n      await router.handleRequest({request});\n      await router.handleRequest({request, event});\n\n      expect(handler.callCount).to.equal(2);\n      expect(handler.firstCall.args[0].url).to.deep.equal(url);\n      expect(handler.firstCall.args[0].request).to.equal(request);\n      expect(handler.firstCall.args[0].event).to.equal(undefined);\n      expect(handler.firstCall.args[0].params).to.equal(undefined);\n\n      expect(handler.secondCall.args[0].url).to.deep.equal(url);\n      expect(handler.secondCall.args[0].request).to.equal(request);\n      expect(handler.secondCall.args[0].event).to.equal(event);\n      expect(handler.secondCall.args[0].params).to.deep.equal([1, 2, 3]);\n    });\n\n    const matchCallbackReturnValues = [{a: 'b'}, [1, 2], 'test'];\n    generateTestVariants(\n      `should pass the matchCallback return value to handlerCallback as params`,\n      matchCallbackReturnValues,\n      async function (returnValue) {\n        const handlerCallbackStub = sinon.stub().resolves(new Response());\n        const router = new Router();\n        const route = new Route(\n          sinon.stub().returns(returnValue),\n          handlerCallbackStub,\n        );\n        router.registerRoute(route);\n\n        const request = new Request(location);\n        const event = new FetchEvent('fetch', {request});\n        await router.handleRequest({request, event});\n\n        expect(handlerCallbackStub.calledOnce).to.be.true;\n        expect(handlerCallbackStub.firstCall.args[0].params).to.eql(\n          returnValue,\n        );\n      },\n    );\n\n    it(`should not throw for router with no-routes set`, async function () {\n      const router = new Router();\n\n      const request = new Request(location);\n      const event = new FetchEvent('fetch', {request});\n      await router.handleRequest({request, event});\n    });\n\n    it(`should set the matchCallback's sameOrigin to false when called with a cross-origin request`, async function () {\n      const router = new Router();\n      const matchCallbackStub = sandbox.stub();\n      const route = new Route(matchCallbackStub, () => new Response());\n      router.registerRoute(route);\n\n      const request = new Request('https://cross-origin.example.com');\n      await router.handleRequest({request});\n\n      expect(matchCallbackStub.callCount).to.eql(1);\n      expect(matchCallbackStub.args[0][0].sameOrigin).to.be.false;\n    });\n\n    it(`should set the matchCallback's sameOrigin to true when called with a same-origin request`, async function () {\n      const router = new Router();\n      const matchCallbackStub = sandbox.stub();\n      const route = new Route(matchCallbackStub, () => new Response());\n      router.registerRoute(route);\n\n      const request = new Request(location.href);\n      await router.handleRequest({request});\n\n      expect(matchCallbackStub.callCount).to.eql(1);\n      expect(matchCallbackStub.args[0][0].sameOrigin).to.be.true;\n    });\n  });\n\n  describe(`findMatchingRoute()`, function () {\n    it(`should log a warning in development when an async matchCallback is used`, function () {\n      if (process.env.NODE_ENV === 'production') return this.skip();\n\n      const loggerStub = sandbox.stub(logger, 'warn');\n\n      const router = new Router();\n      const route = new Route(\n        async () => true,\n        () => new Response(),\n      );\n      router.registerRoute(route);\n\n      const url = new URL(location.href);\n      const request = new Request(url);\n      const event = new FetchEvent('fetch', {request});\n      const sameOrigin = true;\n\n      router.findMatchingRoute({url, sameOrigin, request, event});\n\n      expect(loggerStub.calledOnce).to.be.true;\n      // Just check for a snippet of the warning message.\n      expect(loggerStub.firstCall.args[0]).to.include('async');\n    });\n\n    it(`should return the first matching route`, function () {\n      const router = new Router();\n\n      const match1 = sandbox.stub().returns(false);\n      const route1 = new Route(match1, () => new Response());\n      router.registerRoute(route1);\n\n      const match2 = sandbox.stub().returns(true);\n      const route2 = new Route(match2, () => new Response());\n      router.registerRoute(route2);\n\n      const match3 = sandbox.stub().returns(false);\n      const route3 = new Route(match2, () => new Response());\n      router.registerRoute(route3);\n\n      const url = new URL(location.href);\n      const request = new Request(url);\n      const event = new FetchEvent('fetch', {request});\n      const sameOrigin = true;\n\n      const {route} = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n\n      expect(match1.callCount).to.equal(1);\n      expect(match2.callCount).to.equal(1);\n      expect(match3.callCount).to.equal(0);\n\n      expect(route).to.equal(route2);\n    });\n\n    it(`should invoke route match functions with the correct arguments`, function () {\n      const router = new Router();\n\n      const match1 = sandbox.stub().returns(false);\n      const route1 = new Route(match1, () => new Response());\n      router.registerRoute(route1);\n\n      const match2 = sandbox.stub().returns(true);\n      const route2 = new Route(match2, () => new Response());\n      router.registerRoute(route2);\n\n      const url = new URL(location.href);\n      const request = new Request(url);\n      const event = new FetchEvent('fetch', {request});\n      const sameOrigin = true;\n\n      const {route} = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n\n      expect(match1.callCount).to.equal(1);\n      expect(match1.args[0][0]).to.eql({url, sameOrigin, request, event});\n\n      expect(match2.callCount).to.equal(1);\n      expect(match2.args[0][0]).to.eql({url, sameOrigin, request, event});\n\n      expect(route).to.equal(route2);\n    });\n\n    it(`should return the route and params (if applicable)`, function () {\n      const router = new Router();\n      const match = sandbox\n        .stub()\n        .onCall(0)\n        .returns(true)\n        .onCall(1)\n        .returns('truthy')\n        .onCall(2)\n        .returns([1, 2, 3])\n        .onCall(3)\n        .returns([])\n        .onCall(4)\n        .returns({a: 1})\n        .onCall(5)\n        .returns({});\n\n      const route = new Route(match, () => new Response());\n      router.registerRoute(route);\n\n      const url = new URL(location.href);\n      const request = new Request(url);\n      const event = new FetchEvent('fetch', {request});\n      const sameOrigin = true;\n\n      const result1 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result1.route).to.equal(route);\n      expect(result1.params).to.equal(undefined);\n\n      const result2 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result2.route).to.equal(route);\n      expect(result2.params).to.equal('truthy');\n\n      const result3 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result3.route).to.equal(route);\n      expect(result3.params).to.deep.equal([1, 2, 3]);\n\n      const result4 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result4.route).to.equal(route);\n      expect(result4.params).to.equal(undefined);\n\n      const result5 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result5.route).to.equal(route);\n      expect(result5.params).to.deep.equal({a: 1});\n\n      const result6 = router.findMatchingRoute({\n        url,\n        sameOrigin,\n        request,\n        event,\n      });\n      expect(result6.route).to.equal(route);\n      expect(result6.params).to.equal(undefined);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-registerRoute.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {getOrCreateDefaultRouter} from 'workbox-routing/utils/getOrCreateDefaultRouter.mjs';\nimport {RegExpRoute} from 'workbox-routing/RegExpRoute.mjs';\nimport {registerRoute} from 'workbox-routing/registerRoute.mjs';\nimport {Route} from 'workbox-routing/Route.mjs';\n\ndescribe(`registerRoute()`, function () {\n  const sandbox = sinon.createSandbox();\n  let defaultRouter;\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    if (logger) {\n      sandbox.stub(logger, 'debug');\n    }\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    defaultRouter = getOrCreateDefaultRouter();\n\n    // Spy on all routes added to the default router so they can be removed.\n    sandbox.spy(defaultRouter, 'registerRoute');\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    for (const args of defaultRouter.registerRoute.args) {\n      defaultRouter.unregisterRoute(...args);\n    }\n    sandbox.restore();\n  });\n\n  // This is needed because we're skipping the last test, which for some\n  // reasons seems to be skipping the afterEach hook:\n  // https://github.com/mochajs/mocha/pull/2571#issuecomment-477407091\n  after(function () {\n    sandbox.restore();\n  });\n\n  it(`should use the default router instance`, function () {\n    expect(defaultRouter.registerRoute.callCount).to.equal(0);\n    registerRoute('/abc', sandbox.spy());\n    expect(defaultRouter.registerRoute.callCount).to.equal(1);\n  });\n\n  it(`should throw when using a string that doesn't start with '/' or 'http' is used.`, async function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    await expectError(\n      () => registerRoute('invalid-start', sandbox.stub()),\n      'invalid-string',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('registerRoute');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('capture');\n      },\n    );\n  });\n\n  it(`should handle a string for input and return a route that can be unregistered.`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const handlerSpy = sandbox.spy();\n\n    const route = registerRoute('/abc', handlerSpy);\n    expect(route).to.be.an.instanceof(Route);\n\n    const url = new URL('/abc', location);\n    const request = new Request(url);\n    const event = new FetchEvent('fetch', {request});\n\n    await defaultRouter.handleRequest({request, event});\n\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.getCall(0).args[0].url).to.deep.equal(url);\n    expect(handlerSpy.getCall(0).args[0].request).to.equal(request);\n    expect(handlerSpy.getCall(0).args[0].event).to.equal(event);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(route);\n    await defaultRouter.handleRequest({request, event});\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should handle a string for input, matching same-origin requests, and return a route that can be unregistered.`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const handlerSpy = sandbox.spy();\n\n    const crossOrigin = 'https://cross-origin.example.com';\n    const pathname = '/test/path';\n\n    const route = registerRoute(pathname, handlerSpy);\n    expect(route).to.be.an.instanceof(Route);\n\n    const sameOriginURL = new URL(pathname, location);\n    const sameOriginRequest = new Request(sameOriginURL);\n    const sameOriginEvent = new FetchEvent('fetch', {\n      request: sameOriginRequest,\n    });\n\n    await defaultRouter.handleRequest({\n      request: sameOriginRequest,\n      event: sameOriginEvent,\n    });\n\n    const sameOriginURLNotMatching = new URL('/does/not/match', location);\n    const sameOriginRequestNotMatching = new Request(sameOriginURLNotMatching);\n    const sameOriginEventNotMatching = new FetchEvent('fetch', {\n      request: sameOriginRequestNotMatching,\n    });\n\n    await defaultRouter.handleRequest({\n      request: sameOriginRequestNotMatching,\n      event: sameOriginEventNotMatching,\n    });\n\n    const crossOriginURL = new URL(pathname, crossOrigin);\n    const crossOriginRequest = new Request(crossOriginURL);\n    const crossOriginEvent = new FetchEvent('fetch', {\n      request: crossOriginRequest,\n    });\n\n    await defaultRouter.handleRequest({\n      request: crossOriginRequest,\n      event: crossOriginEvent,\n    });\n\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.firstCall.args[0].url).to.deep.equal(sameOriginURL);\n    expect(handlerSpy.firstCall.args[0].request).to.equal(sameOriginRequest);\n    expect(handlerSpy.firstCall.args[0].event).to.equal(sameOriginEvent);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(route);\n    await defaultRouter.handleRequest({\n      request: sameOriginRequest,\n      event: crossOriginEvent,\n    });\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should handle a string for input, matching cross-origin requests, and return a route that can be unregistered.`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const handlerSpy = sandbox.spy();\n\n    const crossOrigin = 'https://cross-origin.example.com';\n    const pathname = '/test/path';\n\n    const route = registerRoute(`${crossOrigin}${pathname}`, handlerSpy);\n    expect(route).to.be.an.instanceof(Route);\n\n    const sameOriginURL = new URL(pathname, location);\n    const sameOriginRequest = new Request(sameOriginURL);\n    const sameOriginEvent = new FetchEvent('fetch', {\n      request: sameOriginRequest,\n    });\n\n    await defaultRouter.handleRequest({\n      request: sameOriginRequest,\n      event: sameOriginEvent,\n    });\n\n    const crossOriginURL = new URL(pathname, crossOrigin);\n    const crossOriginRequest = new Request(crossOriginURL);\n    const crossOriginEvent = new FetchEvent('fetch', {\n      request: crossOriginRequest,\n    });\n\n    await defaultRouter.handleRequest({\n      request: crossOriginRequest,\n      event: crossOriginEvent,\n    });\n\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.args[0][0].url).to.deep.equal(crossOriginURL);\n    expect(handlerSpy.args[0][0].request).to.equal(crossOriginRequest);\n    expect(handlerSpy.args[0][0].event).to.equal(crossOriginEvent);\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(route);\n    await defaultRouter.handleRequest({\n      request: crossOriginRequest,\n      event: crossOriginEvent,\n    });\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should handle a regex for input and return a route that can be unregistered.`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const handlerSpy = sandbox.spy();\n\n    const route = registerRoute(/.*/, handlerSpy);\n    expect(route).to.be.an.instanceof(RegExpRoute);\n\n    const url = new URL('/', location);\n    const request = new Request(url);\n    const event = new FetchEvent('fetch', {request});\n    await defaultRouter.handleRequest({request, event});\n\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.getCall(0).args[0].url).to.deep.equal(url);\n    expect(handlerSpy.getCall(0).args[0].request).to.equal(request);\n    expect(handlerSpy.getCall(0).args[0].event).to.equal(event);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(route);\n    await defaultRouter.handleRequest({request, event});\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should handle a function for input and return a route that can be unregistered.`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const captureSpy = sandbox.stub().returns(true);\n    const handlerSpy = sandbox.spy();\n\n    const route = registerRoute(captureSpy, handlerSpy);\n    expect(route).to.be.an.instanceof(Route);\n\n    const url = new URL('/', location);\n    const request = new Request(url);\n    const event = new FetchEvent('fetch', {request});\n\n    await defaultRouter.handleRequest({request, event});\n\n    expect(captureSpy.callCount).to.equal(1);\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.getCall(0).args[0].url).to.deep.equal(url);\n    expect(handlerSpy.getCall(0).args[0].request).to.equal(request);\n    expect(handlerSpy.getCall(0).args[0].event).to.equal(event);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(route);\n    await defaultRouter.handleRequest({request, event});\n    expect(captureSpy.callCount).to.equal(0);\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should throw on unexpected capture`, function () {\n    return expectError(() => {\n      registerRoute([], () => {});\n    }, 'unsupported-route-type');\n  });\n\n  it(`should allow registering a normal Route`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const captureSpy = sandbox.stub().callsFake(() => true);\n    const handlerSpy = sandbox.spy();\n\n    const inputRoute = new Route(captureSpy, handlerSpy);\n    const outputRoute = registerRoute(inputRoute);\n    expect(outputRoute).to.equal(inputRoute);\n\n    const url = new URL('/', location);\n    const request = new Request(url);\n    const event = new FetchEvent('fetch', {request});\n\n    await defaultRouter.handleRequest({request, event});\n\n    expect(captureSpy.callCount).to.equal(1);\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.getCall(0).args[0].url).to.deep.equal(url);\n    expect(handlerSpy.getCall(0).args[0].request).to.equal(request);\n    expect(handlerSpy.getCall(0).args[0].event).to.equal(event);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(outputRoute);\n    await defaultRouter.handleRequest({request, event});\n    expect(captureSpy.callCount).to.equal(0);\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should allow registering a class that extends Route`, async function () {\n    const defaultRouter = getOrCreateDefaultRouter();\n    const handlerSpy = sandbox.spy();\n\n    const inputRoute = new RegExpRoute(/.*/, handlerSpy);\n    const outputRoute = registerRoute(inputRoute);\n    expect(outputRoute).to.equal(inputRoute);\n\n    const url = new URL('/', location);\n    const request = new Request(url);\n    const event = new FetchEvent('fetch', {request});\n\n    await defaultRouter.handleRequest({request, event});\n    expect(handlerSpy.callCount).to.equal(1);\n    expect(handlerSpy.getCall(0).args[0].url).to.deep.equal(url);\n    expect(handlerSpy.getCall(0).args[0].request).to.equal(request);\n    expect(handlerSpy.getCall(0).args[0].event).to.equal(event);\n\n    sandbox.resetHistory();\n\n    defaultRouter.unregisterRoute(outputRoute);\n    await defaultRouter.handleRequest({request, event});\n    expect(handlerSpy.callCount).to.equal(0);\n  });\n\n  it(`should log for express styles routes`, function () {\n    if (process.env.NODE_ENV === 'production') this.skip();\n\n    registerRoute('/:example/', () => {});\n\n    expect(logger.debug.callCount).to.be.gt(0);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-setCatchHandler.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {setCatchHandler} from 'workbox-routing/setCatchHandler.mjs';\nimport {getOrCreateDefaultRouter} from 'workbox-routing/utils/getOrCreateDefaultRouter.mjs';\n\ndescribe(`setCatchHandler()`, function () {\n  const sandbox = sinon.createSandbox();\n  let defaultRouter;\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    defaultRouter = getOrCreateDefaultRouter();\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should call setCatchHandler() on the default router`, function () {\n    sandbox.stub(defaultRouter, 'setCatchHandler');\n\n    const handler = sandbox.spy();\n    setCatchHandler(handler);\n\n    expect(defaultRouter.setCatchHandler.callCount).to.equal(1);\n    expect(defaultRouter.setCatchHandler.args[0][0]).to.equal(handler);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/test-setDefaultHandler.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {setDefaultHandler} from 'workbox-routing/setDefaultHandler.mjs';\nimport {getOrCreateDefaultRouter} from 'workbox-routing/utils/getOrCreateDefaultRouter.mjs';\n\ndescribe(`setDefaultHandler()`, function () {\n  const sandbox = sinon.createSandbox();\n  let defaultRouter;\n\n  beforeEach(async function () {\n    sandbox.restore();\n\n    // Spy on all added event listeners so they can be removed.\n    sandbox.spy(self, 'addEventListener');\n\n    defaultRouter = getOrCreateDefaultRouter();\n  });\n\n  afterEach(function () {\n    for (const args of self.addEventListener.args) {\n      self.removeEventListener(...args);\n    }\n    sandbox.restore();\n  });\n\n  it(`should call setDefaultHandler() on the default router`, function () {\n    sandbox.stub(defaultRouter, 'setDefaultHandler');\n\n    const handler = sandbox.spy();\n    setDefaultHandler(handler);\n\n    expect(defaultRouter.setDefaultHandler.callCount).to.equal(1);\n    expect(defaultRouter.setDefaultHandler.args[0][0]).to.equal(handler);\n  });\n});\n"
  },
  {
    "path": "test/workbox-routing/sw/utils/test-normalizeHandler.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {normalizeHandler} from 'workbox-routing/utils/normalizeHandler.mjs';\n\nconst handler = {\n  handle: () => {},\n};\nconst functionHandler = () => {};\n\nconst invalidHandlerObject = {};\nconst invalidHandlerString = 'INVALID';\n\ndescribe(`normalizeHandler()`, function () {\n  it(`should properly normalize an object that exposes a handle method in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    const normalizedHandler = normalizeHandler(handler);\n    expect(normalizedHandler).to.have.property('handle');\n  });\n\n  it(`should properly normalize a function in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    const normalizedHandler = normalizeHandler(functionHandler);\n    expect(normalizedHandler).to.have.property('handle');\n  });\n\n  it(`should throw when called with an object that doesn't expose a handle method in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => normalizeHandler(invalidHandlerObject),\n      'missing-a-method',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n  });\n\n  it(`should throw when called with something other than a function or an object in dev`, async function () {\n    if (process.env.NODE_ENV === 'production') return this.skip();\n\n    await expectError(\n      () => normalizeHandler(invalidHandlerString),\n      'incorrect-type',\n      (error) => {\n        expect(error.details)\n          .to.have.property('moduleName')\n          .that.equals('workbox-routing');\n        expect(error.details)\n          .to.have.property('className')\n          .that.equals('Route');\n        expect(error.details)\n          .to.have.property('funcName')\n          .that.equals('constructor');\n        expect(error.details)\n          .to.have.property('paramName')\n          .that.equals('handler');\n      },\n    );\n  });\n\n  it(`should properly normalize an object that exposes a handle method in production`, async function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    const normalizedHandler = normalizeHandler(handler);\n    expect(normalizedHandler).to.have.property('handle');\n  });\n\n  it(`should properly normalize a function in production`, async function () {\n    if (process.env.NODE_ENV !== 'production') return this.skip();\n\n    const normalizedHandler = normalizeHandler(functionHandler);\n    expect(normalizedHandler).to.have.property('handle');\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-cacheFirst.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\n\ndescribe(`[workbox-strategies] CacheFirst Requests`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-strategies/static/cache-first/`;\n\n  let requestCounter;\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n    requestCounter = global.__workbox.server.startCountingRequests();\n  });\n  afterEach(function () {\n    global.__workbox.server.stopCountingRequests(requestCounter);\n  });\n\n  it(`should respond with a cached response`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(\n        new URL(\n          `/test/workbox-strategies/static/cache-first/example.txt`,\n          location,\n        ).href,\n      )\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    expect(response.trim()).to.equal('hello');\n\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-strategies/static/cache-first/example.txt',\n      ),\n    ).to.eql(1);\n\n    // This request should come from cache and not the server\n    response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(\n        new URL(\n          `/test/workbox-strategies/static/cache-first/example.txt`,\n          location,\n        ).href,\n      )\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    expect(response.trim()).to.equal('hello');\n\n    expect(\n      requestCounter.getURLCount(\n        '/test/workbox-strategies/static/cache-first/example.txt',\n      ),\n    ).to.eql(1);\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-cacheOnly.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\n\ndescribe(`[workbox-strategies] CacheOnly`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-strategies/static/cache-only/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should respond with a cached response`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/CacheOnly/InCache/`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    expect(response).to.eql('Cached');\n\n    response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/CacheOnly/NotInCache/`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    expect(response).to.not.eql('Cached');\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-networkFirst.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\nconst waitUntil = require('../../../infra/testing/wait-until');\n\ndescribe(`[workbox-strategies] NetworkFirst Requests`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-strategies/static/network-first/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should respond with a non-cached entry but stash request in a cache`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    const cacheName = 'network-first';\n\n    let response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const firstResponse = response.trim();\n    expect(firstResponse).to.not.equal('Cached');\n\n    // Writing to the cache is asynchronous, so this might not happen right away.\n    await waitUntil(async () => {\n      const responseText = await runInSW(\n        'getCachedResponseText',\n        cacheName,\n        '/__WORKBOX/uniqueValue',\n      );\n      return responseText === firstResponse;\n    });\n\n    response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const secondResponse = response.trim();\n    expect(secondResponse).to.not.equal(firstResponse);\n\n    // Writing to the cache is asynchronous, so this might not happen right away.\n    await waitUntil(async () => {\n      const responseText = await runInSW(\n        'getCachedResponseText',\n        cacheName,\n        '/__WORKBOX/uniqueValue',\n      );\n      return responseText === secondResponse;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-networkOnly.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\n\ndescribe(`[workbox-strategies] NetworkOnly Requests`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-strategies/static/network-only/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should respond with a non-cached entry`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    let response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const firstResponse = response.trim();\n    expect(firstResponse).to.not.eql('Cached');\n\n    await runInSW('clearAllCaches');\n\n    response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const secondResponse = response.trim();\n    expect(secondResponse).to.not.eql(firstResponse);\n\n    const keys = await runInSW('cachesKeys');\n    expect(keys).to.eql([]);\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-staleWhileRevalidate.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\n\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst cleanSWEnv = require('../../../infra/testing/clean-sw');\nconst runInSW = require('../../../infra/testing/comlink/node-interface');\nconst waitUntil = require('../../../infra/testing/wait-until');\n\ndescribe(`[workbox-strategies] StaleWhileRevalidate Requests`, function () {\n  const baseURL = `${global.__workbox.server.getAddress()}/test/workbox-strategies/static/stale-while-revalidate/`;\n\n  beforeEach(async function () {\n    // Navigate to our test page and clear all caches before this test runs.\n    await cleanSWEnv(global.__workbox.webdriver, `${baseURL}integration.html`);\n  });\n\n  it(`should respond with cached entry and update it`, async function () {\n    const swURL = `${baseURL}sw.js`;\n\n    // Wait for the service worker to register and activate.\n    await activateAndControlSW(swURL);\n\n    const cacheName = 'stale-while-revalidate';\n\n    let response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const firstResponse = response.trim();\n\n    // Writing to the cache is asynchronous, so this might not happen right away.\n    await waitUntil(async () => {\n      const responseText = await runInSW(\n        'getCachedResponseText',\n        cacheName,\n        '/__WORKBOX/uniqueValue',\n      );\n      return responseText === firstResponse;\n    });\n\n    // This response should come from cache and not the server\n    response = await global.__workbox.webdriver.executeAsyncScript((cb) => {\n      fetch(`/__WORKBOX/uniqueValue`)\n        .then((response) => response.text())\n        .then((responseBody) => cb(responseBody))\n        .catch((err) => cb(err.message));\n    });\n    const secondResponse = response.trim();\n    expect(secondResponse).to.eql(firstResponse);\n\n    // Writing to the cache is asynchronous, so this might not happen right away.\n    // We expect a new value, updated from the network, different than secondResponse.\n    await waitUntil(async () => {\n      const responseText = await runInSW(\n        'getCachedResponseText',\n        cacheName,\n        '/__WORKBOX/uniqueValue',\n      );\n      return responseText !== secondResponse;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/integration/test-sw.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\ndescribe(`[workbox-strategies]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-strategies/sw/');\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/static/cache-first/example.txt",
    "content": "hello\n"
  },
  {
    "path": "test/workbox-strategies/static/cache-first/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.routing.registerRoute(\n  new RegExp('/test/workbox-strategies/static/cache-first/example.txt'),\n  new workbox.strategies.CacheFirst(),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-strategies/static/cache-only/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.routing.registerRoute(\n  new RegExp('/CacheOnly/.*/'),\n  new workbox.strategies.CacheOnly(),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(\n    caches\n      .open(workbox.core.cacheNames.runtime)\n      .then((cache) => cache.put('/CacheOnly/InCache/', new Response('Cached')))\n      .then(() => self.skipWaiting()),\n  ),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-strategies/static/network-first/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.routing.registerRoute(\n  new RegExp('/__WORKBOX/uniqueValue'),\n  new workbox.strategies.NetworkFirst({\n    cacheName: 'network-first',\n  }),\n);\n\nself.addEventListener('install', (event) => {\n  self.skipWaiting();\n  event.waitUntil(\n    caches\n      .open('network-first')\n      .then((cache) =>\n        cache.put('/__WORKBOX/uniqueValue', new Response('Cached')),\n      ),\n  );\n});\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-strategies/static/network-only/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.routing.registerRoute(\n  new RegExp('/__WORKBOX/uniqueValue'),\n  new workbox.strategies.NetworkOnly({\n    cacheName: 'network-only',\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(\n    caches\n      .open('network-only')\n      .then((cache) =>\n        cache.put('/__WORKBOX/uniqueValue', new Response('Cached')),\n      )\n      .then(() => self.skipWaiting()),\n  ),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-strategies/static/stale-while-revalidate/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\nworkbox.routing.registerRoute(\n  new RegExp('/__WORKBOX/uniqueValue'),\n  new workbox.strategies.StaleWhileRevalidate({\n    cacheName: 'stale-while-revalidate',\n  }),\n);\n\nself.addEventListener('install', (event) =>\n  event.waitUntil(self.skipWaiting()),\n);\nself.addEventListener('activate', (event) =>\n  event.waitUntil(self.clients.claim()),\n);\n"
  },
  {
    "path": "test/workbox-strategies/sw/plugins/test-cacheOkAndOpaquePlugin.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheOkAndOpaquePlugin} from 'workbox-strategies/plugins/cacheOkAndOpaquePlugin.mjs';\nimport {generateOpaqueResponse} from '../../../../infra/testing/helpers/generateOpaqueResponse.mjs';\nimport {generateUniqueResponse} from '../../../../infra/testing/helpers/generateUniqueResponse.mjs';\n\ndescribe(`cacheOkAndOpaquePlugin`, function () {\n  for (const status of [206, 404]) {\n    it(`should return null when status is ${status}`, async function () {\n      const response = generateUniqueResponse({status});\n      expect(await cacheOkAndOpaquePlugin.cacheWillUpdate({response})).to.equal(\n        null,\n      );\n    });\n  }\n\n  it(`should return Response if status is opaque`, async function () {\n    const response = await generateOpaqueResponse();\n    expect(await cacheOkAndOpaquePlugin.cacheWillUpdate({response})).to.equal(\n      response,\n    );\n  });\n\n  it(`should return Response if status is 200`, async function () {\n    const response = generateUniqueResponse();\n    expect(await cacheOkAndOpaquePlugin.cacheWillUpdate({response})).to.equal(\n      response,\n    );\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-CacheFirst.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {CacheFirst} from 'workbox-strategies/CacheFirst.mjs';\nimport {compareResponses} from '../../../infra/testing/helpers/compareResponses.mjs';\nimport {\n  eventDoneWaiting,\n  spyOnEvent,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateOpaqueResponse} from '../../../infra/testing/helpers/generateOpaqueResponse.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\n\ndescribe(`CacheFirst`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`should be able to fetch and cache a request to default cache`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateUniqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const cacheFirst = new CacheFirst();\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const firstCachedResponse = await cache.match(request);\n\n      await compareResponses(firstCachedResponse, fetchResponse, true);\n      await compareResponses(firstHandleResponse, fetchResponse, true);\n\n      const secondHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Reset spy state so we can check fetch wasn't called.\n      self.fetch.resetHistory();\n\n      const secondCachedResponse = await cache.match(request);\n      await compareResponses(firstCachedResponse, secondHandleResponse, true);\n      await compareResponses(firstCachedResponse, secondCachedResponse, true);\n      expect(fetch.callCount).to.equal(0);\n    });\n\n    it(`should support using a string as the request`, async function () {\n      const stringRequest = 'http://example.io/test/';\n      const request = new Request(stringRequest);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateUniqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const cacheFirst = new CacheFirst();\n      const firstHandleResponse = await cacheFirst.handle({\n        request: stringRequest,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const firstCachedResponse = await cache.match(request);\n\n      await compareResponses(firstCachedResponse, fetchResponse, true);\n      await compareResponses(firstHandleResponse, fetchResponse, true);\n\n      const secondHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Reset spy state so we can check fetch wasn't called.\n      self.fetch.resetHistory();\n\n      const secondCachedResponse = await cache.match(request);\n      await compareResponses(firstCachedResponse, secondHandleResponse, true);\n      await compareResponses(firstCachedResponse, secondCachedResponse, true);\n      expect(fetch.callCount).to.equal(0);\n    });\n\n    it(`should be able to cache a non-existent request to custom cache`, async function () {\n      const cacheName = 'test-cache-name';\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateUniqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const cacheFirst = new CacheFirst({\n        cacheName,\n      });\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheName);\n      const firstCachedResponse = await cache.match(request);\n\n      await compareResponses(firstHandleResponse, firstCachedResponse, true);\n    });\n\n    it(`should not cache an opaque response by default`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = await generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const cacheFirst = new CacheFirst();\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const firstCachedResponse = await cache.match(request);\n\n      expect(firstCachedResponse).to.equal(undefined);\n      expect(firstHandleResponse).to.exist;\n    });\n\n    it(`should cache an opaque response when a cacheWillUpdate plugin returns true`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = await generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const cacheFirst = new CacheFirst({\n        plugins: [\n          {\n            cacheWillUpdate: ({request, response}) => {\n              return response;\n            },\n          },\n        ],\n      });\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const firstCachedResponse = await cache.match(request);\n\n      await compareResponses(firstHandleResponse, firstCachedResponse, true);\n    });\n\n    it(`should return the plugin cache response`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = new Response('response body');\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const pluginResponse = new Response('plugin response');\n      const cacheFirst = new CacheFirst({\n        plugins: [\n          {\n            cachedResponseWillBeUsed: () => {\n              return pluginResponse;\n            },\n          },\n        ],\n      });\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      await compareResponses(firstHandleResponse, pluginResponse, true);\n    });\n\n    it(`should fallback to fetch if the plugin.cacheResponseWillBeUsed returns null`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateUniqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const cacheFirst = new CacheFirst({\n        plugins: [\n          {\n            cachedResponseWillBeUsed: () => {\n              return null;\n            },\n          },\n        ],\n      });\n\n      const firstHandleResponse = await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      // The cache should be overridden.\n      const firstCachedResponse = await cache.match(request);\n\n      await compareResponses(firstCachedResponse, fetchResponse, true);\n      await compareResponses(firstHandleResponse, fetchResponse, true);\n    });\n\n    it(`should be able to handle a network error`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedError = new Error(`Injected Error.`);\n      sandbox.stub(self, 'fetch').rejects(injectedError);\n\n      const cacheFirst = new CacheFirst();\n      await expectError(\n        () =>\n          cacheFirst.handle({\n            request,\n            event,\n          }),\n        'no-response',\n      );\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n    });\n\n    it(`should use the fetchOptions provided`, async function () {\n      const fetchOptions = {credentials: 'include'};\n      const cacheFirst = new CacheFirst({fetchOptions});\n\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .resolves(generateUniqueResponse());\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.calledWith(request, fetchOptions)).to.be.true;\n    });\n\n    it(`should use the CacheQueryOptions when performing a cache match`, async function () {\n      const matchStub = sandbox\n        .stub(self.caches.constructor.prototype, 'match')\n        .resolves(generateUniqueResponse());\n\n      const matchOptions = {ignoreSearch: true};\n      const cacheFirst = new CacheFirst({matchOptions});\n\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await cacheFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      expect(matchStub.calledOnce).to.be.true;\n      expect(matchStub.firstCall.args[0]).to.equal(request);\n      expect(matchStub.firstCall.args[1].ignoreSearch).to.equal(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-CacheOnly.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {CacheOnly} from 'workbox-strategies/CacheOnly.mjs';\nimport {compareResponses} from '../../../infra/testing/helpers/compareResponses.mjs';\nimport {spyOnEvent} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\n\ndescribe(`CacheOnly`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`should not return a response when the cache isn't populated`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const cacheOnly = new CacheOnly();\n      await expectError(\n        () =>\n          cacheOnly.handle({\n            request,\n            event,\n          }),\n        'no-response',\n      );\n    });\n\n    it(`should return the cached response when the cache is populated`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const cacheOnly = new CacheOnly();\n      const handleResponse = await cacheOnly.handle({\n        request,\n        event,\n      });\n      await compareResponses(injectedResponse, handleResponse, true);\n    });\n\n    it(`should support using a string as the request`, async function () {\n      const stringRequest = 'http://example.io/test/';\n      const request = new Request(stringRequest);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const cacheOnly = new CacheOnly();\n      const handleResponse = await cacheOnly.handle({\n        request: stringRequest,\n        event,\n      });\n      await compareResponses(injectedResponse, handleResponse, true);\n    });\n\n    it(`should return no cached response from custom cache name`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const cacheOnly = new CacheOnly({cacheName: 'test-cache-name'});\n      await expectError(\n        () =>\n          cacheOnly.handle({\n            request,\n            event,\n          }),\n        'no-response',\n      );\n    });\n\n    it(`should return cached response from custom cache name`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(\n        cacheNames.getRuntimeName('test-cache-name'),\n      );\n      await cache.put(request, injectedResponse.clone());\n\n      const cacheOnly = new CacheOnly({cacheName: 'test-cache-name'});\n      const handleResponse = await cacheOnly.handle({\n        request,\n        event,\n      });\n      await compareResponses(injectedResponse, handleResponse, true);\n    });\n\n    it(`should return the cached response from plugin.cachedResponseWillBeUsed`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const pluginResponse = generateUniqueResponse();\n      const cacheOnly = new CacheOnly({\n        plugins: [\n          {\n            cachedResponseWillBeUsed: () => {\n              return pluginResponse;\n            },\n          },\n        ],\n      });\n      const handleResponse = await cacheOnly.handle({\n        request,\n        event,\n      });\n      await compareResponses(pluginResponse, handleResponse, true);\n    });\n\n    it(`should use the CacheQueryOptions when performing a cache match`, async function () {\n      const matchStub = sandbox\n        .stub(self.caches.constructor.prototype, 'match')\n        .resolves(generateUniqueResponse());\n\n      const matchOptions = {ignoreSearch: true};\n      const cacheOnly = new CacheOnly({matchOptions});\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await cacheOnly.handle({\n        request,\n        event,\n      });\n\n      expect(matchStub.calledOnce).to.be.true;\n      expect(matchStub.firstCall.args[0]).to.equal(request);\n      expect(matchStub.firstCall.args[1].ignoreSearch).to.equal(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-NetworkFirst.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {NetworkFirst} from 'workbox-strategies/NetworkFirst.mjs';\nimport {compareResponses} from '../../../infra/testing/helpers/compareResponses.mjs';\nimport {\n  eventDoneWaiting,\n  spyOnEvent,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateOpaqueResponse} from '../../../infra/testing/helpers/generateOpaqueResponse.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\nimport {sleep} from '../../../infra/testing/helpers/sleep.mjs';\n\ndescribe(`NetworkFirst`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`should add the network response to the cache`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const networkFirst = new NetworkFirst();\n      const handleResponse = await networkFirst.handle({\n        request,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n\n      await compareResponses(cachedResponse, handleResponse, true);\n    });\n\n    it(`should support using a string as the request`, async function () {\n      const stringRequest = 'http://example.io/test/';\n      const request = new Request(stringRequest);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const networkFirst = new NetworkFirst();\n      const handleResponse = await networkFirst.handle({\n        request: stringRequest,\n        event,\n      });\n\n      // Wait until cache.put is finished.\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n\n      await compareResponses(cachedResponse, handleResponse, true);\n    });\n\n    it(`should return the cached response if exists and not update the cache when the network request fails`, async function () {\n      sandbox.stub(self, 'fetch').rejects(new Error('Injected error.'));\n\n      const request = new Request('http://example.io/test/');\n      const event1 = new FetchEvent('fetch', {request});\n      const event2 = new FetchEvent('fetch', {request});\n      const event3 = new FetchEvent('fetch', {request});\n      spyOnEvent(event1);\n      spyOnEvent(event2);\n      spyOnEvent(event3);\n\n      const networkFirst = new NetworkFirst();\n      await expectError(\n        () =>\n          networkFirst.handle({\n            request,\n            event: event1,\n          }),\n        'no-response',\n      );\n      await eventDoneWaiting(event1);\n\n      const injectedResponse = new Response('response body');\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const cachedResponse = await networkFirst.handle({\n        request,\n        event: event2,\n      });\n      await eventDoneWaiting(event2);\n      await compareResponses(cachedResponse, injectedResponse, true);\n\n      const secondCachedResponse = await networkFirst.handle({\n        request,\n        event: event3,\n      });\n      await eventDoneWaiting(event3);\n      await compareResponses(cachedResponse, secondCachedResponse, true);\n    });\n\n    it(`should return the cached response if the network request times out`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      // Use a short timeout to not slow down the test.\n      // Note Sinon fake timers do not work with `await timeout()` used\n      // in the current `StrategyHandler` implementation.\n      const networkTimeoutSeconds = 0.5;\n      const sleepLongerThanNetworkTimeout = sleep(\n        2 * networkTimeoutSeconds * 1000,\n      );\n\n      sandbox.stub(self, 'fetch').callsFake(async (req) => {\n        await sleepLongerThanNetworkTimeout;\n        return new Response('Timedout Response');\n      });\n\n      const networkFirst = new NetworkFirst({networkTimeoutSeconds});\n\n      const injectedResponse = new Response('response body');\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const [handlePromise, donePromise] = networkFirst.handleAll({\n        request,\n        event,\n      });\n\n      await donePromise;\n\n      const populatedCacheResponse = await handlePromise;\n      await compareResponses(populatedCacheResponse, injectedResponse, true);\n    });\n\n    it(`should signal completion if the network request completes before timing out`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const networkTimeoutSeconds = 10;\n\n      const injectedResponse = new Response('response body');\n      sandbox.stub(self, 'fetch').resolves(injectedResponse);\n\n      const networkFirst = new NetworkFirst({networkTimeoutSeconds});\n\n      const [handlePromise, donePromise] = networkFirst.handleAll({\n        request,\n        event,\n      });\n\n      const startTime = performance.now();\n      await donePromise;\n      expect(performance.now() - startTime).to.be.below(1000);\n\n      const populatedCacheResponse = await handlePromise;\n      await compareResponses(populatedCacheResponse, injectedResponse, true);\n    });\n\n    it(`should return the network response if the timeout is exceeded, but there is no cached response`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      // Use a short timeout to not slow down the test.\n      // Note Sinon fake timers do not work with `await timeout()` used\n      // in the current `StrategyHandler` implementation.\n      const networkTimeoutSeconds = 0.5;\n      const sleepLongerThanNetworkTimeout = sleep(\n        2 * networkTimeoutSeconds * 1000,\n      );\n\n      const networkResponse = new Response('from network');\n\n      sandbox.stub(self, 'fetch').callsFake(async () => {\n        await sleepLongerThanNetworkTimeout;\n        return networkResponse;\n      });\n\n      sandbox.stub(caches, 'match').resolves(undefined);\n\n      const networkFirst = new NetworkFirst({\n        networkTimeoutSeconds,\n      });\n      const handlePromise = networkFirst.handle({\n        request,\n        event,\n      });\n\n      const handlerResponse = await handlePromise;\n\n      expect(handlerResponse).to.equal(networkResponse);\n      expect(caches.match.firstCall.args[0]).to.equal(request);\n    });\n\n    it(`should throw when NetworkFirst() is called with an invalid networkTimeoutSeconds parameter`, function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      return expectError(\n        () => new NetworkFirst({networkTimeoutSeconds: 'invalid'}),\n        'incorrect-type',\n        (err) => {\n          expect(err.details.paramName).to.deep.equal('networkTimeoutSeconds');\n          expect(err.details.expectedType).to.deep.equal('number');\n          expect(err.details.moduleName).to.deep.equal('workbox-strategies');\n          expect(err.details.className).to.deep.equal('NetworkFirst');\n          expect(err.details.funcName).to.deep.equal('constructor');\n        },\n      );\n    });\n\n    it(`should return the network response and update the cache when the network request succeeds`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const injectedResponse = generateUniqueResponse();\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, injectedResponse.clone());\n\n      const networkFirst = new NetworkFirst();\n\n      const handleResponse = await networkFirst.handle({\n        request,\n        event,\n      });\n\n      // wait for cache.put\n      await eventDoneWaiting(event);\n\n      await compareResponses(injectedResponse, handleResponse, false);\n\n      const currentCachedResponse = await cache.match(request);\n      await compareResponses(handleResponse, currentCachedResponse, true);\n    });\n\n    it(`should update the cache with an the opaque cross-origin network response`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = await generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const networkFirst = new NetworkFirst();\n      const handleResponse = await networkFirst.handle({\n        request,\n        event,\n      });\n      expect(handleResponse.status).to.equal(0);\n\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n\n      expect(cachedResponse.status).to.equal(0);\n    });\n\n    it(`should not cache an opaque response if they add a custom plugin`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = await generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const networkFirst = new NetworkFirst({\n        plugins: [{cacheWillUpdate: () => null}],\n      });\n\n      const handleResponse = await networkFirst.handle({\n        request,\n        event,\n      });\n      expect(handleResponse.status).to.equal(0);\n\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n      expect(cachedResponse).to.not.exist;\n    });\n\n    it(`should use the fetchOptions provided`, async function () {\n      const fetchOptions = {credentials: 'include'};\n      const networkFirst = new NetworkFirst({fetchOptions});\n\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .resolves(generateUniqueResponse());\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await networkFirst.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.calledWith(request, fetchOptions)).to.be.true;\n    });\n\n    it(`should use the CacheQueryOptions when performing a cache match`, async function () {\n      const matchStub = sandbox\n        .stub(self.caches.constructor.prototype, 'match')\n        .resolves(generateUniqueResponse());\n\n      sandbox.stub(self, 'fetch').callsFake(() => Promise.reject(new Error()));\n\n      const matchOptions = {ignoreSearch: true};\n      const networkFirst = new NetworkFirst({matchOptions});\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await networkFirst.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      expect(matchStub.callCount).to.equal(1);\n      expect(matchStub.firstCall.args[0]).to.equal(request);\n      expect(matchStub.firstCall.args[1].ignoreSearch).to.equal(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-NetworkOnly.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {NetworkOnly} from 'workbox-strategies/NetworkOnly.mjs';\nimport {spyOnEvent} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\nimport {sleep} from '../../../infra/testing/helpers/sleep.mjs';\n\ndescribe(`NetworkOnly`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`should return a response without adding anything to the cache when the network request is successful`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const networkOnly = new NetworkOnly();\n\n      const handleResponse = await networkOnly.handle({\n        request,\n        event,\n      });\n      expect(handleResponse).to.be.instanceOf(Response);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const keys = await cache.keys();\n      expect(keys).to.be.empty;\n    });\n\n    it(`should support using a string as the request`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const stringRequest = 'http://example.io/test/';\n      const request = new Request(stringRequest);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const networkOnly = new NetworkOnly();\n\n      const handleResponse = await networkOnly.handle({\n        request: stringRequest,\n        event,\n      });\n      expect(handleResponse).to.be.instanceOf(Response);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const keys = await cache.keys();\n      expect(keys).to.be.empty;\n    });\n\n    it(`should reject when the network request fails`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      sandbox.stub(self, 'fetch').callsFake(() => {\n        return Promise.reject(new Error(`Injected Error`));\n      });\n\n      const networkOnly = new NetworkOnly();\n      await expectError(\n        () =>\n          networkOnly.handle({\n            request,\n            event,\n          }),\n        'no-response',\n      );\n    });\n\n    it(`should use plugins response`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const pluginRequest = new Request('http://something-else.io/test/');\n\n      sandbox.stub(self, 'fetch').callsFake((req) => {\n        expect(req).to.equal(pluginRequest);\n        return Promise.resolve(new Response('Injected Response'));\n      });\n\n      const networkOnly = new NetworkOnly({\n        plugins: [\n          {\n            requestWillFetch: () => {\n              return pluginRequest;\n            },\n          },\n        ],\n      });\n\n      const handleResponse = await networkOnly.handle({\n        request,\n        event,\n      });\n      expect(handleResponse).to.be.instanceOf(Response);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const keys = await cache.keys();\n      expect(keys).to.be.empty;\n    });\n\n    it(`should use the fetchOptions provided`, async function () {\n      const fetchOptions = {credentials: 'include'};\n      const networkOnly = new NetworkOnly({fetchOptions});\n\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .resolves(generateUniqueResponse());\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await networkOnly.handle({\n        request,\n        event,\n      });\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.calledWith(request, fetchOptions)).to.be.true;\n    });\n\n    it(`should throw if the network request times out`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      // Use a short timeout to not slow down the test.\n      // Note Sinon fake timers do not work with `await timeout()` used\n      // in the current `StrategyHandler` implementation.\n      const networkTimeoutSeconds = 0.5;\n      const sleepLongerThanNetworkTimeout = sleep(\n        2 * networkTimeoutSeconds * 1000,\n      );\n\n      sandbox.stub(self, 'fetch').callsFake(async () => {\n        await sleepLongerThanNetworkTimeout;\n        return new Response('Unexpected Response');\n      });\n\n      const networkOnly = new NetworkOnly({networkTimeoutSeconds});\n\n      await expectError(\n        () => networkOnly.handle({event, request}),\n        'no-response',\n        (err) => {\n          expect(err.details.error.message).to.include(networkTimeoutSeconds);\n        },\n      );\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-StaleWhileRevalidate.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {cacheNames} from 'workbox-core/_private/cacheNames.mjs';\nimport {StaleWhileRevalidate} from 'workbox-strategies/StaleWhileRevalidate.mjs';\nimport {compareResponses} from '../../../infra/testing/helpers/compareResponses.mjs';\nimport {\n  eventDoneWaiting,\n  spyOnEvent,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateOpaqueResponse} from '../../../infra/testing/helpers/generateOpaqueResponse.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\n\ndescribe(`StaleWhileRevalidate`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  after(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe(`handle()`, function () {\n    it(`should add the initial response to the cache`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n      const handleResponse = await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n\n      await compareResponses(cachedResponse, handleResponse, true);\n    });\n\n    it(`should support using a string as the request`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const stringRequest = 'http://example.io/test/';\n      const request = new Request(stringRequest);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n      const handleResponse = await staleWhileRevalidate.handle({\n        request: stringRequest,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n\n      await compareResponses(cachedResponse, handleResponse, true);\n    });\n\n    it(`should return the cached response and not update the cache when the network request fails`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      sandbox.stub(self, 'fetch').rejects(new Error(`Inject Error`));\n\n      const firstCachedResponse = new Response('response body');\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, firstCachedResponse.clone());\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n\n      const handleResponse = await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      await compareResponses(firstCachedResponse, handleResponse, true);\n      const secondCachedResponse = await cache.match(request);\n      await compareResponses(firstCachedResponse, secondCachedResponse, true);\n    });\n\n    it(`should return the cached response and update the cache when the network request succeeds`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const firstCachedResponse = new Response('response body 1');\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      await cache.put(request, firstCachedResponse.clone());\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n      const handleResponse = await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      await compareResponses(firstCachedResponse, handleResponse, true);\n      const secondCachedResponse = await cache.match(request);\n      await compareResponses(firstCachedResponse, secondCachedResponse, false);\n    });\n\n    it(`should update the cache with an the opaque cross-origin network response`, async function () {\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const fetchResponse = await generateOpaqueResponse();\n      sandbox.stub(self, 'fetch').resolves(fetchResponse);\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n      const handleResponse = await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      expect(handleResponse.status).to.eql(0);\n\n      const cache = await caches.open(cacheNames.getRuntimeName());\n      const cachedResponse = await cache.match(request);\n      expect(cachedResponse.status).to.eql(0);\n    });\n\n    it(`should allow adding plugins to override cacheOkAndOpaque`, function () {\n      const plugins = [\n        {\n          cacheWillUpdate: () => {},\n        },\n      ];\n      const staleWhileRevalidate = new StaleWhileRevalidate({\n        plugins,\n      });\n      expect(staleWhileRevalidate.plugins).to.equal(plugins);\n    });\n\n    it(`should use the fetchOptions provided`, async function () {\n      const fetchOptions = {credentials: 'include'};\n      const staleWhileRevalidate = new StaleWhileRevalidate({fetchOptions});\n\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .resolves(generateUniqueResponse());\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.calledWith(request, fetchOptions)).to.be.true;\n    });\n\n    it(`should use the CacheQueryOptions when performing a cache match`, async function () {\n      sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n\n      const matchStub = sandbox\n        .stub(self.caches.constructor.prototype, 'match')\n        .resolves(generateUniqueResponse());\n\n      const matchOptions = {ignoreSearch: true};\n      const staleWhileRevalidate = new StaleWhileRevalidate({matchOptions});\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      await staleWhileRevalidate.handle({\n        request,\n        event,\n      });\n\n      await eventDoneWaiting(event);\n\n      expect(matchStub.callCount).to.equal(1);\n      expect(matchStub.firstCall.args[0]).to.equal(request);\n      expect(matchStub.firstCall.args[1].ignoreSearch).to.equal(true);\n    });\n\n    it(`should throw an error when the network request fails, and there's no cache match`, async function () {\n      sandbox.stub(self, 'fetch').rejects(new Error('Injected error.'));\n\n      const request = new Request('http://example.io/test/');\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      const staleWhileRevalidate = new StaleWhileRevalidate();\n      await expectError(\n        () =>\n          staleWhileRevalidate.handle({\n            request,\n            event,\n          }),\n        'no-response',\n      );\n\n      await eventDoneWaiting(event);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-Strategy.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {timeout} from 'workbox-core/_private/timeout.mjs';\nimport {Strategy} from 'workbox-strategies/Strategy.mjs';\nimport {spyOnEvent} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nclass FetchStrategy extends Strategy {\n  _handle(request, handler) {\n    return handler.fetch(request);\n  }\n}\n\nclass LongWaitUntilStrategy extends Strategy {\n  _handle(request, handler) {\n    handler.waitUntil(timeout(200));\n    return new Response('generated response');\n  }\n}\n\nclass EmptyStrategy extends Strategy {\n  _handle(request, handler) {\n    return new Response('generated response');\n  }\n}\n\nclass ErrorStrategy extends Strategy {\n  _handle(request, handler) {\n    handler.waitUntil(Promise.reject(new Error('waitUntil error')));\n    return new Response('generated response');\n  }\n}\n\nclass HandlerThrowsStrategy extends Strategy {\n  constructor(options) {\n    super(options);\n    this._error = options.error;\n  }\n  async _handle(request, handler) {\n    throw this._error;\n  }\n}\n\nclass HandlerReturnsUndefinedStrategy extends Strategy {\n  async _handle(request, handler) {\n    return undefined;\n  }\n}\n\nclass HandlerReturnsResponseErrorStrategy extends Strategy {\n  async _handle(request, handler) {\n    return Response.error();\n  }\n}\n\nclass ExtendingStrategy extends FetchStrategy {\n  constructor(options) {\n    super(options);\n    this._newProperty = options.newProperty;\n  }\n  async _handle(request, handler) {\n    handler.waitUntil(timeout(100));\n    return (\n      (await handler.cacheMatch(request)) ||\n      (await super._handle(request, handler))\n    );\n  }\n}\n\nfunction generateOptions() {\n  const request = new Request('/strategy-request');\n  const event = new FetchEvent('fetch', {request});\n  spyOnEvent(event);\n\n  return {request, event};\n}\n\ndescribe(`Strategy`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  afterEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe('constructor()', function () {\n    it('works when extended but not overridden', function () {\n      const options = {\n        cacheName: 'test-cache',\n        fetchOptions: {credentials: 'include'},\n        matchOptions: {ignoreSearch: true},\n        plugins: [\n          {\n            handlerWillStart() {},\n          },\n        ],\n      };\n\n      const strategy = new FetchStrategy(options);\n\n      expect(strategy.cacheName).to.equal(options.cacheName);\n      expect(strategy.fetchOptions).to.equal(options.fetchOptions);\n      expect(strategy.matchOptions).to.equal(options.matchOptions);\n      expect(strategy.plugins).to.equal(options.plugins);\n    });\n\n    it('works when extended and overridden', function () {\n      const options = {\n        cacheName: 'test-cache',\n        fetchOptions: {credentials: 'include'},\n        matchOptions: {ignoreSearch: true},\n        plugins: [\n          {\n            handlerWillStart() {},\n          },\n        ],\n        newProperty: 'I am new!',\n      };\n\n      const strategy = new ExtendingStrategy(options);\n\n      expect(strategy.cacheName).to.equal(options.cacheName);\n      expect(strategy.fetchOptions).to.equal(options.fetchOptions);\n      expect(strategy.matchOptions).to.equal(options.matchOptions);\n      expect(strategy.plugins).to.equal(options.plugins);\n\n      expect(strategy._newProperty).to.equal(options.newProperty);\n    });\n  });\n\n  describe('.handleAll()', function () {\n    it('runs the strategy and returns a response and done promise tuple', async function () {\n      const strategy = new LongWaitUntilStrategy();\n      const {request, event} = generateOptions();\n\n      const startTime = performance.now();\n      const [responsePromise, donePromise] = strategy.handleAll({\n        request,\n        event,\n      });\n\n      const response = await responsePromise;\n      expect(performance.now() - startTime < 200).to.be.true;\n      expect(await response.text()).to.equal('generated response');\n\n      await donePromise;\n      expect(performance.now() - startTime >= 200).to.be.true;\n    });\n\n    it('accepts a string as the request param', async function () {\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n\n      const strategy = new FetchStrategy();\n      const {event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({\n        request: '/string-url',\n        event,\n      });\n      await responsePromise;\n\n      expect(self.fetch.firstCall.args[0].url).to.equal(\n        location.origin + '/string-url',\n      );\n    });\n\n    it('accepts a Request as the request param', async function () {\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n\n      const strategy = new FetchStrategy();\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      await responsePromise;\n\n      expect(self.fetch.firstCall.args[0]).to.equal(request);\n    });\n\n    it(`runs all handlerWillStart callbacks`, async function () {\n      const plugins = [\n        {handlerWillStart: sandbox.spy()},\n        {nonMatchingCallback: sandbox.spy()},\n        {\n          /* Empty plugin */\n        },\n        {handlerWillStart: sandbox.spy()},\n      ];\n      const strategy = new EmptyStrategy({plugins});\n      const {request, event} = generateOptions();\n\n      await Promise.all(strategy.handleAll({request, event}));\n\n      expect(\n        plugins[0].handlerWillStart.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n\n      expect(plugins[1].nonMatchingCallback.callCount).to.equal(0);\n\n      expect(\n        plugins[3].handlerWillStart.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n    });\n\n    it(`runs all handlerWillRespond callbacks with the response`, async function () {\n      const plugins = [\n        {\n          handlerWillRespond: sandbox\n            .stub()\n            .callsFake(({response}) => response),\n        },\n        {nonMatchingCallback: sandbox.spy()},\n        {\n          /* Empty plugin */\n        },\n        {\n          handlerWillRespond: sandbox\n            .stub()\n            .callsFake(({response}) => response),\n        },\n      ];\n      const strategy = new EmptyStrategy({plugins});\n      const {request, event} = generateOptions();\n\n      const [response] = await Promise.all(\n        strategy.handleAll({request, event}),\n      );\n\n      expect(\n        plugins[0].handlerWillRespond.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[0].handlerWillRespond.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n      expect(\n        await plugins[0].handlerWillRespond.firstCall.returnValue\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n\n      expect(plugins[1].nonMatchingCallback.callCount).to.equal(0);\n\n      expect(\n        plugins[3].handlerWillRespond.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[3].handlerWillRespond.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n      expect(\n        await plugins[3].handlerWillRespond.firstCall.returnValue\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n\n      expect(await response.clone().text()).to.equal('generated response');\n    });\n\n    it(`runs all handlerDidRespond callbacks with the response`, async function () {\n      const plugins = [\n        {handlerDidRespond: sandbox.spy()},\n        {nonMatchingCallback: sandbox.spy()},\n        {\n          /* Empty plugin */\n        },\n        {handlerDidRespond: sandbox.spy()},\n      ];\n      const strategy = new EmptyStrategy({plugins});\n      const {request, event} = generateOptions();\n\n      const [response] = await Promise.all(\n        strategy.handleAll({request, event}),\n      );\n\n      expect(\n        plugins[0].handlerDidRespond.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[0].handlerDidRespond.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n\n      expect(plugins[1].nonMatchingCallback.callCount).to.equal(0);\n\n      expect(\n        plugins[3].handlerDidRespond.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[3].handlerDidRespond.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n\n      expect(await response.clone().text()).to.equal('generated response');\n    });\n\n    it(`runs all handlerDidComplete callbacks`, async function () {\n      const plugins = [\n        {handlerDidComplete: sandbox.spy()},\n        {nonMatchingCallback: sandbox.spy()},\n        {\n          /* Empty plugin */\n        },\n        {handlerDidComplete: sandbox.spy()},\n      ];\n      const strategy = new EmptyStrategy({plugins});\n      const {request, event} = generateOptions();\n\n      await Promise.all(strategy.handleAll({request, event}));\n\n      expect(\n        plugins[0].handlerDidComplete.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[0].handlerDidComplete.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n\n      expect(plugins[1].nonMatchingCallback.callCount).to.equal(0);\n\n      expect(\n        plugins[3].handlerDidComplete.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[3].handlerDidComplete.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n    });\n\n    it('passes any errors in waitUntil promises to the handlerDidComplete callback', async function () {\n      const plugins = [{handlerDidComplete: sinon.spy()}];\n      const strategy = new ErrorStrategy({plugins});\n\n      const {request, event} = generateOptions();\n\n      let doneError;\n      try {\n        const [, done] = strategy.handleAll({request, event});\n        await done;\n      } catch (error) {\n        doneError = error;\n      }\n\n      expect(\n        plugins[0].handlerDidComplete.calledOnceWith(\n          sinon.match({\n            request,\n            event,\n            error: doneError,\n            state: sinon.match(Object),\n          }),\n        ),\n      ).to.be.true;\n      expect(\n        await plugins[0].handlerDidComplete.firstCall.args[0].response\n          .clone()\n          .text(),\n      ).to.equal('generated response');\n    });\n  });\n\n  describe('handlerDidError', function () {\n    it('should use the first callback that returns a Response when _handler() throws', async function () {\n      const plugins = [\n        {\n          handlerDidError: sandbox.stub().resolves(undefined),\n        },\n        {\n          handlerDidError: sandbox.stub().resolves(new Response('from plugin')),\n        },\n        {\n          handlerDidError: sandbox.stub().resolves(undefined),\n        },\n      ];\n      const error = new Error('thrown error');\n      const strategy = new HandlerThrowsStrategy({error, plugins});\n\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      const response = await responsePromise;\n      const responseBody = await response.text();\n      const expectedArgs = [\n        [\n          {\n            error,\n            event,\n            request,\n            state: {},\n          },\n        ],\n      ];\n\n      expect(responseBody).to.eql('from plugin');\n      expect(plugins[0].handlerDidError.args).to.eql(expectedArgs);\n      expect(plugins[1].handlerDidError.args).to.eql(expectedArgs);\n      // This shouldn't run, since the previous plugin returns a response.\n      expect(plugins[2].handlerDidError.args).to.eql([]);\n    });\n\n    it(`should rethrow the error when the callbacks don't return a Response`, async function () {\n      const plugins = [\n        {\n          handlerDidError: sandbox.stub().resolves(undefined),\n        },\n        {\n          handlerDidError: sandbox.stub().resolves(undefined),\n        },\n      ];\n      const error = new Error('thrown error');\n      const strategy = new HandlerThrowsStrategy({error, plugins});\n\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      try {\n        await responsePromise;\n        throw new Error('unexpected error');\n      } catch (thrownError) {\n        expect(thrownError).to.eql(error);\n\n        const expectedArgs = [\n          [\n            {\n              error,\n              event,\n              request,\n              state: {},\n            },\n          ],\n        ];\n\n        expect(plugins[0].handlerDidError.args).to.eql(expectedArgs);\n        expect(plugins[1].handlerDidError.args).to.eql(expectedArgs);\n      }\n    });\n\n    it('should use the callback Response when _handler() returns undefined', async function () {\n      const plugins = [\n        {\n          handlerDidError: sandbox.stub().resolves(new Response('from plugin')),\n        },\n      ];\n      const strategy = new HandlerReturnsUndefinedStrategy({plugins});\n\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      const response = await responsePromise;\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql('from plugin');\n\n      // We can't do a deep equality check without a reference to the\n      // WorkboxError, so just do a sanity check.\n      expect(plugins[0].handlerDidError.args[0][0].error.name).to.eql(\n        'no-response',\n      );\n    });\n\n    it('should throw an error when _handler() returns undefined and the callbacks return undefined', async function () {\n      const plugins = [\n        {\n          handlerDidError: sandbox.stub().resolves(undefined),\n        },\n      ];\n      const strategy = new HandlerReturnsUndefinedStrategy({plugins});\n\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      try {\n        await responsePromise;\n        throw new Error('unexpected error');\n      } catch (thrownError) {\n        expect(thrownError.name).to.eql('no-response');\n\n        expect(plugins[0].handlerDidError.args).to.eql([\n          [\n            {\n              event,\n              request,\n              error: thrownError,\n              state: {},\n            },\n          ],\n        ]);\n      }\n    });\n\n    it('should use the callback Response when _handler() returns Response.error()', async function () {\n      const plugins = [\n        {\n          handlerDidError: sandbox.stub().resolves(new Response('from plugin')),\n        },\n      ];\n      const strategy = new HandlerReturnsResponseErrorStrategy({plugins});\n\n      const {request, event} = generateOptions();\n\n      const [responsePromise] = strategy.handleAll({request, event});\n      const response = await responsePromise;\n      const responseBody = await response.text();\n\n      expect(responseBody).to.eql('from plugin');\n\n      // We can't do a deep equality check without a reference to the\n      // WorkboxError, so just do a sanity check.\n      expect(plugins[0].handlerDidError.args[0][0].error.name).to.eql(\n        'no-response',\n      );\n    });\n  });\n\n  describe('handle', function () {\n    it(`invokes handleAll() and returns the response promise`, async function () {\n      const strategy = new EmptyStrategy();\n      const {request, event} = generateOptions();\n\n      const stubResponse = new Response('stub response');\n      sandbox\n        .stub(strategy, 'handleAll')\n        .returns([Promise.resolve(stubResponse), Promise.resolve()]);\n\n      const response = await strategy.handle({request, event});\n\n      expect(response).to.equal(stubResponse);\n      expect(strategy.handleAll.callCount).to.equal(1);\n      expect(strategy.handleAll.calledWith({event, request})).to.be.true;\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-StrategyHandler.mjs",
    "content": "/*\n  Copyright 2020 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Deferred} from 'workbox-core/_private/Deferred.mjs';\nimport {logger} from 'workbox-core/_private/logger.mjs';\nimport {registerQuotaErrorCallback} from 'workbox-core/registerQuotaErrorCallback.mjs';\nimport {Strategy} from 'workbox-strategies/Strategy.mjs';\nimport {StrategyHandler} from 'workbox-strategies/StrategyHandler.mjs';\nimport {timeout} from 'workbox-core/_private/timeout.mjs';\n\nimport {\n  spyOnEvent,\n  eventDoneWaiting,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\n\nclass TestStrategy extends Strategy {\n  _handle() {\n    return new Response('Test response');\n  }\n}\n\nconst createStrategyHandler = (options) => {\n  const request = new Request('/handler-request');\n  const event = new FetchEvent('fetch', {request});\n  const url = new URL(request.url);\n  const params = {a: 1, b: 2};\n\n  spyOnEvent(event);\n\n  // First arg should be a `Strategy` instance, but the options just get\n  // copied to the instance anyway, so an object will suffice.\n  // return new StrategyHandler(options, {request, event});\n  return new StrategyHandler(new TestStrategy(options), {\n    request,\n    event,\n    url,\n    params,\n  });\n};\n\ndescribe(`StrategyHandler`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  afterEach(async function () {\n    const keys = await caches.keys();\n    await Promise.all(keys.map((key) => caches.delete(key)));\n    sandbox.restore();\n  });\n\n  describe('constructor()', function () {\n    it(`should throw when called without an 'event' parameter in dev`, async function () {\n      if (process.env.NODE_ENV === 'production') {\n        return this.skip();\n      }\n\n      await expectError(\n        () => new StrategyHandler(new TestStrategy(), {}),\n        'incorrect-class',\n        (error) => {\n          expect(error.details)\n            .to.have.property('moduleName')\n            .that.equals('workbox-strategies');\n          expect(error.details)\n            .to.have.property('className')\n            .that.equals('StrategyHandler');\n          expect(error.details)\n            .to.have.property('funcName')\n            .that.equals('constructor');\n          expect(error.details)\n            .to.have.property('paramName')\n            .that.equals('options.event');\n        },\n      );\n    });\n\n    it('creates an object with the correct public properties', function () {\n      const handler = createStrategyHandler();\n\n      expect(handler.request).to.be.instanceOf(Request);\n      expect(handler.event).to.be.instanceOf(ExtendableEvent);\n      expect(handler.url).to.be.instanceOf(URL);\n      expect(handler.params).to.be.instanceOf(Object);\n    });\n\n    it('passes a deferred to event waitUntil (if passed an event)', function () {\n      const handler = createStrategyHandler();\n\n      expect(handler.event.waitUntil.callCount).to.equal(1);\n      expect(handler.event.waitUntil.firstCall.args[0]).to.be.instanceOf(\n        Promise,\n      );\n    });\n  });\n\n  describe('waitUntil()', function () {\n    it('adds promises to an internal queue', function () {\n      const handler = createStrategyHandler();\n\n      handler.waitUntil(Promise.resolve());\n      expect(handler._extendLifetimePromises).to.have.lengthOf(1);\n\n      handler.waitUntil(Promise.resolve());\n      handler.waitUntil(Promise.resolve());\n      expect(handler._extendLifetimePromises).to.have.lengthOf(3);\n    });\n\n    it('returns the passed promise', function () {\n      const handler = createStrategyHandler();\n      const promise = Promise.resolve();\n\n      expect(handler.waitUntil(promise)).to.equal(promise);\n    });\n  });\n\n  describe('doneWaiting()', function () {\n    it('returns a promise the resolves once all waitUntil promises have settled', async function () {\n      const handler = createStrategyHandler();\n\n      const spy = sandbox.spy();\n      const startTime = performance.now();\n      handler.waitUntil(timeout(200).then(spy));\n      handler.waitUntil(timeout(50).then(spy));\n      handler.waitUntil(timeout(100).then(spy));\n\n      await handler.doneWaiting();\n\n      expect(spy.callCount).to.equal(3);\n      expect(performance.now() - startTime >= 200).to.be.true;\n    });\n  });\n\n  describe('destroy()', function () {\n    it('resolves any waitUntil promises immediately', function (done) {\n      const handler = createStrategyHandler();\n\n      const deferred = new Deferred();\n      handler.waitUntil(deferred.promise);\n\n      eventDoneWaiting(handler.event).then(() => {\n        deferred.resolve();\n        done();\n      });\n\n      // Even though the promises passed to `handler.waitUntil()` hasn't\n      // resolved, calling `destroy()` should ensure the handler's event can.\n      handler.destroy();\n    });\n  });\n\n  // These tests were copied from `test-fetchWrapper` to ensure we don't\n  // lose any of the existing behavior in the update.\n  describe(`fetch()`, function () {\n    it(`should work with request string`, async function () {\n      const stub = sandbox.stub(self, 'fetch').callsFake(() => new Response());\n\n      const handler = createStrategyHandler();\n      await handler.fetch('/test/string');\n\n      expect(stub.callCount).to.equal(1);\n      const fetchRequest = stub.args[0][0];\n      expect(fetchRequest.url).to.equal(`${location.origin}/test/string`);\n    });\n\n    it(`should work with Request instance`, async function () {\n      const stub = sandbox.stub(self, 'fetch').callsFake(() => new Response());\n\n      const handler = createStrategyHandler();\n      await handler.fetch(new Request('/test/response'));\n\n      expect(stub.callCount).to.equal(1);\n      const fetchRequest = stub.args[0][0];\n      expect(fetchRequest.url).to.equal(`${location.origin}/test/response`);\n    });\n\n    it(`should use fetchOptions from the strategy`, async function () {\n      const stub = sandbox.stub(self, 'fetch').callsFake(() => new Response());\n\n      const fetchOptions = {\n        method: 'POST',\n        headers: {\n          Custom: 'Header',\n        },\n        body: 'Example Body',\n      };\n\n      const handler = createStrategyHandler({fetchOptions});\n      await handler.fetch('/test/fetchOptions');\n\n      expect(stub.callCount).to.equal(1);\n      const fetchArgs = stub.args[0];\n      expect(fetchArgs[0].url).to.equal(`${location.origin}/test/fetchOptions`);\n      expect(fetchArgs[1]).to.deep.equal(fetchOptions);\n    });\n\n    it(`should ignore fetchOptions when request.mode === 'navigate'`, async function () {\n      // Firefox crashes when we change the request mode, so skipping this test.\n      if (navigator.userAgent.match(/firefox/i)) {\n        this.skip();\n      }\n      // See https://github.com/GoogleChrome/workbox/issues/1796\n      const fetchStub = sandbox.stub(self, 'fetch').resolves(new Response());\n\n      const fetchOptions = {\n        headers: {\n          'X-Test': 'Header',\n        },\n      };\n\n      const request = new Request('/test/navigateFetchOptions');\n      // You normally can't generate a navigation request programmatically,\n      // but we can fake it with `Object.defineProperty()` after creation.\n      Object.defineProperty(request, 'mode', {value: 'navigate'});\n\n      const handler = createStrategyHandler({fetchOptions});\n      await handler.fetch(request);\n\n      expect(fetchStub.calledOnce).to.be.true;\n      expect(fetchStub.firstCall.args[0]).to.be.instanceOf(Request);\n      expect(fetchStub.firstCall.args[0].url).to.eql(request.url);\n      expect(fetchStub.firstCall.args[0].mode).to.eql('navigate');\n      expect(fetchStub.firstCall.args[1]).not.to.exist;\n    });\n\n    it(`should call the requestWillFetch callback in all strategy plugins and use the returned request`, async function () {\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .callsFake(() => new Response());\n\n      const stub1 = sandbox\n        .stub()\n        .returns(new Request('/test/requestWillFetch/1'));\n      const stub2 = sandbox\n        .stub()\n        .returns(new Request('/test/requestWillFetch/2'));\n\n      const firstPlugin = {requestWillFetch: stub1};\n      const secondPlugin = {requestWillFetch: stub2};\n\n      const handler = createStrategyHandler({\n        plugins: [firstPlugin, secondPlugin],\n      });\n\n      await handler.fetch('/test/requestWillFetch/0');\n\n      expect(stub1.callCount).equal(1);\n      expect(stub1.args[0][0].request.url).to.equal(\n        `${location.origin}/test/requestWillFetch/0`,\n      );\n      expect(stub1.args[0][0].event).to.equal(handler.event);\n      expect(stub2.callCount).equal(1);\n      expect(stub2.args[0][0].request.url).to.equal(\n        `${location.origin}/test/requestWillFetch/1`,\n      );\n      expect(stub2.args[0][0].event).to.equal(handler.event);\n\n      expect(fetchStub.callCount).to.equal(1);\n\n      const fetchRequest = fetchStub.args[0][0];\n      expect(fetchRequest.url).to.equal(\n        `${location.origin}/test/requestWillFetch/2`,\n      );\n    });\n\n    it(`should throw a meaningful error on bad requestWillFetch plugin`, async function () {\n      const fetchStub = sandbox\n        .stub(self, 'fetch')\n        .callsFake(() => new Response());\n      const errorPlugin = {\n        requestWillFetch: (request) => {\n          throw new Error('Injected Error from Test.');\n        },\n      };\n      const errorPluginSpy = sandbox.spy(errorPlugin, 'requestWillFetch');\n\n      const handler = createStrategyHandler({\n        plugins: [errorPlugin],\n      });\n\n      await expectError(\n        () => {\n          return handler.fetch('/test/requestWillFetch/0');\n        },\n        'plugin-error-request-will-fetch',\n        (err) => {\n          expect(err.details.thrownErrorMessage).to.exist;\n          expect(err.details.thrownErrorMessage).to.equal(\n            'Injected Error from Test.',\n          );\n        },\n      );\n\n      expect(errorPluginSpy.callCount).equal(1);\n      expect(fetchStub.callCount).to.equal(0);\n    });\n\n    it(`should call fetchDidFail method in plugins`, async function () {\n      sandbox.stub(self, 'fetch').callsFake(() => {\n        return Promise.reject(new Error('Injected Error.'));\n      });\n\n      const secondPlugin = {\n        fetchDidFail: sandbox\n          .stub()\n          .callsFake(({originalRequest, request, event, error}) => {\n            expect(originalRequest.url).to.equal(\n              `${location.origin}/test/failingRequest/0`,\n            );\n            expect(request.url).to.equal(\n              `${location.origin}/test/failingRequest/1`,\n            );\n            expect(error.message).to.equal('Injected Error.');\n          }),\n      };\n\n      const firstPlugin = {\n        requestWillFetch: ({request}) => {\n          return new Request('/test/failingRequest/1');\n        },\n        fetchDidFail: sandbox\n          .stub()\n          .callsFake(({originalRequest, request, event, error}) => {\n            // This should be called first\n            expect(secondPlugin.fetchDidFail.callCount).to.equal(0);\n            expect(originalRequest.url).to.equal(\n              `${location.origin}/test/failingRequest/0`,\n            );\n            expect(request.url).to.equal(\n              `${location.origin}/test/failingRequest/1`,\n            );\n            expect(error.message).to.equal('Injected Error.');\n          }),\n      };\n\n      const handler = createStrategyHandler({\n        plugins: [\n          firstPlugin,\n          {\n            // It should be able to handle plugins without the required method.\n          },\n          secondPlugin,\n        ],\n      });\n\n      try {\n        await handler.fetch('/test/failingRequest/0');\n        throw new Error('No error thrown when it was expected.');\n      } catch (err) {\n        expect(err.message).to.equal('Injected Error.');\n      }\n\n      expect(firstPlugin.fetchDidFail.callCount).equal(1);\n      expect(secondPlugin.fetchDidFail.callCount).equal(1);\n      expect(self.fetch.callCount).to.equal(1);\n\n      const fetchRequest = self.fetch.args[0][0];\n      expect(fetchRequest.url).to.equal(\n        `${location.origin}/test/failingRequest/1`,\n      );\n    });\n\n    it(`should call the fetchDidSucceed method in plugins`, async function () {\n      const originalRequest = new Request('/testing');\n\n      sandbox.stub(self, 'fetch').resolves(\n        new Response('', {\n          headers: {\n            'x-count': 1,\n          },\n        }),\n      );\n\n      const fetchDidSucceed = sandbox.stub().callsFake(({response}) => {\n        const count = Number(response.headers.get('x-count'));\n        return new Response('', {\n          headers: {\n            'x-count': count + 1,\n          },\n        });\n      });\n\n      const handler = createStrategyHandler({\n        plugins: [\n          // Two plugins, both with the same callback.\n          {fetchDidSucceed},\n          {fetchDidSucceed},\n        ],\n      });\n\n      const finalResponse = await handler.fetch(originalRequest);\n\n      expect(fetchDidSucceed.callCount).to.eql(2);\n\n      for (const args of fetchDidSucceed.args) {\n        expect(args[0].request).to.be.instanceOf(Request);\n        expect(args[0].response).to.be.instanceOf(Response);\n        expect(args[0].event).to.be.instanceOf(FetchEvent);\n      }\n\n      const finalCount = finalResponse.headers.get('x-count');\n      expect(finalCount).to.equal('3');\n    });\n  });\n\n  // These tests were copied from `test-cacheWrapper` to ensure we don't\n  // lose any of the existing behavior in the update.\n  describe(`.cachePut()`, function () {\n    it(`should work with a request and response`, async function () {\n      const testCache = await caches.open('TEST-CACHE');\n      const cacheOpenStub = sandbox.stub(self.caches, 'open');\n      const cachePutStub = sandbox.stub(testCache, 'put');\n      cacheOpenStub.callsFake(async (cacheName) => {\n        return testCache;\n      });\n      const putRequest = new Request('/test/string');\n      const putResponse = new Response('Response for /test/string');\n\n      const handler = createStrategyHandler({cacheName: 'TODO-CHANGE-ME'});\n      await handler.cachePut(putRequest, putResponse);\n\n      expect(cacheOpenStub.callCount).to.equal(1);\n      const cacheName1 = cacheOpenStub.args[0][0];\n      expect(cacheName1).to.equal('TODO-CHANGE-ME');\n\n      expect(cachePutStub.callCount).to.equal(1);\n      const cacheRequest = cachePutStub.args[0][0];\n      const cacheResponse = cachePutStub.args[0][1];\n      expect(cacheRequest).to.equal(putRequest);\n      expect(cacheResponse).to.equal(putResponse);\n    });\n\n    // This covers opaque responses (0) and partial content responses (206).\n    for (const status of [0, 206]) {\n      it(`should not cache response.status of ${status} by default`, async function () {\n        const cacheName = 'test-cache';\n        const testCache = await caches.open(cacheName);\n        const cacheOpenStub = sandbox\n          .stub(self.caches, 'open')\n          .resolves(testCache);\n        const cachePutSpy = sandbox.spy(testCache, 'put');\n\n        const putRequest = new Request('/test/string');\n\n        const putResponse = new Response('');\n        // You normally can't generate a 0 response status programmatically,\n        // but we can fake it with `Object.defineProperty()` after creation.\n        Object.defineProperty(putResponse, 'status', {value: status});\n\n        const handler = createStrategyHandler({cacheName});\n        await handler.cachePut(putRequest, putResponse);\n\n        expect(cacheOpenStub.callCount).to.equal(0);\n        expect(cachePutSpy.callCount).to.equal(0);\n      });\n    }\n\n    it(`should throw when trying to cache POST requests in dev mode`, async function () {\n      if (process.env.NODE_ENV === 'production') this.skip();\n\n      const testCache = await caches.open('TEST-CACHE');\n      const cacheOpenStub = sandbox.stub(self.caches, 'open');\n      const cachePutStub = sandbox.stub(testCache, 'put');\n      cacheOpenStub.callsFake(async (cacheName) => {\n        return testCache;\n      });\n      const putRequest = new Request('/test/string', {\n        method: 'POST',\n      });\n      const putResponse = new Response('Response for /test/string');\n\n      const handler = createStrategyHandler({cacheName: 'CACHE NAME'});\n\n      await expectError(async () => {\n        await handler.cachePut(putRequest, putResponse);\n      }, 'attempt-to-cache-non-get-request');\n\n      expect(cacheOpenStub.callCount).to.equal(0);\n      expect(cachePutStub.callCount).to.equal(0);\n    });\n\n    it(`should log when caching a response with a Vary: header in dev mode`, async function () {\n      if (process.env.NODE_ENV === 'production') {\n        this.skip();\n      }\n\n      const loggerSpy = sandbox.spy(logger, 'debug');\n\n      const request = new Request('/test/vary');\n      const response = new Response('Vary response', {\n        headers: {Vary: 'user-agent'},\n      });\n\n      const handler = createStrategyHandler({cacheName: 'vary-test'});\n      await handler.cachePut(request, response);\n\n      // Just a basic test for the logged string.\n      expect(loggerSpy.firstCall.args[0]).to.include('ignoreVary');\n    });\n\n    it(`should call cacheDidUpdate`, async function () {\n      const firstPlugin = {\n        cacheDidUpdate: () => {},\n      };\n\n      const secondPlugin = {\n        cacheDidUpdate: () => {},\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cacheDidUpdate');\n      const spyTwo = sandbox.spy(secondPlugin, 'cacheDidUpdate');\n\n      const putRequest = new Request('/test/string');\n      const putResponse = new Response('Response for /test/string', {\n        headers: {'x-id': '1'},\n      });\n\n      const handler = createStrategyHandler({\n        cacheName: 'TODO-CHANGE-ME',\n        plugins: [\n          firstPlugin,\n          {\n            // Should work without require functions\n          },\n          secondPlugin,\n        ],\n      });\n\n      await handler.cachePut(putRequest, putResponse);\n\n      [spyOne, spyTwo].forEach((pluginSpy) => {\n        expect(pluginSpy.callCount).to.equal(1);\n        expect(pluginSpy.args[0][0].cacheName).to.equal('TODO-CHANGE-ME');\n        expect(pluginSpy.args[0][0].request).to.equal(putRequest);\n        expect(pluginSpy.args[0][0].oldResponse).to.equal(undefined);\n        // `newResponse` is cloned, so don't compare for object equality.\n        expect(pluginSpy.args[0][0].newResponse.headers.get('x-id')).to.equal(\n          '1',\n        );\n\n        // Reset so the spies are clean for next step in the test.\n        pluginSpy.resetHistory();\n      });\n\n      const putResponseUpdate = new Response(\n        'Response for /test/string number 2',\n        {\n          headers: {'x-id': '2'},\n        },\n      );\n\n      await handler.cachePut(putRequest, putResponseUpdate);\n\n      [spyOne, spyTwo].forEach((pluginSpy) => {\n        expect(pluginSpy.callCount).to.equal(1);\n        expect(pluginSpy.args[0][0].cacheName).to.equal('TODO-CHANGE-ME');\n        expect(pluginSpy.args[0][0].request).to.equal(putRequest);\n        // `oldResponse` and `newResponse` are cloned,\n        // so don't compare for object equality.\n        expect(pluginSpy.args[0][0].oldResponse.headers.get('x-id')).to.equal(\n          '1',\n        );\n        expect(pluginSpy.args[0][0].newResponse.headers.get('x-id')).to.equal(\n          '2',\n        );\n      });\n    });\n\n    it(`should call cacheWillUpdate`, async function () {\n      const firstPluginResponse = new Response('Response for /test/string/1');\n      const firstPlugin = {\n        cacheWillUpdate: () => {\n          return firstPluginResponse;\n        },\n      };\n\n      const secondPlugin = {\n        cacheWillUpdate: () => {\n          return new Response('Response for /test/string/2');\n        },\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cacheWillUpdate');\n      const spyTwo = sandbox.spy(secondPlugin, 'cacheWillUpdate');\n\n      const putRequest = new Request('/test/string');\n      const putResponse = new Response('Response for /test/string');\n\n      const handler = createStrategyHandler({\n        cacheName: 'TODO-CHANGE-ME',\n        plugins: [\n          firstPlugin,\n          {\n            // Should work without require functions\n          },\n          secondPlugin,\n        ],\n      });\n\n      await handler.cachePut(putRequest, putResponse);\n\n      expect(spyOne.callCount).to.equal(1);\n\n      expect(\n        spyOne.calledWith(\n          sinon.match({\n            request: putRequest,\n            response: putResponse,\n            event: handler.event,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyTwo.callCount).to.equal(1);\n      expect(\n        spyTwo.calledWith(\n          sinon.match({\n            request: putRequest,\n            response: firstPluginResponse,\n            event: handler.event,\n          }),\n        ),\n      ).to.be.true;\n    });\n\n    it(`should call cacheKeyWillBeUsed`, async function () {\n      const cacheName = 'cacheKeyWillBeUsed-test-cache';\n      const cache = await caches.open(cacheName);\n      sandbox.stub(caches, 'open').resolves(cache);\n      const cachePutStub = sandbox.stub(cache, 'put').resolves();\n\n      const firstPluginReturnValue = new Request('/firstPlugin');\n      const firstPlugin = {\n        cacheKeyWillBeUsed: () => firstPluginReturnValue,\n      };\n\n      const secondPlugin = {\n        // This string will be converted to a Request.\n        cacheKeyWillBeUsed: () => '/secondPlugin',\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cacheKeyWillBeUsed');\n      const spyTwo = sandbox.spy(secondPlugin, 'cacheKeyWillBeUsed');\n\n      const initialRequest = new Request('/noPlugin');\n      const response = new Response('Test response.');\n\n      const handler = createStrategyHandler({\n        cacheName,\n        plugins: [\n          firstPlugin,\n          {}, // Intentionally empty to ensure it's filtered out.\n          secondPlugin,\n        ],\n      });\n\n      await handler.cachePut(initialRequest, response);\n\n      expect(\n        spyOne.calledOnceWith(\n          sinon.match({\n            mode: 'write',\n            request: initialRequest,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyOne.thisValues[0]).to.eql(firstPlugin);\n\n      expect(\n        spyTwo.calledOnceWith(\n          sinon.match({\n            mode: 'write',\n            request: firstPluginReturnValue,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyTwo.thisValues[0]).to.eql(secondPlugin);\n\n      expect(cachePutStub.calledOnce).to.be.true;\n      // Check the url of the Request passed to cache.put().\n      expect(cachePutStub.args[0][0].url).to.eql(\n        `${self.location.origin}/secondPlugin`,\n      );\n      expect(cachePutStub.args[0][1]).to.eql(response);\n    });\n\n    it(`should allow caching of posts if cacheKeyWillBeUsed returns a get request`, async function () {\n      const cacheName = 'cacheKeyWillBeUsed-test-cache';\n      const cache = await caches.open(cacheName);\n      sandbox.stub(caches, 'open').resolves(cache);\n      const cachePutStub = sandbox.stub(cache, 'put').resolves();\n\n      const firstPluginReturnValue = new Request('/firstPlugin', {\n        method: 'get',\n      });\n\n      const firstPlugin = {\n        cacheKeyWillBeUsed: () => firstPluginReturnValue,\n      };\n\n      const secondPlugin = {\n        // This string will be converted to a Request.\n        cacheKeyWillBeUsed: () => '/secondPlugin',\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cacheKeyWillBeUsed');\n      const spyTwo = sandbox.spy(secondPlugin, 'cacheKeyWillBeUsed');\n\n      const initialRequest = new Request('/noPlugin', {\n        method: 'post',\n      });\n\n      const response = new Response('Test response.');\n\n      const handler = createStrategyHandler({\n        cacheName,\n        plugins: [\n          firstPlugin,\n          {}, // Intentionally empty to ensure it's filtered out.\n          secondPlugin,\n        ],\n      });\n\n      await handler.cachePut(initialRequest, response);\n\n      expect(\n        spyOne.calledOnceWith(\n          sinon.match({\n            mode: 'write',\n            request: initialRequest,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyOne.thisValues[0]).to.eql(firstPlugin);\n\n      expect(\n        spyTwo.calledOnceWith(\n          sinon.match({\n            mode: 'write',\n            request: firstPluginReturnValue,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyTwo.thisValues[0]).to.eql(secondPlugin);\n\n      expect(cachePutStub.calledOnce).to.be.true;\n      // Check the url of the Request passed to cache.put().\n      expect(cachePutStub.args[0][0].url).to.eql(\n        `${self.location.origin}/secondPlugin`,\n      );\n      expect(cachePutStub.args[0][1]).to.eql(response);\n    });\n\n    it(`should call the quota exceeded callbacks when there's a QuotaExceeded error`, async function () {\n      const callback1 = sandbox.stub();\n      registerQuotaErrorCallback(callback1);\n      const callback2 = sandbox.stub();\n      registerQuotaErrorCallback(callback2);\n\n      const cacheName = 'test-cache';\n      const testCache = await caches.open(cacheName);\n      sandbox.stub(self.caches, 'open').returns(Promise.resolve(testCache));\n      sandbox.stub(testCache, 'put').throws('QuotaExceededError');\n\n      const handler = createStrategyHandler({cacheName});\n\n      try {\n        await handler.cachePut('ignored', new Response());\n        throw new Error('Unexpected success.');\n      } catch (error) {\n        expect(error.name).to.eql('QuotaExceededError');\n      }\n      expect(callback1.calledOnce).to.be.true;\n      expect(callback2.calledOnce).to.be.true;\n    });\n\n    it(`should not call the quota exceeded callbacks when there's a non-QuotaExceeded error`, async function () {\n      const callback = sandbox.stub();\n      registerQuotaErrorCallback(callback);\n\n      const cacheName = 'test-cache';\n      const testCache = await caches.open(cacheName);\n      sandbox.stub(self.caches, 'open').returns(Promise.resolve(testCache));\n      sandbox.stub(testCache, 'put').throws('NetworkError');\n\n      const handler = createStrategyHandler({cacheName});\n\n      try {\n        await handler.cachePut('ignored', new Response());\n        throw new Error('Unexpected success.');\n      } catch (error) {\n        expect(error.name).to.eql('NetworkError');\n      }\n      expect(callback.called).to.be.false;\n    });\n  });\n\n  // These tests were copied from `test-cacheWrapper` to ensure we don't\n  // lose any of the existing behavior in the update.\n  describe(`.cacheMatch()`, function () {\n    it(`should use the matchOptions that were provided to put()`, async function () {\n      const matchOptions = {\n        ignoreSearch: true,\n      };\n      const cacheName = 'test-cache';\n\n      const matchSpy = sandbox.spy(self.caches, 'match');\n\n      const handler = createStrategyHandler({\n        cacheName,\n        matchOptions,\n        plugins: [\n          {\n            cacheDidUpdate: () => {},\n          },\n        ],\n      });\n\n      await handler.cacheMatch(\n        new Request('/test/request'),\n        new Response('test'),\n      );\n\n      expect(matchSpy.calledOnce).to.be.true;\n      expect(matchSpy.args[0][1]).to.eql(\n        Object.assign({}, matchOptions, {\n          cacheName,\n        }),\n      );\n    });\n\n    it(`should call cachedResponseWillBeUsed`, async function () {\n      const options = {};\n      const matchCacheName = 'MATCH-CACHE-NAME';\n      const matchRequest = new Request('/test/string');\n      const matchResponse = new Response('Response for /test/string', {\n        headers: {'x-id': '1'},\n      });\n\n      const firstPluginResponse = new Response('Response for /test/string/1', {\n        headers: {'x-id': '2'},\n      });\n      const secondPluginResponse = new Response('Response for /test/string/2', {\n        headers: {'x-id': '3'},\n      });\n\n      const firstPlugin = {\n        cachedResponseWillBeUsed: ({\n          cacheName,\n          request,\n          matchOptions,\n          cachedResponse,\n        }) => {\n          expect(request).to.equal(matchRequest);\n          expect(cacheName).to.equal(matchCacheName);\n          expect(matchOptions).to.equal(options);\n          expect(cachedResponse.headers.get('x-id')).to.equal(\n            matchResponse.headers.get('x-id'),\n          );\n\n          return firstPluginResponse;\n        },\n      };\n\n      const secondPlugin = {\n        cachedResponseWillBeUsed: ({\n          cacheName,\n          request,\n          matchOptions,\n          cachedResponse,\n        }) => {\n          expect(request).to.equal(matchRequest);\n          expect(cacheName).to.equal(matchCacheName);\n          expect(matchOptions).to.equal(options);\n          expect(cachedResponse.headers.get('x-id')).to.equal(\n            firstPluginResponse.headers.get('x-id'),\n          );\n          return secondPluginResponse;\n        },\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cachedResponseWillBeUsed');\n      const spyTwo = sandbox.spy(secondPlugin, 'cachedResponseWillBeUsed');\n\n      const openCache = await caches.open(matchCacheName);\n      await openCache.put(matchRequest, matchResponse);\n\n      const handler = createStrategyHandler({\n        cacheName: matchCacheName,\n        matchOptions: options,\n        plugins: [\n          firstPlugin,\n          {\n            // Should work without require functions\n          },\n          secondPlugin,\n        ],\n      });\n\n      const result = await handler.cacheMatch(matchRequest);\n\n      expect(result.headers.get('x-id')).to.equal(\n        secondPluginResponse.headers.get('x-id'),\n      );\n      expect(spyOne.callCount).to.equal(1);\n      expect(spyTwo.callCount).to.equal(1);\n    });\n\n    it(`should call cacheKeyWillBeUsed`, async function () {\n      const cacheName = 'cacheKeyWillBeUsed-test-cache';\n      const cacheMatchStub = sandbox\n        .stub(self.caches, 'match')\n        .resolves(new Response('Test response.'));\n\n      const firstPluginReturnValue = new Request('/firstPlugin');\n      const firstPlugin = {\n        cacheKeyWillBeUsed: () => firstPluginReturnValue,\n      };\n\n      const secondPlugin = {\n        // This string will be converted to a Request.\n        cacheKeyWillBeUsed: () => '/secondPlugin',\n      };\n\n      const spyOne = sandbox.spy(firstPlugin, 'cacheKeyWillBeUsed');\n      const spyTwo = sandbox.spy(secondPlugin, 'cacheKeyWillBeUsed');\n\n      const initialRequest = new Request('/noPlugin');\n\n      const handler = createStrategyHandler({\n        cacheName,\n        request: initialRequest,\n        plugins: [\n          firstPlugin,\n          {}, // Intentionally empty to ensure it's filtered out.\n          secondPlugin,\n        ],\n      });\n\n      await handler.cacheMatch(initialRequest);\n\n      expect(\n        spyOne.calledOnceWith(\n          sinon.match({\n            mode: 'read',\n            request: initialRequest,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyOne.thisValues[0]).to.eql(firstPlugin);\n\n      expect(\n        spyTwo.calledOnceWith(\n          sinon.match({\n            mode: 'read',\n            request: firstPluginReturnValue,\n          }),\n        ),\n      ).to.be.true;\n      expect(spyTwo.thisValues[0]).to.eql(secondPlugin);\n\n      expect(cacheMatchStub.calledOnce).to.be.true;\n      // Check the url of the Request passed to cache.put().\n      expect(cacheMatchStub.args[0][0].url).to.eql(\n        `${self.location.origin}/secondPlugin`,\n      );\n    });\n  });\n\n  describe('fetchAndCachePut()', function () {\n    it('calls fetch and then cachePut with the response', async function () {\n      const handler = createStrategyHandler();\n\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n      sandbox.stub(Cache.prototype, 'put');\n\n      sandbox.spy(handler, 'fetch');\n      sandbox.spy(handler, 'cachePut');\n\n      const response = await handler.fetchAndCachePut('/test');\n      expect(handler.fetch.callCount).to.equal(1);\n      expect(await response.text()).to.equal('fetch response');\n\n      expect(handler.cachePut.callCount).to.equal(1);\n      const spyCall = handler.cachePut.firstCall;\n      expect(spyCall.args[0]).to.equal('/test');\n      expect(await spyCall.args[1].text()).to.equal('fetch response');\n    });\n\n    it(`should work with request string`, async function () {\n      const handler = createStrategyHandler();\n\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n\n      const response = await handler.fetchAndCachePut('/url');\n      expect(await response.text()).to.equal('fetch response');\n\n      expect(self.fetch.args[0][0]).to.be.instanceOf(Request);\n      expect(self.fetch.args[0][0].url).to.equal(location.origin + '/url');\n    });\n\n    it(`should work with request object`, async function () {\n      const handler = createStrategyHandler();\n\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n\n      const response = await handler.fetchAndCachePut(new Request('/request'));\n      expect(await response.text()).to.equal('fetch response');\n\n      expect(self.fetch.args[0][0]).to.be.instanceOf(Request);\n      expect(self.fetch.args[0][0].url).to.equal(location.origin + '/request');\n    });\n\n    it(`should keep the handler alive until the cache put settles`, async function () {\n      const handler = createStrategyHandler();\n\n      sandbox.stub(self, 'fetch').resolves(new Response('fetch response'));\n      sandbox.spy(handler, 'cachePut');\n      sandbox.spy(handler, 'waitUntil');\n\n      await handler.fetchAndCachePut('/url');\n\n      await handler.doneWaiting();\n      expect(\n        handler.waitUntil.calledWith(handler.cachePut.firstCall.returnValue),\n      ).to.be.true;\n    });\n  });\n\n  describe('hasCallback()', function () {\n    it('return true if the strategy contains at least one plugin with the given callback', function () {\n      const handler = createStrategyHandler({\n        plugins: [\n          {\n            handlerWillStart: sandbox.spy(),\n          },\n          {\n            handlerWillStart: sandbox.spy(),\n            handlerDidComplete: sandbox.spy(),\n          },\n          {\n            // Empty plugin.\n          },\n          {\n            requestWillFetch: sandbox.spy(),\n          },\n        ],\n      });\n\n      expect(handler.hasCallback('handlerWillStart')).to.equal(true);\n      expect(handler.hasCallback('handlerDidComplete')).to.equal(true);\n      expect(handler.hasCallback('requestWillFetch')).to.equal(true);\n      expect(handler.hasCallback('cacheKeyWillBeUsed')).to.equal(false);\n      expect(handler.hasCallback('fetchDidFail')).to.equal(false);\n    });\n  });\n\n  describe('runCallbacks()', function () {\n    it('runs all matching callbacks with the correct arguments', async function () {\n      const spy = sandbox.spy();\n      const handler = createStrategyHandler({\n        plugins: [\n          {\n            handlerWillStart: spy,\n          },\n          {\n            // Empty plugin.\n          },\n          {\n            handlerWillStart: spy,\n          },\n        ],\n      });\n\n      const request = new Request('test-request');\n      const event = new FetchEvent('fetch', {request});\n\n      await handler.runCallbacks('handlerWillStart', {request, event});\n\n      expect(spy.callCount).to.equal(2);\n\n      expect(spy.firstCall.args[0].request).to.equal(request);\n      expect(spy.firstCall.args[0].event).to.equal(event);\n      expect(spy.firstCall.args[0].state).to.be.instanceOf(Object);\n\n      expect(spy.secondCall.args[0].request).to.equal(request);\n      expect(spy.secondCall.args[0].event).to.equal(event);\n      expect(spy.secondCall.args[0].state).to.be.instanceOf(Object);\n    });\n\n    it('uses a unique state object per plugin', async function () {\n      const plugins = [\n        {\n          handlerWillStart: sandbox.spy(),\n          handlerDidComplete: sandbox.spy(),\n        },\n        {\n          handlerWillStart: sandbox.spy(),\n        },\n        {\n          // Empty plugin.\n        },\n        {\n          handlerWillStart: sandbox.spy(),\n          handlerDidComplete: sandbox.spy(),\n        },\n      ];\n\n      const handler = createStrategyHandler({plugins});\n\n      const request = new Request('test-request');\n      const event = new FetchEvent('fetch', {request});\n\n      await handler.runCallbacks('handlerWillStart', {request, event});\n\n      // All of the `handlerWillStart` callbacks are from different plugins,\n      // so they should all have different state objects.\n      expect(plugins[0].handlerWillStart.firstCall.args[0].state).not.to.equal(\n        plugins[1].handlerWillStart.firstCall.args[0].state,\n      );\n      expect(plugins[1].handlerWillStart.firstCall.args[0].state).not.to.equal(\n        plugins[3].handlerWillStart.firstCall.args[0].state,\n      );\n\n      await handler.runCallbacks('handlerDidComplete', {request, event});\n\n      // All of the `handlerDidComplete` callbacks are from different plugins,\n      // so they should also all have different state objects.\n      expect(\n        plugins[0].handlerDidComplete.firstCall.args[0].state,\n      ).not.to.equal(plugins[3].handlerDidComplete.firstCall.args[0].state);\n\n      // State objects from callbacks in the same plugin should be the same.\n      expect(plugins[0].handlerWillStart.firstCall.args[0].state).to.equal(\n        plugins[0].handlerDidComplete.firstCall.args[0].state,\n      );\n      expect(plugins[3].handlerWillStart.firstCall.args[0].state).to.equal(\n        plugins[3].handlerDidComplete.firstCall.args[0].state,\n      );\n    });\n  });\n\n  describe('iterateCallbacks()', function () {\n    it('loops through all matching callbacks', async function () {\n      const spy = sandbox.spy();\n      const handler = createStrategyHandler({\n        plugins: [\n          {\n            handlerWillStart: spy,\n          },\n          {\n            // Empty plugin.\n          },\n          {\n            handlerWillStart: spy,\n          },\n        ],\n      });\n\n      const request = new Request('/test-request');\n      const event = new FetchEvent('fetch', {request});\n\n      for (const callback of handler.iterateCallbacks('handlerWillStart')) {\n        await callback({event, request});\n\n        expect(spy.callCount).to.equal(1);\n        expect(spy.firstCall.args[0].request).to.equal(request);\n        expect(spy.firstCall.args[0].event).to.equal(event);\n        expect(spy.firstCall.args[0].state).to.be.instanceOf(Object);\n\n        spy.resetHistory();\n      }\n    });\n\n    it('uses a unique state object per plugin', async function () {\n      const plugins = [\n        {\n          handlerWillStart: sandbox.spy(),\n          handlerDidComplete: sandbox.spy(),\n        },\n        {\n          handlerWillStart: sandbox.spy(),\n        },\n        {\n          // Empty plugin.\n        },\n        {\n          handlerWillStart: sandbox.spy(),\n          handlerDidComplete: sandbox.spy(),\n        },\n      ];\n\n      const handler = createStrategyHandler({plugins});\n\n      const request = new Request('test-request');\n      const event = new FetchEvent('fetch', {request});\n\n      for (const callback of handler.iterateCallbacks('handlerWillStart')) {\n        await callback({event, request});\n      }\n\n      // All of the `handlerWillStart` callbacks are from different plugins,\n      // so they should all have different state objects.\n      expect(plugins[0].handlerWillStart.firstCall.args[0].state).not.to.equal(\n        plugins[1].handlerWillStart.firstCall.args[0].state,\n      );\n      expect(plugins[1].handlerWillStart.firstCall.args[0].state).not.to.equal(\n        plugins[3].handlerWillStart.firstCall.args[0].state,\n      );\n\n      for (const callback of handler.iterateCallbacks('handlerDidComplete')) {\n        await callback({event, request});\n      }\n\n      // All of the `handlerDidComplete` callbacks are from different plugins,\n      // so they should also all have different state objects.\n      expect(\n        plugins[0].handlerDidComplete.firstCall.args[0].state,\n      ).not.to.equal(plugins[3].handlerDidComplete.firstCall.args[0].state);\n\n      // State objects from callbacks in the same plugin should be the same.\n      expect(plugins[0].handlerWillStart.firstCall.args[0].state).to.equal(\n        plugins[0].handlerDidComplete.firstCall.args[0].state,\n      );\n      expect(plugins[3].handlerWillStart.firstCall.args[0].state).to.equal(\n        plugins[3].handlerDidComplete.firstCall.args[0].state,\n      );\n    });\n\n    it('returns the value returned by the callback', async function () {\n      let request = new Request('/test-request');\n      const event = new FetchEvent('fetch', {request});\n\n      const handler = createStrategyHandler({\n        plugins: [\n          {\n            handlerWillStart({event, request}) {\n              return new Request(request.url + '+1');\n            },\n          },\n          {\n            // Empty plugin.\n          },\n          {\n            handlerWillStart({event, request}) {\n              return new Request(request.url + '+2');\n            },\n          },\n        ],\n      });\n\n      for (const callback of handler.iterateCallbacks('handlerWillStart')) {\n        request = await callback({event, request});\n      }\n\n      expect(request.url).to.equal(location.origin + '/test-request+1+2');\n    });\n  });\n\n  describe(`getCacheKey`, function () {\n    it(`returns the cackeKey after applying plugins`, async function () {\n      const request = new Request('/test');\n      const handler = createStrategyHandler({\n        plugins: [\n          {\n            cacheKeyWillBeUsed({mode, request}) {\n              return new Request(request.url + '+1');\n            },\n          },\n          {\n            cacheKeyWillBeUsed({mode, request}) {\n              return mode === 'read'\n                ? new Request(request.url + '+read')\n                : request;\n            },\n          },\n          {\n            cacheKeyWillBeUsed({mode, request}) {\n              return mode === 'write'\n                ? new Request(request.url + '+write')\n                : request;\n            },\n          },\n        ],\n      });\n\n      const readCacheKey = await handler.getCacheKey(request, 'read');\n      expect(readCacheKey.url).to.equal(location.origin + '/test+1+read');\n\n      const writeCacheKey = await handler.getCacheKey(request, 'write');\n      expect(writeCacheKey.url).to.equal(location.origin + '/test+1+write');\n    });\n\n    it(`caches the key using request + mode avoid repeat invocations`, async function () {\n      const request = new Request('/test');\n      const plugin = {\n        cacheKeyWillBeUsed({mode, request}) {\n          return new Request(request.url + '+' + mode);\n        },\n      };\n      sandbox.spy(plugin, 'cacheKeyWillBeUsed');\n\n      const handler = createStrategyHandler({\n        plugins: [plugin],\n      });\n\n      const readCacheKey = await handler.getCacheKey(request, 'read');\n      expect(readCacheKey.url).to.equal(location.origin + '/test+read');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(1);\n\n      await handler.getCacheKey(request, 'read');\n      await handler.getCacheKey(request, 'read');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(1);\n\n      const writeCacheKey = await handler.getCacheKey(request, 'write');\n      expect(writeCacheKey.url).to.equal(location.origin + '/test+write');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(2);\n\n      await handler.getCacheKey(request, 'write');\n      await handler.getCacheKey(request, 'write');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(2);\n\n      // See https://github.com/GoogleChrome/workbox/issues/2972\n      const secondRequest = new Request('/test2');\n\n      await handler.getCacheKey(secondRequest, 'read');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(3);\n\n      await handler.getCacheKey(secondRequest, 'write');\n      await handler.getCacheKey(secondRequest, 'write');\n      expect(plugin.cacheKeyWillBeUsed.callCount).to.equal(4);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-strategies/sw/test-UsageWithRouter.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {CacheFirst} from 'workbox-strategies/CacheFirst.mjs';\nimport {CacheOnly} from 'workbox-strategies/CacheOnly.mjs';\nimport {NetworkFirst} from 'workbox-strategies/NetworkFirst.mjs';\nimport {NetworkOnly} from 'workbox-strategies/NetworkOnly.mjs';\nimport {StaleWhileRevalidate} from 'workbox-strategies/StaleWhileRevalidate.mjs';\nimport {Router} from 'workbox-routing/Router.mjs';\nimport {Route} from 'workbox-routing/Route.mjs';\nimport {\n  eventDoneWaiting,\n  spyOnEvent,\n} from '../../../infra/testing/helpers/extendable-event-utils.mjs';\nimport {generateUniqueResponse} from '../../../infra/testing/helpers/generateUniqueResponse.mjs';\n\ndescribe(`Router`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(async function () {\n    sandbox.restore();\n    sandbox.stub(self, 'fetch').resolves(generateUniqueResponse());\n    sandbox.stub(Cache.prototype, 'match').resolves(generateUniqueResponse());\n  });\n\n  after(async function () {\n    sandbox.restore();\n  });\n\n  [\n    CacheFirst,\n    CacheOnly,\n    NetworkFirst,\n    NetworkOnly,\n    StaleWhileRevalidate,\n  ].forEach((StrategyClass) => {\n    it(`should work with the '${StrategyClass.name}' strategy`, async function () {\n      const router = new Router();\n      router.registerRoute(new Route(() => true, new StrategyClass()));\n\n      const request = new Request(self.location);\n      const event = new FetchEvent('fetch', {request});\n      spyOnEvent(event);\n\n      router.handleRequest({request, event});\n      await eventDoneWaiting(event);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-streams/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {expect} = require('chai');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\nconst activateAndControlSW = require('../../../infra/testing/activate-and-control');\nconst crossOriginServer = require('../../../infra/testing/server/cross-origin-server');\nconst upath = require('upath');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-streams]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-streams/sw/');\n  });\n});\n\ndescribe(`[workbox-streams] Integration Tests`, function () {\n  const testServerAddress = server.getAddress();\n  const testingURL = `${testServerAddress}/test/workbox-streams/static/`;\n  const swURL = `${testingURL}sw.js`;\n  let crossOriginURL;\n\n  before(async function () {\n    await webdriver.get(testingURL);\n    await activateAndControlSW(swURL);\n    const crossOrigin = await crossOriginServer.start(\n      upath.join('..', 'static'),\n    );\n    crossOriginURL = `${crossOrigin}/4.txt`;\n  });\n\n  after(function () {\n    crossOriginServer.stop();\n  });\n\n  for (const testCase of ['concatenate', 'concatenateToResponse', 'strategy']) {\n    it(`should return the expected response for the '${testCase}' approach`, async function () {\n      const {text, headers} = await webdriver.executeAsyncScript(\n        async (testCase, cb) => {\n          try {\n            const response = await fetch(new URL(testCase, location));\n            const headers = [...response.headers].sort((a, b) => {\n              return a[0] > b[0];\n            });\n            const text = await response.text();\n            cb({headers, text});\n          } catch (error) {\n            cb({text: error.message});\n          }\n        },\n        testCase,\n      );\n\n      if (text === 'No streams support') {\n        this.skip();\n      } else {\n        expect(text).to.eql('01234\\n');\n        expect(headers).to.eql([\n          ['content-type', 'text/plain'],\n          ['x-test-case', testCase],\n        ]);\n      }\n    });\n  }\n\n  it(`should error when a stream source results in an opaque response`, async function () {\n    const {text} = await webdriver.executeAsyncScript(\n      async (crossOriginURL, cb) => {\n        try {\n          const url = new URL('/crossOriginURL', location);\n          url.searchParams.set('cross-origin-url', crossOriginURL);\n\n          const response = await fetch(url);\n          const text = await response.text();\n          cb({text});\n        } catch (error) {\n          cb({text: error.name});\n        }\n      },\n      crossOriginURL,\n    );\n\n    if (text === 'No streams support') {\n      this.skip();\n    } else {\n      // The exception name varies from browser to browser.\n      expect(text).to.be.oneOf(['TypeError', 'AbortError']);\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-streams/static/4.txt",
    "content": "4\n"
  },
  {
    "path": "test/workbox-streams/static/index.html",
    "content": "<html>\n  <body>\n    <p>\n      You need to manually call\n      <code>navigator.serviceWorker.register('sw.js')</code>\n    </p>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-streams/static/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-core');\nimportScripts('/__WORKBOX/buildFile/workbox-routing');\nimportScripts('/__WORKBOX/buildFile/workbox-streams');\n\nself.addEventListener('install', () => self.skipWaiting());\nself.addEventListener('activate', () => self.clients.claim());\n\n// Test a variety of different sources.\nconst getSourceFunctions = () => [\n  () => Promise.resolve('0'),\n  () => Promise.resolve(new Response(1)),\n  () => '2',\n  () => new Response(3),\n  () => fetch('4.txt'),\n];\n\nself.addEventListener('fetch', (event) => {\n  const url = new URL(event.request.url);\n  const crossOriginURL = url.searchParams.get('cross-origin-url');\n\n  if (!workbox.streams.isSupported()) {\n    event.respondWith(new Response('No streams support'));\n  } else if (crossOriginURL) {\n    const {done, response} = workbox.streams.concatenateToResponse(\n      [\n        () => 'this will',\n        () => fetch(crossOriginURL, {mode: 'no-cors'}),\n        () => 'error',\n      ].map((f) => f()),\n      {\n        'content-type': 'text/plain',\n        'x-test-case': 'crossOriginURL',\n      },\n    );\n    event.respondWith(response);\n    event.waitUntil(done);\n  } else if (url.pathname.endsWith('concatenateToResponse')) {\n    const {done, response} = workbox.streams.concatenateToResponse(\n      getSourceFunctions().map((f) => f()),\n      {\n        'content-type': 'text/plain',\n        'x-test-case': 'concatenateToResponse',\n      },\n    );\n    event.respondWith(response);\n    event.waitUntil(done);\n  } else if (url.pathname.endsWith('concatenate')) {\n    const {done, stream} = workbox.streams.concatenate(\n      getSourceFunctions().map((f) => f()),\n    );\n    const response = new Response(stream, {\n      headers: {\n        'content-type': 'text/plain',\n        'x-test-case': 'concatenate',\n      },\n    });\n    event.respondWith(response);\n    event.waitUntil(done);\n  }\n});\n\nworkbox.routing.registerRoute(\n  ({url}) => url.pathname.endsWith('strategy'),\n  workbox.streams.strategy(getSourceFunctions(), {\n    'content-type': 'text/plain',\n    'x-test-case': 'strategy',\n  }),\n);\n"
  },
  {
    "path": "test/workbox-streams/sw/test-isSupported.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {isSupported} from 'workbox-streams/isSupported.mjs';\n\ndescribe(`isSupported`, function () {\n  it(`should return true when ReadableStream is available`, async function () {\n    try {\n      new ReadableStream({start() {}});\n\n      expect(isSupported()).to.be.true;\n    } catch (error) {\n      expect(isSupported()).to.be.false;\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-streams/sw/utils/test-createHeaders.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {createHeaders} from 'workbox-streams/utils/createHeaders.mjs';\n\ndescribe(`createHeaders`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n  });\n\n  afterEach(function () {\n    sandbox.restore();\n  });\n\n  const DEFAULT_CONTENT_TYPE = ['content-type', 'text/html'];\n\n  it(`should use the default Content-Type, and construct with an empty object, when headersInit is undefined`, function () {\n    const headersSpy = sandbox.spy(self, 'Headers');\n    const headers = createHeaders();\n    expect([...headers]).to.eql([DEFAULT_CONTENT_TYPE]);\n    expect(headersSpy.calledOnce).to.be.true;\n    // See https://github.com/GoogleChrome/workbox/issues/1461\n    expect(headersSpy.args[0][0]).to.eql({});\n  });\n\n  it(`should use the default Content-Type along with custom headersInit values`, function () {\n    const headersInit = {\n      'x-one': '1',\n      'x-two': '2',\n    };\n    const headers = createHeaders(headersInit);\n\n    expect([...headers]).to.eql([\n      DEFAULT_CONTENT_TYPE,\n      ...Object.entries(headersInit),\n    ]);\n  });\n\n  it(`should use a custom Content-Type`, function () {\n    const headersInit = {\n      'content-type': 'text/plain',\n    };\n    const headers = createHeaders(headersInit);\n    expect([...headers]).to.eql(Object.entries(headersInit));\n  });\n});\n"
  },
  {
    "path": "test/workbox-sw/integration/test-all.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst expect = require('chai').expect;\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\ndescribe(`[workbox-sw]`, function () {\n  it(`passes all SW unit tests`, async function () {\n    await runUnitTests('/test/workbox-sw/sw/');\n  });\n});\n\ndescribe(`WorkboxSW interface`, function () {\n  const wasRegistrationSuccessful = (swFile) => {\n    return webdriver.executeAsyncScript((swFile, cb) => {\n      // Invokes cb() with true when registration succeeds, and false otherwise.\n      navigator.serviceWorker\n        .register(swFile)\n        .then(() => cb(true))\n        .catch(() => cb(false));\n    }, swFile);\n  };\n\n  const testServerAddress = server.getAddress();\n  const testPageURL = `${testServerAddress}/test/workbox-sw/static/integration/`;\n\n  before(async function () {\n    await webdriver.get(testPageURL);\n  });\n\n  it(`should fail to activate an invalid SW which loads non-existent modules`, async function () {\n    const invalidSW = 'invalid-sw.js';\n    const outcome = await wasRegistrationSuccessful(invalidSW);\n    expect(outcome).to.be.false;\n  });\n\n  it(`should be able to activate a SW which loads all valid modules`, async function () {\n    const validSW = 'valid-sw.js';\n    const outcome = await wasRegistrationSuccessful(validSW);\n    expect(outcome).to.be.true;\n  });\n});\n"
  },
  {
    "path": "test/workbox-sw/static/example.css",
    "content": "body {\n  background-color: 'green';\n}\n"
  },
  {
    "path": "test/workbox-sw/static/example.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nwindow.example = 1 + 1;\n"
  },
  {
    "path": "test/workbox-sw/static/index.html",
    "content": "<!-- This is a static file -->\n<!-- served from your routes in server.js -->\n\n<!-- You might want to try something fancier: -->\n<!-- html/nunjucks docs: https://mozilla.github.io/nunjucks/ -->\n<!-- pug: https://pugjs.org/ -->\n<!-- haml: http://haml.info/ -->\n<!-- hbs(handlebars): http://handlebarsjs.com/ -->\n\n<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n  </head>\n  <body>\n    <h1>Testing Loading of Workbox via CDN</h1>\n    <p>This page will register a service worker that uses the Workbox CDN.</p>\n    <script>\n      navigator.serviceWorker.register('sw.js');\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-sw/static/integration/index.html",
    "content": "<html>\n  <head> </head>\n  <body>\n    <p>You need to manually register sw.js</p>\n    <script>\n      window.__test = {};\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-sw/static/integration/invalid-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\n// This is expected to lead to an error.\nconst namespace = 'doesnotexist';\nconst module = self.workbox[namespace];\nif (!module) {\n  throw new Error(`self.workbox.${namespace} did not load anything.`);\n}\n"
  },
  {
    "path": "test/workbox-sw/static/integration/valid-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('/__WORKBOX/buildFile/workbox-sw');\nimportScripts('/infra/testing/comlink/sw-interface.js');\n\nworkbox.setConfig({modulePathPrefix: '/__WORKBOX/buildFile/'});\n\n// TODO: Find some way to autogenerate this list.\nconst namespaces = [\n  'backgroundSync',\n  'broadcastUpdate',\n  'cacheableResponse',\n  'core',\n  'expiration',\n  'googleAnalytics',\n  'precaching',\n  'rangeRequests',\n  'routing',\n  'strategies',\n];\n\nfor (const namespace of namespaces) {\n  const module = self.workbox[namespace];\n  if (!module) {\n    throw new Error(\n      `$self.workbox.{namespace} did not load the expected interface.`,\n    );\n  }\n}\n"
  },
  {
    "path": "test/workbox-sw/static/sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimportScripts('../../../packages/workbox-sw/build/browser/workbox-sw.js');\n\nconst wb = new self.WorkboxSW({\n  modulePathCb: (moduleName, debug) => {\n    const build = debug ? 'dev' : 'prod';\n    return `../../../packages/${moduleName}/build/browser/${moduleName}.${build}.js`;\n  },\n});\n\nwb.skipWaiting();\nwb.clientsClaim();\n\nwb.core.setLogLevel(self.workbox.core.LOG_LEVELS.debug);\n\nwb.precaching.precache(['example.css', 'example.js']);\n"
  },
  {
    "path": "test/workbox-sw/sw/controllers/test-WorkboxSW.mjs",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {WorkboxSW} from 'workbox-sw/controllers/WorkboxSW.mjs';\nimport generateTestVariants from '../../../../infra/testing/generate-variant-tests';\n\ndescribe(`WorkboxSW`, function () {\n  const sandbox = sinon.createSandbox();\n\n  beforeEach(function () {\n    sandbox.restore();\n    delete self.workbox;\n  });\n\n  after(function () {\n    sandbox.restore();\n    delete self.workbox;\n  });\n\n  describe(`constructor`, function () {\n    it(`should construct with expect defaults`, function () {\n      sandbox.stub(location, 'hostname').value('example.com');\n\n      self.workbox = new WorkboxSW();\n      expect(self.workbox._options).to.deep.equal({\n        debug: false,\n        modulePathPrefix: null,\n        modulePathCb: null,\n      });\n    });\n\n    it(`should construct debug true when on localhost`, function () {\n      sandbox.stub(location, 'hostname').value('localhost');\n\n      self.workbox = new WorkboxSW();\n      expect(self.workbox._options.debug).to.deep.equal(true);\n    });\n  });\n\n  describe(`setConfig`, function () {\n    it(`should override default config options`, function () {\n      const cb = () => {};\n      self.workbox = new WorkboxSW();\n\n      self.workbox.setConfig({\n        debug: true,\n        modulePathPrefix:\n          'http://custom-cdn.example.com/workbox-modules/v1.0.0/',\n        modulePathCb: cb,\n      });\n      expect(self.workbox._options).to.deep.equal({\n        debug: true,\n        modulePathPrefix:\n          'http://custom-cdn.example.com/workbox-modules/v1.0.0/',\n        modulePathCb: cb,\n      });\n    });\n\n    it(`should throw when invoking config after loading a module`, function () {\n      sandbox.stub(self, 'importScripts');\n\n      self.workbox = new WorkboxSW();\n\n      expect(() => {\n        self.workbox.setConfig({\n          modulePathPrefix:\n            'http://custom-cdn.example.com/workbox-modules/v1.0.0/',\n        });\n      }).not.to.throw();\n\n      // Accessing .core loads workbox-core.\n      self.workbox.core;\n\n      expect(importScripts.callCount).to.equal(1);\n      expect(importScripts.args[0][0]).to.equal(\n        `http://custom-cdn.example.com/workbox-modules/v1.0.0/workbox-core.dev.js`,\n      );\n\n      expect(() => {\n        self.workbox.setConfig({\n          modulePathPrefix:\n            'http://custom-cdn.example.com/workbox-modules/v2.0.0/',\n        });\n      }).to.throw();\n\n      expect(importScripts.callCount).to.equal(1);\n    });\n\n    it(`should not throw on no config and environment should stay the same`, function () {\n      self.workbox = new WorkboxSW();\n\n      const originalOptions = self.workbox._options;\n\n      self.workbox.setConfig();\n\n      expect(self.workbox._options).to.equal(originalOptions);\n    });\n  });\n\n  describe(`get`, function () {\n    it(`should print error message when importScripts fails`, function () {\n      const errorMessage = 'Injected error.';\n\n      sandbox.stub(self, 'importScripts').throws(new Error(errorMessage));\n      sandbox.stub(console, 'error').callsFake((errMsg) => {\n        expect(errMsg.includes('workbox-core')).to.be.true;\n        expect(errMsg.includes('WORKBOX_CDN_ROOT_URL/workbox-core')).to.be.true;\n      });\n\n      try {\n        self.workbox = new WorkboxSW();\n\n        // Accessing .core loads workbox-core.\n        self.workbox.core;\n\n        throw new Error('No error thrown.');\n      } catch (err) {\n        expect(err.message).to.equal(errorMessage);\n      }\n    });\n\n    it(`should use modulePathCb to load modules if provided`, function () {\n      sandbox.stub(self, 'importScripts');\n\n      const callbackSpy = sandbox.spy((moduleName, debug) => {\n        return `/custom-path/${moduleName}/${debug}`;\n      });\n\n      self.workbox = new WorkboxSW();\n      self.workbox.setConfig({\n        debug: true,\n        modulePathCb: callbackSpy,\n      });\n\n      // Accessing .core loads workbox-core.\n      self.workbox.core;\n\n      expect(callbackSpy.callCount).to.equal(1);\n      expect(callbackSpy.args[0]).to.deep.equal(['workbox-core', true]);\n      expect(self.importScripts.args[0]).to.deep.equal([\n        '/custom-path/workbox-core/true',\n      ]);\n    });\n\n    const modulePathVariations = [\n      {\n        prefix: '/',\n        expectedImport: '/workbox-core.dev.js',\n      },\n      {\n        prefix: '/custom-path',\n        expectedImport: '/custom-path/workbox-core.dev.js',\n      },\n      {\n        prefix: '/custom-path/',\n        expectedImport: '/custom-path/workbox-core.dev.js',\n      },\n      {\n        prefix: 'custom-path/',\n        expectedImport: 'custom-path/workbox-core.dev.js',\n      },\n      {\n        prefix: 'custom-path',\n        expectedImport: 'custom-path/workbox-core.dev.js',\n      },\n      {\n        prefix: 'custom-path/with/directories/',\n        expectedImport: 'custom-path/with/directories/workbox-core.dev.js',\n      },\n      {\n        prefix: 'custom-path/with/directories',\n        expectedImport: 'custom-path/with/directories/workbox-core.dev.js',\n      },\n      {\n        prefix: '/custom-path/with/directories',\n        expectedImport: '/custom-path/with/directories/workbox-core.dev.js',\n      },\n    ];\n    generateTestVariants(\n      `should import using modulePathPrefix`,\n      modulePathVariations,\n      async function (variant) {\n        sandbox.stub(self, 'importScripts');\n\n        self.workbox = new WorkboxSW();\n\n        self.workbox.setConfig({\n          debug: true,\n          modulePathPrefix: variant.prefix,\n        });\n\n        // Accessing .core loads workbox-core.\n        self.workbox.core;\n\n        expect(self.importScripts.args[0]).to.deep.equal([\n          variant.expectedImport,\n        ]);\n      },\n    );\n  });\n\n  SW_NAMESPACES.forEach((namespace) => {\n    // Don't test workbox-sw, which exports the `workbox` namespace.\n    if (namespace === 'workbox') return;\n\n    describe(`get ${namespace}`, function () {\n      it(`should return ${namespace}`, function () {\n        const getter = namespace.split('.')[1];\n        self.workbox = new WorkboxSW();\n        expect(self.workbox[getter]).to.exist;\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/dependency-check.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst depcheck = require('depcheck');\nconst upath = require('upath');\n\ndescribe(`[workbox-webpack-plugin] Test Dependencies`, function () {\n  it(`should have required dependencies`, function () {\n    return new Promise((resolve, reject) => {\n      depcheck(\n        upath.join(\n          __dirname,\n          '..',\n          '..',\n          '..',\n          'packages',\n          'workbox-webpack-plugin',\n        ),\n        {\n          ignoreDirs: ['test', 'build', 'demo'],\n          ignoreMatches: ['@babel/runtime'],\n        },\n        (unusedDeps) => {\n          if (unusedDeps.dependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.dependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (unusedDeps.devDependencies.length > 0) {\n            return reject(\n              new Error(\n                `Unused dependencies defined in package.json: ${JSON.stringify(\n                  unusedDeps.devDependencies,\n                )}`,\n              ),\n            );\n          }\n\n          if (Object.keys(unusedDeps.missing).length > 0) {\n            return reject(\n              new Error(\n                `Dependencies missing from package.json: ${JSON.stringify(\n                  unusedDeps.missing,\n                )}`,\n              ),\n            );\n          }\n\n          resolve();\n        },\n      );\n    });\n  });\n\n  it(`should have no devDependencies`, function () {\n    // This test exists because there have been a number of situations where\n    // dependencies have been used from the top level project and NOT from\n    // this module itself. So dependencies are checked above and devDependencies\n    // can be put in top level.\n    const pkg = require('../../../packages/workbox-build/package.json');\n    if (pkg.devDependencies && Object.keys(pkg.devDependencies) > 0) {\n      throw new Error('No devDependencies in this module.');\n    }\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v4/generate-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// workbox-webpack-plugin needs to do require('webpack'), and in order to test\n// against multiple webpack versions, we need that to resolve to whatever the\n// correct webpack is for this test.\n// See https://jeffy.info/2020/10/01/testing-multiple-webpack-versions.html\ntry {\n  delete require.cache[require.resolve('html-webpack-plugin')];\n  delete require.cache[require.resolve('webpack')];\n} catch (error) {\n  // Ignore if require.resolve() fails.\n}\nconst upath = require('upath');\nconst moduleAlias = require('module-alias');\nmoduleAlias.addAlias(\n  'html-webpack-plugin',\n  upath.resolve('node_modules', 'html-webpack-plugin-v4'),\n);\nmoduleAlias.addAlias('webpack', upath.resolve('node_modules', 'webpack-v4'));\n\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst MemoryFS = require('memory-fs');\nconst WorkerPlugin = require('worker-plugin');\nconst expect = require('chai').expect;\nconst globby = require('globby');\nconst tempy = require('tempy');\nconst webpack = require('webpack');\n\nconst CreateWebpackAssetPlugin = require('./lib/create-webpack-asset-plugin');\nconst validateServiceWorkerRuntime = require('../../../../infra/testing/validator/service-worker-runtime');\nconst webpackBuildCheck = require('../../../../infra/testing/webpack-build-check');\nconst {\n  GenerateSW,\n} = require('../../../../packages/workbox-webpack-plugin/build/generate-sw');\n\ndescribe(`[workbox-webpack-plugin] GenerateSW with webpack v4`, function () {\n  const WEBPACK_ENTRY_FILENAME = 'webpackEntry.js';\n  const SRC_DIR = upath.join(\n    __dirname,\n    '..',\n    '..',\n    'static',\n    'example-project-1',\n  );\n\n  describe(`[workbox-webpack-plugin] Runtime errors`, function () {\n    it(`should lead to a webpack compilation error when passed invalid config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            invalid: 'invalid',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors).to.have.members([\n            `Please check your GenerateSW plugin configuration:\\n[WebpackGenerateSW] 'invalid' property is not expected to be here. Did you mean property 'include'?`,\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple chunks`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should work when called with importScriptsViaChunks`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        devtool: 'source-map',\n        entry: {\n          main: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          imported: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            importScriptsViaChunks: ['imported', 'INVALID_CHUNK_NAME'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(webpackError).not.to.exist;\n          expect(statsJson.errors, JSON.stringify(statsJson.errors)).to.be\n            .empty;\n          // There should be a warning logged, due to INVALID_CHUNK_NAME.\n          expect(statsJson.warnings).to.have.length(1);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(8);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              // imported-[chunkhash].js.map should *not* be included.\n              importScripts: [\n                [/^\\.\\/workbox-[0-9a-f]{8}$/],\n                [/^imported-[0-9a-f]{20}\\.js$/],\n              ],\n              // imported-[chunkhash].js should *not* be included.\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should work when called with additionalManifestEntries`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            additionalManifestEntries: [\n              {url: 'one', revision: null},\n              {url: 'two', revision: null},\n              {url: 'three', revision: '333'},\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          const statsJson = stats.toJson();\n          expect(webpackError).not.to.exist;\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.length(0);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: 'one',\n                    },\n                    {\n                      revision: '333',\n                      url: 'three',\n                    },\n                    {\n                      revision: null,\n                      url: 'two',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'entry2'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config, including children created via SplitChunksPlugin`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          main: upath.join(SRC_DIR, 'splitChunksEntry.js'),\n        },\n        output: {\n          chunkFilename: '[name].js',\n          filename: 'main.js',\n          path: outputDir,\n        },\n        optimization: {\n          splitChunks: {\n            chunks: 'all',\n          },\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['main'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'main.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'vendors~main.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'excludeChunks' denylist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            excludeChunks: ['entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor setting both the 'chunks' and 'excludeChunks', with the denylist taking precedence`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'entry2'],\n            excludeChunks: ['entry2', 'entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] html-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new HtmlWebpackPlugin(), new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] copy-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW(),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/example-jpeg.jpg',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filtering via include/exclude`, function () {\n    it(`should exclude .map and manifest.js files by default`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new CreateWebpackAssetPlugin('manifest.js'),\n          new CreateWebpackAssetPlugin('manifest.json'),\n          new CreateWebpackAssetPlugin('not-ignored.js'),\n          new GenerateSW(),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(9);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest.json',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'not-ignored.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to override the default exclude filter`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new GenerateSW({\n            exclude: [],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(6);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js.map',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to allowlist via include`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            include: [/.html$/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to combine the include and exclude filters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            include: [/.html$/],\n            exclude: [/index/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] swDest variations`, function () {\n    it(`should work when swDest is an absolute path`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            // upath.resolve() will always return an absolute upath.\n            swDest: upath.resolve(upath.join(outputDir, 'service-worker.js')),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Reporting webpack warnings`, function () {\n    it(`should warn when when passed a non-existent chunk`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'doesNotExist'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.members([\n            `The chunk 'doesNotExist' was provided in your Workbox chunks config, but was not found in the compilation.`,\n          ]);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should add maximumFileSizeToCacheInBytes warnings to compilation.warnings`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            // Make this large enough to cache some, but not all, files.\n            maximumFileSizeToCacheInBytes: 14 * 1024,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        if (webpackError) {\n          return done(webpackError);\n        }\n\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(statsJson.warnings).to.have.members([\n            `images/example-jpeg.jpg is 15.3 kB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.`,\n          ]);\n\n          const swFile = upath.join(outputDir, 'service-worker.js');\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(12);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Customizing output paths and names`, function () {\n    it(`should honor publicPath`, function (done) {\n      const outputDir = tempy.directory();\n      const publicPath = '/testing/';\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          publicPath,\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^\\/testing\\/entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] WASM Code`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/1916\n    it(`should support projects that bundle WASM code`, function (done) {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'wasm-project',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'index.js'),\n        },\n        output: {\n          filename: '[name].js',\n          globalObject: 'self',\n          path: outputDir,\n        },\n        plugins: [new WorkerPlugin(), new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // Bundling WASM into a Worker seems to lead to different hashes in\n          // different environments. Instead of hardcoding hash checks, just\n          // confirm that we output the expected number of files, which will\n          // only be true if the build was successful.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(6);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filesystem options`, function () {\n    it(`should support using MemoryFS as the outputFileSystem`, function (done) {\n      const memoryFS = new MemoryFS();\n      const outputDir = '/output/dir';\n      memoryFS.mkdirpSync(outputDir);\n\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.outputFileSystem = memoryFS;\n\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = memoryFS.readdirSync(outputDir);\n          expect(files).to.have.length(3);\n\n          const swString = memoryFS.readFileSync(\n            `${outputDir}/service-worker.js`,\n            'utf-8',\n          );\n\n          await validateServiceWorkerRuntime({\n            swString,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple invocation scenarios`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2158\n    it(`should support multiple compilations using the same plugin instance`, async function () {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'example-project-1',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'webpackEntry.js'),\n        },\n        output: {\n          filename: '[name].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(3);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n\n    it(`should not list the swDest from one plugin in the other's manifest`, function (done) {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'example-project-1',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'webpackEntry.js'),\n        },\n        output: {\n          filename: '[name].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            swDest: 'sw1.js',\n          }),\n          new GenerateSW({\n            swDest: 'sw2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const sw1File = upath.join(outputDir, 'sw1.js');\n        const sw2File = upath.join(outputDir, 'sw2.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile: sw1File,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          await validateServiceWorkerRuntime({\n            swFile: sw2File,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Rollup plugin configuration options`, function () {\n    it(`should support inlining the Workbox runtime`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:6].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            inlineWorkboxRuntime: true,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // We can't really mock evaluation of the service worker script when\n          // the Workbox runtime is inlined, so just check to make sure the\n          // correct files are output.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support inlining the Workbox runtime and generating sourcemaps`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:6].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            inlineWorkboxRuntime: true,\n            sourcemap: true,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // We can't really mock evaluation of the service worker script when\n          // the Workbox runtime is inlined, so just check to make sure the\n          // correct files are output.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support using a swDest that includes a subdirectory`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            swDest: upath.join('sub', 'directory', 'service-worker.js'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // Make sure that the expected generated service worker files are\n          // output into the subdirectory.\n          const files = await globby('**/*', {\n            cwd: upath.join(outputDir, 'sub', 'directory'),\n          });\n          expect(files).to.have.length(2);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest transformations`, function () {\n    it(`should use dontCacheBustURLsMatching`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            dontCacheBustURLsMatching: /\\.[0-9a-f]{20}\\./,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                      revision: null,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use modifyURLPrefix`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            modifyURLPrefix: {\n              '/public/': 'https://example.org/',\n            },\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^https:\\/\\/example\\.org\\/main\\.[0-9a-f]{20}\\.js/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use manifestTransforms`, function (done) {\n      const outputDir = tempy.directory();\n      const warningMessage = 'test warning';\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            manifestTransforms: [\n              (manifest, compilation) => {\n                expect(manifest).to.have.lengthOf(1);\n                expect(manifest[0].size).to.eql(959);\n                expect(manifest[0].url.startsWith('main.')).to.be.true;\n                expect(manifest[0].revision).to.be.null;\n                expect(compilation).to.exist;\n\n                manifest = manifest.map((entry) => {\n                  entry.url += '-suffix';\n                  entry.revision = null;\n                  return entry;\n                });\n\n                return {\n                  manifest,\n                  warnings: [warningMessage],\n                };\n              },\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors, JSON.stringify(statsJson.errors)).to.be\n            .empty;\n          expect(statsJson.warnings).to.have.members([warningMessage]);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js-suffix$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v4/inject-manifest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// workbox-webpack-plugin needs to do require('webpack'), and in order to test\n// against multiple webpack versions, we need that to resolve to whatever the\n// correct webpack is for this test.\n// See https://jeffy.info/2020/10/01/testing-multiple-webpack-versions.html\ntry {\n  delete require.cache[require.resolve('html-webpack-plugin')];\n  delete require.cache[require.resolve('webpack')];\n} catch (error) {\n  // Ignore if require.resolve() fails.\n}\nconst upath = require('upath');\nconst moduleAlias = require('module-alias');\nmoduleAlias.addAlias(\n  'html-webpack-plugin',\n  upath.resolve('node_modules', 'html-webpack-plugin-v4'),\n);\nmoduleAlias.addAlias('webpack', upath.resolve('node_modules', 'webpack-v4'));\n\nconst chai = require('chai');\nconst chaiMatchPattern = require('chai-match-pattern');\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst fse = require('fs-extra');\nconst globby = require('globby');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst tempy = require('tempy');\nconst webpack = require('webpack');\nconst WorkerPlugin = require('worker-plugin');\n\nconst CreateWebpackAssetPlugin = require('./lib/create-webpack-asset-plugin');\nconst validateServiceWorkerRuntime = require('../../../../infra/testing/validator/service-worker-runtime');\nconst webpackBuildCheck = require('../../../../infra/testing/webpack-build-check');\nconst {\n  InjectManifest,\n} = require('../../../../packages/workbox-webpack-plugin/build/inject-manifest');\n\nchai.use(chaiMatchPattern);\nconst {expect} = chai;\n\ndescribe(`[workbox-webpack-plugin] InjectManifest with webpack v4`, function () {\n  const WEBPACK_ENTRY_FILENAME = 'webpackEntry.js';\n  const SRC_DIR = upath.join(\n    __dirname,\n    '..',\n    '..',\n    'static',\n    'example-project-1',\n  );\n  const SW_SRC = upath.join(__dirname, '..', '..', 'static', 'sw-src.js');\n\n  describe(`[workbox-webpack-plugin] Runtime errors`, function () {\n    it(`should lead to a webpack compilation error when passed invalid config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            invalid: 'invalid',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors).to.have.members([\n            `Please check your InjectManifest plugin configuration:\\n[WebpackInjectManifest] 'invalid' property is not expected to be here. Did you mean property 'include'?`,\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should lead to a webpack compilation error when the swSrc contains multiple injection points`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'bad-multiple-injection.js',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors).to.have.members([\n            `Multiple instances of self.__WB_MANIFEST were found in your SW source. Include it only once. For more info, see https://github.com/GoogleChrome/workbox/issues/2681`,\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple chunks`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'entry2'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config, including children created via SplitChunksPlugin`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          main: upath.join(SRC_DIR, 'splitChunksEntry.js'),\n        },\n        output: {\n          chunkFilename: '[name].js',\n          filename: 'main.js',\n          path: outputDir,\n        },\n        optimization: {\n          splitChunks: {\n            chunks: 'all',\n          },\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['main'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'main.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'vendors~main.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'excludeChunks' denylist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            excludeChunks: ['entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor setting both the 'chunks' and 'excludeChunks', with the denylist taking precedence`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'entry2'],\n            excludeChunks: ['entry2', 'entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] html-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new HtmlWebpackPlugin(),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] copy-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/example-jpeg.jpg',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Sourcemap manipulation`, function () {\n    it(`should update the sourcemap to account for manifest injection`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          const expectedSourcemap = await fse.readJSON(\n            upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'expected-service-worker.js.map',\n            ),\n          );\n          const actualSourcemap = await fse.readJSON(\n            upath.join(outputDir, 'service-worker.js.map'),\n          );\n\n          // The mappings will vary depending on the webpack version.\n          delete expectedSourcemap.mappings;\n          delete actualSourcemap.mappings;\n\n          expect(actualSourcemap).to.eql(expectedSourcemap);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should handle a custom output.sourceMapFilename`, function (done) {\n      const outputDir = tempy.directory();\n\n      const sourceMapFilename = upath.join('subdir', '[file].map');\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          sourceMapFilename,\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          const expectedSourcemap = await fse.readJSON(\n            upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'expected-service-worker.js.map',\n            ),\n          );\n          const actualSourcemap = await fse.readJSON(\n            upath.join(outputDir, 'subdir', 'service-worker.js.map'),\n          );\n\n          // The mappings will vary depending on the webpack version.\n          delete expectedSourcemap.mappings;\n          delete actualSourcemap.mappings;\n\n          expect(actualSourcemap).to.eql(expectedSourcemap);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should not fail if the sourcemap is missing from the assets`, function (done) {\n      const outputDir = tempy.directory();\n      const swSrc = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'sw-src-missing-sourcemap.js',\n      );\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: false,\n        plugins: [\n          new InjectManifest({\n            swSrc,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    // See https://github.com/GoogleChrome/workbox/issues/2729\n    it(`should produce valid JavaScript when eval-cheap-source-map and minimization are used`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'eval-cheap-source-map',\n        optimization: {\n          minimize: true,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'module-import-sw.js',\n            ),\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            // We can't verify expectedMethodCalls here, since we're using\n            // a compiled ES module import, not the workbox-sw interfaces.\n            // This test just confirms that the compilation produces valid JS.\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    // See https://github.com/GoogleChrome/workbox/issues/2729\n    it(`should produce valid JavaScript when eval-cheap-source-map is used without minimization`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'eval-cheap-source-map',\n        optimization: {\n          minimize: false,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'module-import-sw.js',\n            ),\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            // We can't verify expectedMethodCalls here, since we're using\n            // a compiled ES module import, not the workbox-sw interfaces.\n            // This test just confirms that the compilation produces valid JS.\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filtering via include/exclude`, function () {\n    it(`should exclude .map and manifest.js files by default`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new CreateWebpackAssetPlugin('manifest.js'),\n          new CreateWebpackAssetPlugin('manifest.json'),\n          new CreateWebpackAssetPlugin('not-ignored.js'),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(7);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest.json',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'not-ignored.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to override the default exclude filter`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            exclude: [],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js.map',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to allowlist via include`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            include: [/.html$/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to combine the include and exclude filters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            include: [/.html$/],\n            exclude: [/index/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] swDest variations`, function () {\n    it(`should work when swDest is an absolute path`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: upath.resolve(upath.join(outputDir, 'service-worker.js')),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Reporting webpack warnings`, function () {\n    it(`should warn when when passed a non-existent chunk`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'doesNotExist'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.members([\n            `The chunk 'doesNotExist' was provided in your Workbox chunks config, but was not found in the compilation.`,\n          ]);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should add maximumFileSizeToCacheInBytes warnings to compilation.warnings`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            // Make this large enough to cache some, but not all, files.\n            maximumFileSizeToCacheInBytes: 14 * 1024,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        if (webpackError) {\n          return done(webpackError);\n        }\n\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(statsJson.warnings).to.have.members([\n            `images/example-jpeg.jpg is 15.3 kB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.`,\n          ]);\n\n          const swFile = upath.join(outputDir, 'service-worker.js');\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Customizing output paths and names`, function () {\n    it(`should honor publicPath`, function (done) {\n      const outputDir = tempy.directory();\n      const publicPath = '/testing/';\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          publicPath,\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^\\/testing\\/entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] WASM Code`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/1916\n    it(`should support projects that bundle WASM code`, function (done) {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'wasm-project',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'index.js'),\n        },\n        output: {\n          filename: '[name].js',\n          globalObject: 'self',\n          path: outputDir,\n        },\n        plugins: [\n          new WorkerPlugin(),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // Bundling WASM into a Worker seems to lead to different hashes in\n          // different environments. Instead of hardcoding hash checks, just\n          // confirm that we output the expected number of files, which will\n          // only be true if the build was successful.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest transformations`, function () {\n    it(`should use dontCacheBustURLsMatching`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            dontCacheBustURLsMatching: /\\.[0-9a-f]{20}\\./,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                      revision: null,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use modifyURLPrefix`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            modifyURLPrefix: {\n              '/public/': 'https://example.org/',\n            },\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^https:\\/\\/example\\.org\\/main\\.[0-9a-f]{20}\\.js/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use webpackCompilationPlugins with DefinePlugin`, function (done) {\n      const prefix = 'replaced-by-define-plugin';\n      const swSrc = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'sw-src-define-plugin.js',\n      );\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc,\n            swDest: 'service-worker.js',\n            webpackCompilationPlugins: [\n              new webpack.DefinePlugin({\n                __PREFIX__: JSON.stringify(prefix),\n              }),\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              setCacheNameDetails: [[{prefix}]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use manifestTransforms`, function (done) {\n      const outputDir = tempy.directory();\n      const warningMessage = 'test warning';\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            manifestTransforms: [\n              (manifest, compilation) => {\n                expect(manifest).to.have.lengthOf(1);\n                expect(manifest[0].size).to.eql(959);\n                expect(manifest[0].url.startsWith('main.')).to.be.true;\n                expect(manifest[0].revision).to.be.null;\n                expect(compilation).to.exist;\n\n                manifest = manifest.map((entry) => {\n                  entry.url += '-suffix';\n                  entry.revision = null;\n                  return entry;\n                });\n\n                return {\n                  manifest,\n                  warnings: [warningMessage],\n                };\n              },\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.members([warningMessage]);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main.[0-9a-f]{20}\\.js-suffix$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] TypeScript compilation`, function () {\n    it(`should rename a swSrc with a .ts extension to .js`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('*', {cwd: outputDir});\n          expect(files).to.contain('sw.js');\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple invocation scenarios`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2158\n    it(`should support multiple compilations using the same plugin instance`, async function () {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(2);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n\n    it(`should only log once per invocation when using multiple plugin instances`, async function () {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker1.js',\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a single warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790#issuecomment-640132556\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(3);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple plugin instances`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2181\n    it(`should not list the swDest from one plugin in the other's manifest`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n            swDest: 'sw1.js',\n          }),\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n            swDest: 'sw2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const sw1File = upath.join(outputDir, 'sw1.js');\n        const sw2File = upath.join(outputDir, 'sw2.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile: sw1File,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          await validateServiceWorkerRuntime({\n            swFile: sw2File,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest injection in development mode`, function () {\n    it(`should produce valid, parsable JavaScript`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swDest: 'sw.js',\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw-src.js'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'sw.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile: swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Non-compilation scenarios`, function () {\n    it(`should warn when compileSrc is false and webpackCompilationPlugins is used`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.json',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.json',\n            ),\n            webpackCompilationPlugins: [{}],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.members([\n            'compileSrc is false, so the webpackCompilationPlugins option will be ignored.',\n          ]);\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support injecting a manifest into a JSON file`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.json',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.json',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          const manifest = await fse.readJSON(\n            upath.join(outputDir, 'injected-manifest.json'),\n          );\n          expect(manifest).to.matchPattern([\n            {\n              revision: null,\n              url: /^main\\.[0-9a-f]{20}\\.js$/,\n            },\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support injecting a manifest into a CJS module`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[hash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.js',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.js',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          const manifest = require(upath.join(\n            outputDir,\n            'injected-manifest.js',\n          ));\n          expect(manifest).to.matchPattern([\n            {\n              revision: null,\n              url: /^main\\.[0-9a-f]{20}\\.js$/,\n            },\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v4/lib/create-webpack-asset-plugin.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// A small helper class to generate a \"fake\" webpack asset for testing purposes.\n// Roughly equivalent to https://www.npmjs.com/package/generate-asset-webpack-plugin,\n// but this uses the webpack v4 syntax.\n\nclass CreateWebpackAssetPlugin {\n  constructor(name) {\n    if (typeof name !== 'string') {\n      throw new Error('Please pass in a string.');\n    }\n    this.name = name;\n  }\n\n  apply(compiler) {\n    compiler.hooks.emit.tap(\n      this.constructor.name,\n      (compilation) =>\n        (compilation.assets[this.name] = {\n          source: () => this.name,\n          size: () => this.name.length,\n        }),\n    );\n  }\n}\n\nmodule.exports = CreateWebpackAssetPlugin;\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v5/generate-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// workbox-webpack-plugin needs to do require('webpack'), and in order to test\n// against multiple webpack versions, we need that to resolve to whatever the\n// correct webpack is for this test.\n// See https://jeffy.info/2020/10/01/testing-multiple-webpack-versions.html\ntry {\n  delete require.cache[require.resolve('html-webpack-plugin')];\n  delete require.cache[require.resolve('webpack')];\n} catch (error) {\n  // Ignore if require.resolve() fails.\n}\nconst upath = require('upath');\nconst moduleAlias = require('module-alias');\nmoduleAlias.addAlias(\n  'html-webpack-plugin',\n  upath.resolve('node_modules', 'html-webpack-plugin-v5'),\n);\nmoduleAlias.addAlias('webpack', upath.resolve('node_modules', 'webpack-v5'));\n\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst MemoryFS = require('memory-fs');\nconst expect = require('chai').expect;\nconst globby = require('globby');\nconst tempy = require('tempy');\nconst webpack = require('webpack');\n\nconst CreateWebpackAssetPlugin = require('./lib/create-webpack-asset-plugin');\nconst validateServiceWorkerRuntime = require('../../../../infra/testing/validator/service-worker-runtime');\nconst webpackBuildCheck = require('../../../../infra/testing/webpack-build-check');\nconst {\n  GenerateSW,\n} = require('../../../../packages/workbox-webpack-plugin/build/generate-sw');\n\ndescribe(`[workbox-webpack-plugin] GenerateSW with webpack v5`, function () {\n  const WEBPACK_ENTRY_FILENAME = 'webpackEntry.js';\n  const SRC_DIR = upath.join(\n    __dirname,\n    '..',\n    '..',\n    'static',\n    'example-project-1',\n  );\n\n  describe(`[workbox-webpack-plugin] Runtime errors`, function () {\n    it(`should lead to a webpack compilation error when passed invalid config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            invalid: 'invalid',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors).to.have.length(1);\n          expect(statsJson.errors[0].message).to.eql(\n            `Please check your GenerateSW plugin configuration:\\n[WebpackGenerateSW] 'invalid' property is not expected to be here. Did you mean property 'include'?`,\n          );\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple chunks`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should work when called with importScriptsViaChunks`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        devtool: 'source-map',\n        entry: {\n          main: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          imported: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            importScriptsViaChunks: ['imported', 'INVALID_CHUNK_NAME'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(webpackError).not.to.exist;\n          expect(statsJson.errors).to.be.empty;\n          // There should be a warning logged, due to INVALID_CHUNK_NAME.\n          expect(statsJson.warnings).to.have.length(1);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(8);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              // imported-[chunkhash].js.map should *not* be included.\n              importScripts: [\n                [/^\\.\\/workbox-[0-9a-f]{8}$/],\n                [/^imported-[0-9a-f]{20}\\.js$/],\n              ],\n              // imported-[chunkhash].js should *not* be included.\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should work when called with additionalManifestEntries`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            additionalManifestEntries: [\n              {url: 'one', revision: null},\n              {url: 'two', revision: null},\n              {url: 'three', revision: '333'},\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          const statsJson = stats.toJson();\n          expect(webpackError).not.to.exist;\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings).to.have.length(0);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: 'one',\n                    },\n                    {\n                      revision: '333',\n                      url: 'three',\n                    },\n                    {\n                      revision: null,\n                      url: 'two',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'entry2'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config, including children created via SplitChunksPlugin`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          main: upath.join(SRC_DIR, 'splitChunksEntry.js'),\n        },\n        output: {\n          filename: '[chunkhash].js',\n          path: outputDir,\n        },\n        optimization: {\n          minimize: false,\n          splitChunks: {\n            chunks: 'all',\n          },\n        },\n        performance: {\n          hints: false,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['main'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'excludeChunks' denylist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            excludeChunks: ['entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor setting both the 'chunks' and 'excludeChunks', with the denylist taking precedence`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'entry2'],\n            excludeChunks: ['entry2', 'entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] html-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new HtmlWebpackPlugin(), new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(5);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] copy-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW(),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/example-jpeg.jpg',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filtering via include/exclude`, function () {\n    it(`should exclude .map and manifest.js files by default`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new CreateWebpackAssetPlugin('manifest.js'),\n          new CreateWebpackAssetPlugin('manifest.json'),\n          new CreateWebpackAssetPlugin('not-ignored.js'),\n          new GenerateSW(),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(9);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest.json',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'not-ignored.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to override the default exclude filter`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: 'manifest-normally-ignored.js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            exclude: [],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest-normally-ignored.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to allowlist via include`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            include: [/.html$/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to combine the include and exclude filters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            include: [/.html$/],\n            exclude: [/index/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] swDest variations`, function () {\n    it(`should work when swDest is an absolute path`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            // upath.resolve() will always return an absolute upath.\n            swDest: upath.resolve(upath.join(outputDir, 'service-worker.js')),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Reporting webpack warnings`, function () {\n    it(`should warn when when passed a non-existent chunk`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            chunks: ['entry1', 'doesNotExist'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings[0].message).to.eql(\n            `The chunk 'doesNotExist' was provided in your Workbox chunks config, but was not found in the compilation.`,\n          );\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should add maximumFileSizeToCacheInBytes warnings to compilation.warnings`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new GenerateSW({\n            // Make this large enough to cache some, but not all, files.\n            maximumFileSizeToCacheInBytes: 14 * 1024,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        if (webpackError) {\n          return done(webpackError);\n        }\n\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(statsJson.warnings[0].message).to.eql(\n            `images/example-jpeg.jpg is 15.3 kB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.`,\n          );\n\n          const swFile = upath.join(outputDir, 'service-worker.js');\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(12);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Customizing output paths and names`, function () {\n    it(`should honor publicPath`, function (done) {\n      const outputDir = tempy.directory();\n      const publicPath = '/testing/';\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          publicPath,\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^\\/testing\\/entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filesystem options`, function () {\n    it(`should support using MemoryFS as the outputFileSystem`, function (done) {\n      const memoryFS = new MemoryFS();\n      const outputDir = '/output/dir';\n      memoryFS.mkdirpSync(outputDir);\n\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      compiler.outputFileSystem = memoryFS;\n\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = memoryFS.readdirSync(outputDir);\n          expect(files).to.have.length(3);\n\n          const swString = memoryFS.readFileSync(\n            `${outputDir}/service-worker.js`,\n            'utf-8',\n          );\n\n          await validateServiceWorkerRuntime({\n            swString,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple invocation scenarios`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2158\n    it(`should support multiple compilations using the same plugin instance`, async function () {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'example-project-1',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'webpackEntry.js'),\n        },\n        output: {\n          filename: '[name].js',\n          path: outputDir,\n        },\n        plugins: [new GenerateSW()],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(3);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n\n    it(`should not list the swDest from one plugin in the other's manifest`, function (done) {\n      const outputDir = tempy.directory();\n      const srcDir = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'example-project-1',\n      );\n      const config = {\n        mode: 'production',\n        entry: {\n          index: upath.join(srcDir, 'webpackEntry.js'),\n        },\n        output: {\n          filename: '[name].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            swDest: 'sw1.js',\n          }),\n          new GenerateSW({\n            swDest: 'sw2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const sw1File = upath.join(outputDir, 'sw1.js');\n        const sw2File = upath.join(outputDir, 'sw2.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile: sw1File,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          await validateServiceWorkerRuntime({\n            swFile: sw2File,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Rollup plugin configuration options`, function () {\n    it(`should support inlining the Workbox runtime`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:6].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            inlineWorkboxRuntime: true,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // We can't really mock evaluation of the service worker script when\n          // the Workbox runtime is inlined, so just check to make sure the\n          // correct files are output.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support inlining the Workbox runtime and generating sourcemaps`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:6].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            inlineWorkboxRuntime: true,\n            sourcemap: true,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // We can't really mock evaluation of the service worker script when\n          // the Workbox runtime is inlined, so just check to make sure the\n          // correct files are output.\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support using a swDest that includes a subdirectory`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            swDest: upath.join('sub', 'directory', 'service-worker.js'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          // Make sure that the expected generated service worker files are\n          // output into the subdirectory.\n          const files = await globby('**/*', {\n            cwd: upath.join(outputDir, 'sub', 'directory'),\n          });\n          expect(files).to.have.length(2);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest transformations`, function () {\n    it(`should use dontCacheBustURLsMatching`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            dontCacheBustURLsMatching: /\\.[0-9a-f]{20}\\./,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                      revision: null,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use modifyURLPrefix`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new GenerateSW({\n            modifyURLPrefix: {\n              '/public/': 'https://example.org/',\n            },\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^https:\\/\\/example\\.org\\/main\\.[0-9a-f]{20}\\.js/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use manifestTransforms`, function (done) {\n      const outputDir = tempy.directory();\n      const warningMessage = 'test warning';\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new GenerateSW({\n            manifestTransforms: [\n              (manifest, compilation) => {\n                expect(manifest).to.have.lengthOf(1);\n                expect(manifest[0].size).to.eql(30);\n                expect(manifest[0].url.startsWith('main.')).to.be.true;\n                expect(manifest[0].revision).to.be.null;\n                expect(compilation).to.exist;\n\n                manifest = manifest.map((entry) => {\n                  entry.url += '-suffix';\n                  entry.revision = null;\n                  return entry;\n                });\n\n                return {\n                  manifest,\n                  warnings: [warningMessage],\n                };\n              },\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors, JSON.stringify(statsJson.errors)).to.be\n            .empty;\n          expect(statsJson.warnings[0].message).to.eql(warningMessage);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            expectedMethodCalls: {\n              importScripts: [[/^\\.\\/workbox-[0-9a-f]{8}$/]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js-suffix$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v5/inject-manifest.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// workbox-webpack-plugin needs to do require('webpack'), and in order to test\n// against multiple webpack versions, we need that to resolve to whatever the\n// correct webpack is for this test.\n// See https://jeffy.info/2020/10/01/testing-multiple-webpack-versions.html\ntry {\n  delete require.cache[require.resolve('html-webpack-plugin')];\n  delete require.cache[require.resolve('webpack')];\n} catch (error) {\n  // Ignore if require.resolve() fails.\n}\nconst upath = require('upath');\nconst moduleAlias = require('module-alias');\nmoduleAlias.addAlias(\n  'html-webpack-plugin',\n  upath.resolve('node_modules', 'html-webpack-plugin-v5'),\n);\nmoduleAlias.addAlias('webpack', upath.resolve('node_modules', 'webpack-v5'));\n\nconst chai = require('chai');\nconst chaiMatchPattern = require('chai-match-pattern');\nconst CopyWebpackPlugin = require('copy-webpack-plugin');\nconst fse = require('fs-extra');\nconst globby = require('globby');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst tempy = require('tempy');\nconst webpack = require('webpack');\n\nconst CreateWebpackAssetPlugin = require('./lib/create-webpack-asset-plugin');\nconst validateServiceWorkerRuntime = require('../../../../infra/testing/validator/service-worker-runtime');\nconst webpackBuildCheck = require('../../../../infra/testing/webpack-build-check');\nconst {\n  InjectManifest,\n} = require('../../../../packages/workbox-webpack-plugin/build/inject-manifest');\n\nchai.use(chaiMatchPattern);\nconst {expect} = chai;\n\ndescribe(`[workbox-webpack-plugin] InjectManifest with webpack v5`, function () {\n  const WEBPACK_ENTRY_FILENAME = 'webpackEntry.js';\n  const SRC_DIR = upath.join(\n    __dirname,\n    '..',\n    '..',\n    'static',\n    'example-project-1',\n  );\n  const SW_SRC = upath.join(__dirname, '..', '..', 'static', 'sw-src.js');\n\n  describe(`[workbox-webpack-plugin] Runtime errors`, function () {\n    it(`should lead to a webpack compilation error when passed invalid config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            invalid: 'invalid',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors[0].message).to.eql(\n            `Please check your InjectManifest plugin configuration:\\n[WebpackInjectManifest] 'invalid' property is not expected to be here. Did you mean property 'include'?`,\n          );\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should lead to a webpack compilation error when the swSrc contains multiple injection points`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'bad-multiple-injection.js',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.warnings).to.be.empty;\n          expect(statsJson.errors[0].message).to.eql(\n            `Multiple instances of self.__WB_MANIFEST were found in your SW source. Include it only once. For more info, see https://github.com/GoogleChrome/workbox/issues/2681`,\n          );\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple chunks`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'entry2'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'chunks' allowlist config, including children created via SplitChunksPlugin`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          main: upath.join(SRC_DIR, 'splitChunksEntry.js'),\n        },\n        output: {\n          filename: '[chunkhash].js',\n          path: outputDir,\n        },\n        optimization: {\n          minimize: false,\n          splitChunks: {\n            chunks: 'all',\n          },\n        },\n        performance: {\n          hints: false,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['main'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor the 'excludeChunks' denylist config`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            excludeChunks: ['entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should honor setting both the 'chunks' and 'excludeChunks', with the denylist taking precedence`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry3: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'entry2'],\n            excludeChunks: ['entry2', 'entry3'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] html-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n          entry2: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new HtmlWebpackPlugin(),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: null,\n                      url: /^entry2-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] copy-webpack-plugin and a single chunk`, function () {\n    it(`should work when called without any parameters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/example-jpeg.jpg',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Sourcemap manipulation`, function () {\n    it(`should update the sourcemap to account for manifest injection`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          const expectedSourcemap = await fse.readJSON(\n            upath.join(__dirname, 'static', 'expected-service-worker.js.map'),\n          );\n          const actualSourcemap = await fse.readJSON(\n            upath.join(outputDir, 'service-worker.js.map'),\n          );\n\n          // The mappings will vary depending on the webpack version.\n          delete expectedSourcemap.mappings;\n          delete actualSourcemap.mappings;\n\n          expect(actualSourcemap).to.eql(expectedSourcemap);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should handle a custom output.sourceMapFilename`, function (done) {\n      const outputDir = tempy.directory();\n\n      const sourceMapFilename = upath.join('subdir', '[file].map');\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          sourceMapFilename,\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          const expectedSourcemap = await fse.readJSON(\n            upath.join(__dirname, 'static', 'expected-service-worker.js.map'),\n          );\n          const actualSourcemap = await fse.readJSON(\n            upath.join(outputDir, 'subdir', 'service-worker.js.map'),\n          );\n\n          // The mappings will vary depending on the webpack version.\n          delete expectedSourcemap.mappings;\n          delete actualSourcemap.mappings;\n\n          expect(actualSourcemap).to.eql(expectedSourcemap);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should not fail if the sourcemap is missing from the assets`, function (done) {\n      const outputDir = tempy.directory();\n      const swSrc = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'sw-src-missing-sourcemap.js',\n      );\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: false,\n        plugins: [\n          new InjectManifest({\n            swSrc,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    // See https://github.com/GoogleChrome/workbox/issues/2729\n    it(`should produce valid JavaScript when eval-cheap-source-map and minimization are used`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'eval-cheap-source-map',\n        optimization: {\n          minimize: true,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'module-import-sw.js',\n            ),\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(4);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            // We can't verify expectedMethodCalls here, since we're using\n            // a compiled ES module import, not the workbox-sw interfaces.\n            // This test just confirms that the compilation produces valid JS.\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    // See https://github.com/GoogleChrome/workbox/issues/2729\n    it(`should produce valid JavaScript when eval-cheap-source-map is used without minimization`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'eval-cheap-source-map',\n        optimization: {\n          minimize: false,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'module-import-sw.js',\n            ),\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            // We can't verify expectedMethodCalls here, since we're using\n            // a compiled ES module import, not the workbox-sw interfaces.\n            // This test just confirms that the compilation produces valid JS.\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Filtering via include/exclude`, function () {\n    it(`should exclude .map and manifest.js files by default`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        devtool: 'source-map',\n        plugins: [\n          new CreateWebpackAssetPlugin('manifest.js'),\n          new CreateWebpackAssetPlugin('manifest.json'),\n          new CreateWebpackAssetPlugin('not-ignored.js'),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(7);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest.json',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'not-ignored.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to override the default exclude filter`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: 'manifest-normally-ignored.js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            exclude: [],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'manifest-normally-ignored.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to allowlist via include`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            include: [/.html$/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should allow developers to combine the include and exclude filters`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            include: [/.html$/],\n            exclude: [/index/],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(10);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] swDest variations`, function () {\n    it(`should work when swDest is an absolute path`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: WEBPACK_ENTRY_FILENAME,\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: upath.resolve(upath.join(outputDir, 'service-worker.js')),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Reporting webpack warnings`, function () {\n    it(`should warn when when passed a non-existent chunk`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            chunks: ['entry1', 'doesNotExist'],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings[0].message).to.eql(\n            `The chunk 'doesNotExist' was provided in your Workbox chunks config, but was not found in the compilation.`,\n          );\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should add maximumFileSizeToCacheInBytes warnings to compilation.warnings`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new CopyWebpackPlugin({\n            patterns: [\n              {\n                from: SRC_DIR,\n                to: outputDir,\n              },\n            ],\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            // Make this large enough to cache some, but not all, files.\n            maximumFileSizeToCacheInBytes: 14 * 1024,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        if (webpackError) {\n          return done(webpackError);\n        }\n\n        try {\n          const statsJson = stats.toJson('verbose');\n          expect(statsJson.warnings[0].message).to.eql(\n            `images/example-jpeg.jpg is 15.3 kB, and won't be precached. Configure maximumFileSizeToCacheInBytes to change this limit.`,\n          );\n\n          const swFile = upath.join(outputDir, 'service-worker.js');\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(11);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'images/web-fundamentals-icon192x192.png',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'index.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-1.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'page-2.html',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'splitChunksEntry.js',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-1.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'styles/stylesheet-2.css',\n                    },\n                    {\n                      revision: /^[0-9a-f]{32}$/,\n                      url: 'webpackEntry.js',\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Customizing output paths and names`, function () {\n    it(`should honor publicPath`, function (done) {\n      const outputDir = tempy.directory();\n      const publicPath = '/testing/';\n      const config = {\n        mode: 'production',\n        entry: {\n          entry1: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        },\n        output: {\n          publicPath,\n          filename: '[name]-[chunkhash].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^\\/testing\\/entry1-[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest transformations`, function () {\n    it(`should use dontCacheBustURLsMatching`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            dontCacheBustURLsMatching: /\\.[0-9a-f]{20}\\./,\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                      revision: null,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use modifyURLPrefix`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n          publicPath: '/public/',\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            modifyURLPrefix: {\n              '/public/': 'https://example.org/',\n            },\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^https:\\/\\/example\\.org\\/main\\.[0-9a-f]{20}\\.js/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use webpackCompilationPlugins with DefinePlugin`, function (done) {\n      const prefix = 'replaced-by-define-plugin';\n      const swSrc = upath.join(\n        __dirname,\n        '..',\n        '..',\n        'static',\n        'sw-src-define-plugin.js',\n      );\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc,\n            swDest: 'service-worker.js',\n            webpackCompilationPlugins: [\n              new webpack.DefinePlugin({\n                __PREFIX__: JSON.stringify(prefix),\n              }),\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              setCacheNameDetails: [[{prefix}]],\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should use manifestTransforms`, function (done) {\n      const outputDir = tempy.directory();\n      const warningMessage = 'test warning';\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n            manifestTransforms: [\n              (manifest, compilation) => {\n                expect(manifest).to.have.lengthOf(1);\n                expect(manifest[0].size).to.eql(30);\n                expect(manifest[0].url.startsWith('main.')).to.be.true;\n                expect(manifest[0].revision).to.be.null;\n                expect(compilation).to.exist;\n\n                manifest = manifest.map((entry) => {\n                  entry.url += '-suffix';\n                  entry.revision = null;\n                  return entry;\n                });\n\n                return {\n                  manifest,\n                  warnings: [warningMessage],\n                };\n              },\n            ],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'service-worker.js');\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings[0].message).to.eql(warningMessage);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main.[0-9a-f]{20}\\.js-suffix$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] TypeScript compilation`, function () {\n    it(`should rename a swSrc with a .ts extension to .js`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('*', {cwd: outputDir});\n          expect(files).to.contain('sw.js');\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple invocation scenarios`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2158\n    it(`should support multiple compilations using the same plugin instance`, async function () {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(2);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n\n    it(`should only log once per invocation when using multiple plugin instances`, async function () {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:6].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker1.js',\n          }),\n          new InjectManifest({\n            swSrc: SW_SRC,\n            swDest: 'service-worker2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      for (const i of [1, 2, 3]) {\n        await new Promise((resolve, reject) => {\n          compiler.run(async (webpackError, stats) => {\n            try {\n              if (webpackError) {\n                throw new Error(webpackError.message);\n              }\n\n              const statsJson = stats.toJson('verbose');\n              expect(statsJson.errors).to.have.length(0);\n\n              // There should be a single warning logged after the first compilation.\n              // See https://github.com/GoogleChrome/workbox/issues/1790#issuecomment-640132556\n              if (i > 1) {\n                expect(statsJson.warnings).to.have.length(1);\n              } else {\n                expect(statsJson.warnings).to.have.length(0);\n              }\n\n              const files = await globby('**', {cwd: outputDir});\n              expect(files).to.have.length(3);\n\n              resolve();\n            } catch (error) {\n              reject(new Error(`Failure during compilation ${i}: ${error}`));\n            }\n          });\n        });\n      }\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Multiple plugin instances`, function () {\n    // See https://github.com/GoogleChrome/workbox/issues/2181\n    it(`should not list the swDest from one plugin in the other's manifest`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n            swDest: 'sw1.js',\n          }),\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw.ts'),\n            swDest: 'sw2.js',\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const sw1File = upath.join(outputDir, 'sw1.js');\n        const sw2File = upath.join(outputDir, 'sw2.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(3);\n\n          await validateServiceWorkerRuntime({\n            swFile: sw1File,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          await validateServiceWorkerRuntime({\n            swFile: sw2File,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Manifest injection in development mode`, function () {\n    it(`should produce valid, parsable JavaScript`, function (done) {\n      const outputDir = tempy.directory();\n      const config = {\n        mode: 'development',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            exclude: [/sw\\d.js/],\n            swDest: 'sw.js',\n            swSrc: upath.join(__dirname, '..', '..', 'static', 'sw-src.js'),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        const swFile = upath.join(outputDir, 'sw.js');\n\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          await validateServiceWorkerRuntime({\n            swFile: swFile,\n            entryPoint: 'injectManifest',\n            expectedMethodCalls: {\n              precacheAndRoute: [\n                [\n                  [\n                    {\n                      revision: null,\n                      url: /^main\\.[0-9a-f]{20}\\.js$/,\n                    },\n                  ],\n                  {},\n                ],\n              ],\n            },\n          });\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n\n  describe(`[workbox-webpack-plugin] Non-compilation scenarios`, function () {\n    it(`should error when compileSrc is false and webpackCompilationPlugins is used`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.json',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.json',\n            ),\n            webpackCompilationPlugins: [{}],\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run((webpackError, stats) => {\n        try {\n          expect(webpackError).not.to.exist;\n          const statsJson = stats.toJson();\n          expect(statsJson.errors).to.be.empty;\n          expect(statsJson.warnings[0].message).to.eql(\n            'compileSrc is false, so the webpackCompilationPlugins option will be ignored.',\n          );\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support injecting a manifest into a JSON file`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.json',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.json',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          const manifest = await fse.readJSON(\n            upath.join(outputDir, 'injected-manifest.json'),\n          );\n          expect(manifest).to.matchPattern([\n            {\n              revision: null,\n              url: /^main\\.[0-9a-f]{20}\\.js$/,\n            },\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n\n    it(`should support injecting a manifest into a CJS module`, function (done) {\n      const outputDir = tempy.directory();\n\n      const config = {\n        mode: 'production',\n        entry: upath.join(SRC_DIR, WEBPACK_ENTRY_FILENAME),\n        output: {\n          filename: '[name].[contenthash:20].js',\n          path: outputDir,\n        },\n        plugins: [\n          new InjectManifest({\n            compileSrc: false,\n            swDest: 'injected-manifest.js',\n            swSrc: upath.join(\n              __dirname,\n              '..',\n              '..',\n              'static',\n              'injected-manifest.js',\n            ),\n          }),\n        ],\n      };\n\n      const compiler = webpack(config);\n      compiler.run(async (webpackError, stats) => {\n        try {\n          webpackBuildCheck(webpackError, stats);\n\n          const files = await globby('**', {cwd: outputDir});\n          expect(files).to.have.length(2);\n\n          const manifest = require(upath.join(\n            outputDir,\n            'injected-manifest.js',\n          ));\n          expect(manifest).to.matchPattern([\n            {\n              revision: null,\n              url: /^main\\.[0-9a-f]{20}\\.js$/,\n            },\n          ]);\n\n          done();\n        } catch (error) {\n          done(error);\n        }\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/node/v5/lib/create-webpack-asset-plugin.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {sources} = require('webpack');\n\nclass CreateWebpackAssetPlugin {\n  constructor(name) {\n    if (typeof name !== 'string') {\n      throw new Error('Please pass in a string.');\n    }\n    this.name = name;\n  }\n\n  apply(compiler) {\n    compiler.hooks.thisCompilation.tap(this.constructor.name, (compilation) =>\n      compilation.emitAsset(this.name, new sources.RawSource(this.name)),\n    );\n  }\n}\n\nmodule.exports = CreateWebpackAssetPlugin;\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/bad-multiple-injection.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// Multiple entries.\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST);\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/index.html",
    "content": "<h1>Index</h1>\n\n<script>\n  // Helper function which returns a promise which resolves once the service worker registration\n  // is past the \"installing\" state.\n  function waitUntilInstalled(registration) {\n    return new Promise(function (resolve, reject) {\n      if (registration.installing) {\n        // If the current registration represents the \"installing\" service worker, then wait\n        // until the installation step (during which the resources are pre-fetched) completes\n        // to display the file list.\n        registration.installing.addEventListener('statechange', function (e) {\n          if (e.target.state === 'installed') {\n            resolve();\n          } else if (e.target.state === 'redundant') {\n            reject();\n          }\n        });\n      } else {\n        // Otherwise, if this isn't the \"installing\" service worker, then installation must have been\n        // completed during a previous visit to this page, and the resources are already pre-fetched.\n        // So we can show the list of files right away.\n        resolve();\n      }\n    });\n  }\n\n  const queryString = location.search.substring(1);\n  const keyValues = queryString.split('&');\n  const searchParams = {};\n  keyValues.forEach((keyValue) => {\n    const split = keyValue.split('=');\n    if (split[1].indexOf('/') === split[1].length - 1) {\n      split[1] = split[1].substring(0, split[1].length - 1);\n    }\n    searchParams[split[0]] = decodeURIComponent(split[1]);\n  });\n  navigator.serviceWorker\n    .register(searchParams.sw)\n    .then((registration) => {\n      return waitUntilInstalled(registration);\n    })\n    .then(() => {\n      return window.caches\n        .keys()\n        .then((keys) => {\n          return window.caches.open(keys[0]);\n        })\n        .then((cache) => {\n          return cache.keys();\n        });\n    })\n    .then((entries) => {\n      window.__testresult = {\n        entries: entries.map((entry) => {\n          return entry.url;\n        }),\n      };\n    })\n    .catch((err) => {\n      document.body.style.color = 'red';\n      document.body.innerHTML = `<h1>${err.message}</h1>\n\n    <pre>${err.stack}</pre>`;\n\n      console.error(err.message);\n      console.error(err.stack);\n\n      // This is just to help with local development so we know there is an error\n      setTimeout(() => {\n        window.__testresult = {\n          error: err,\n        };\n      }, 5 * 1000);\n    });\n</script>\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/page-1.html",
    "content": "<h1>Page 1</h1>\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/page-2.html",
    "content": "<h1>Page 2</h1>\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/splitChunksEntry.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport expect from 'chai';\n\nconsole.log({expect});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/styles/stylesheet-1.css",
    "content": "body {\n  background-color: red;\n}\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/styles/stylesheet-2.css",
    "content": "body {\n  background-color: blue;\n}\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/example-project-1/webpackEntry.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconsole.log('I am an entry.');\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/injected-manifest.js",
    "content": "/*\n  Copyright 2020 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nmodule.exports = self.__WB_MANIFEST;\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/injected-manifest.json",
    "content": "self.__WB_MANIFEST\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/module-import-sw.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {precacheAndRoute} from '../../../packages/workbox-precaching';\n\nprecacheAndRoute(self.__WB_MANIFEST);\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/sw-src-define-plugin.js",
    "content": "/*\n  Copyright 2020 Google LLC\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// This is used to validate the DefinePlugin's replacement.\nconst prefix = __PREFIX__;\nworkbox.core.setCacheNameDetails({prefix});\n\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST, {});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/sw-src-missing-sourcemap.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n/* eslint-disable */\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST, {});\n//# sourceMappingURL=does-not-exist.js.map\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/sw-src.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST, {});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/sw.ts",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\n// At the moment, this is just used to test the extension renaming.\nworkbox.precaching.precacheAndRoute(self.__WB_MANIFEST, {});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/wasm-project/index.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nnew Worker('./worker.js', {type: 'module'});\n"
  },
  {
    "path": "test/workbox-webpack-plugin/static/wasm-project/worker.js",
    "content": "/*\n  Copyright 2018 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport('./add.wasm').then(({add}) => {\n  console.log(add(10, 20));\n});\n"
  },
  {
    "path": "test/workbox-window/integration/test-all.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nconst {expect} = require('chai');\n\nconst {\n  executeAsyncAndCatch,\n} = require('../../../infra/testing/webdriver/executeAsyncAndCatch');\nconst {runUnitTests} = require('../../../infra/testing/webdriver/runUnitTests');\nconst {\n  IframeManager,\n} = require('../../../infra/testing/webdriver/IframeManager');\nconst {\n  unregisterAllSWs,\n} = require('../../../infra/testing/webdriver/unregisterAllSWs');\nconst {windowLoaded} = require('../../../infra/testing/webdriver/windowLoaded');\nconst templateData = require('../../../infra/testing/server/template-data');\n\n// Store local references of these globals.\nconst {webdriver, server} = global.__workbox;\n\nconst testServerOrigin = server.getAddress();\nconst testPath = `${testServerOrigin}/test/workbox-window/static/`;\n\ndescribe(`[workbox-window]`, function () {\n  it(`passes all window unit tests`, async function () {\n    await runUnitTests('/test/workbox-window/window/');\n  });\n});\n\ndescribe(`[workbox-window] Workbox`, function () {\n  beforeEach(async function () {\n    templateData.assign({version: '1'});\n    await webdriver.get(testPath);\n    await windowLoaded();\n  });\n\n  afterEach(async function () {\n    try {\n      await unregisterAllSWs();\n    } catch (error) {\n      // no-op\n    }\n  });\n\n  describe('register', () => {\n    it(`registers a new service worker`, async function () {\n      const result = await executeAsyncAndCatch(async (cb) => {\n        try {\n          const wb = new Workbox('sw-clients-claim.js.njk');\n          await wb.register();\n\n          const reg = await navigator.serviceWorker.getRegistration();\n          const sw = reg.installing || reg.waiting || reg.active;\n\n          cb({scriptURL: sw.scriptURL});\n        } catch (error) {\n          cb({error: error.stack});\n        }\n      });\n\n      expect(result.scriptURL).to.equal(`${testPath}sw-clients-claim.js.njk`);\n    });\n\n    it(`reports all events for a new SW registration`, async function () {\n      const result = await executeAsyncAndCatch(async (cb) => {\n        try {\n          const wb = new Workbox('sw-clients-claim.js.njk');\n\n          const installedSpy = sinon.spy();\n          const waitingSpy = sinon.spy();\n          const activatedSpy = sinon.spy();\n          const controllingSpy = sinon.spy();\n\n          wb.addEventListener('installed', installedSpy);\n          wb.addEventListener('waiting', waitingSpy);\n          wb.addEventListener('activated', activatedSpy);\n          wb.addEventListener('controlling', controllingSpy);\n\n          await wb.register();\n\n          await window.activatedAndControlling(wb);\n          cb({\n            isUpdate: installedSpy.args[0][0].isUpdate,\n            installedSpyCallCount: installedSpy.callCount,\n            waitingSpyCallCount: waitingSpy.callCount,\n            controllingSpyCallCount: controllingSpy.callCount,\n            controllingIsExternal: controllingSpy.args[0][0].isExternal,\n            activatedSpyCallCount: activatedSpy.callCount,\n          });\n        } catch (error) {\n          cb({error: error.stack});\n        }\n      });\n\n      // Test for truthiness because some browsers structure clone\n      // `undefined` to `null`.\n      expect(result.isUpdate).to.not.be.ok;\n      expect(result.controllingIsExternal).to.not.be.ok;\n      expect(result.installedSpyCallCount).to.equal(1);\n      expect(result.activatedSpyCallCount).to.equal(1);\n      expect(result.controllingSpyCallCount).to.equal(1);\n\n      //  A new installation shouldn't enter the waiting phase.\n      expect(result.waitingSpyCallCount).to.equal(0);\n    });\n\n    it(`reports all events for an updated SW registration`, async function () {\n      const result = await executeAsyncAndCatch(async (cb) => {\n        try {\n          const wb1 = new Workbox('sw-clients-claim.js.njk?v=1');\n          const redundantSpy = sinon.spy();\n          const wb1ControllingSpy = sinon.spy();\n          wb1.addEventListener('redundant', redundantSpy);\n          wb1.addEventListener('controlling', wb1ControllingSpy);\n\n          await wb1.register();\n          await window.activatedAndControlling(wb1);\n\n          const wb2 = new Workbox('sw-clients-claim.js.njk?v=2');\n\n          const installedSpy = sinon.spy();\n          const waitingSpy = sinon.spy();\n          const activatedSpy = sinon.spy();\n          const wb2ControllingSpy = sinon.spy();\n\n          wb2.addEventListener('installed', installedSpy);\n          wb2.addEventListener('waiting', waitingSpy);\n          wb2.addEventListener('activated', activatedSpy);\n          wb2.addEventListener('controlling', wb2ControllingSpy);\n\n          await wb2.register();\n\n          // Once the newly updated SW is in control, report back.\n          await window.activatedAndControlling(wb2);\n          cb({\n            wb1IsUpdate: redundantSpy.args[0][0].isUpdate,\n            wb2IsUpdate: installedSpy.args[0][0].isUpdate,\n            wb1ControllingIsExternal: wb1ControllingSpy.args[0][0].isExternal,\n            wb2ControllingIsExternal: wb2ControllingSpy.args[0][0].isExternal,\n            installedSpyCallCount: installedSpy.callCount,\n            waitingSpyCallCount: waitingSpy.callCount,\n            controllingSpyCallCount: wb2ControllingSpy.callCount,\n            activatedSpyCallCount: activatedSpy.callCount,\n          });\n        } catch (error) {\n          cb({error: error.stack});\n        }\n      });\n\n      // Test for truthiness because some browsers structure clone\n      // `undefined` to `null`.\n      expect(result.wb1IsUpdate).to.not.be.ok;\n      expect(result.wb2IsUpdate).to.equal(true);\n      expect(result.wb1ControllingIsExternal).to.not.be.ok;\n      expect(result.wb2ControllingIsExternal).to.not.be.ok;\n      expect(result.installedSpyCallCount).to.equal(1);\n      expect(result.waitingSpyCallCount).to.equal(0);\n      expect(result.activatedSpyCallCount).to.equal(1);\n      expect(result.controllingSpyCallCount).to.equal(1);\n    });\n\n    it(`reports all events for an external SW registration`, async function () {\n      const iframeManager = new IframeManager(webdriver);\n\n      await executeAsyncAndCatch(async (cb) => {\n        try {\n          const wb = new Workbox('sw-clients-claim.js.njk');\n\n          // Use a global variable so these are accessible to future\n          // `executeAsyncAndCatch()` calls.\n          self.__spies = {\n            installedSpy: sinon.spy(),\n            waitingSpy: sinon.spy(),\n            activatedSpy: sinon.spy(),\n            controllingSpy: sinon.spy(),\n          };\n\n          wb.addEventListener('installed', self.__spies.installedSpy);\n          wb.addEventListener('waiting', self.__spies.waitingSpy);\n          wb.addEventListener('activated', self.__spies.activatedSpy);\n          wb.addEventListener('controlling', self.__spies.controllingSpy);\n\n          await wb.register();\n\n          // Resolve this execution block once the SW is in control.\n          await window.activatedAndControlling(wb);\n          cb();\n        } catch (error) {\n          cb({error: error.stack});\n        }\n      });\n\n      // Update the version in sw.js to trigger a new installation.\n      templateData.assign({version: '2'});\n\n      const secondPath = `${testPath}?second`;\n      const iframeClient = await iframeManager.createIframeClient(secondPath);\n      const location = await iframeClient.executeAsyncScript(`\n        const wb = new Workbox('sw-clients-claim.js.njk');\n        wb.register()\n          .then(() => window.activatedAndControlling(wb))\n          .then(() => location.href);\n      `);\n\n      // Just confirm we're operating on the page we expect.\n      expect(location).to.eql(secondPath);\n\n      const result = await executeAsyncAndCatch(async (cb) => {\n        cb({\n          location: location.href,\n          installedSpyArgs: JSON.stringify(self.__spies.installedSpy.args),\n          waitingSpyArgs: JSON.stringify(self.__spies.waitingSpy.args),\n          activatedSpyArgs: JSON.stringify(self.__spies.activatedSpy.args),\n          controllingSpyArgs: JSON.stringify(self.__spies.controllingSpy.args),\n        });\n      });\n\n      const installedSpyArgs = JSON.parse(result.installedSpyArgs);\n      const waitingSpyArgs = JSON.parse(result.waitingSpyArgs);\n      const activatedSpyArgs = JSON.parse(result.activatedSpyArgs);\n      const controllingSpyArgs = JSON.parse(result.controllingSpyArgs);\n\n      // Just confirm we're operating on the page we expect.\n      expect(result.location).to.eql(testPath);\n\n      expect(installedSpyArgs.length, 'installedSpy').to.eql(2);\n      expect(waitingSpyArgs.length, 'waitingSpy').to.eql(0);\n      expect(activatedSpyArgs.length, 'activatedSpy').to.eql(2);\n      expect(controllingSpyArgs.length, 'controllingSpy').to.eql(2);\n\n      expect(installedSpyArgs[0][0].isExternal).to.eql(false);\n      expect(activatedSpyArgs[0][0].isExternal).to.eql(false);\n      expect(controllingSpyArgs[0][0].isExternal).to.eql(false);\n      expect(installedSpyArgs[1][0].isExternal).to.eql(true);\n      expect(activatedSpyArgs[1][0].isExternal).to.eql(true);\n      expect(controllingSpyArgs[1][0].isExternal).to.eql(true);\n    });\n  });\n});\n"
  },
  {
    "path": "test/workbox-window/static/index.html",
    "content": "<!DOCTYPE html>\n<!--\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n-->\n<html>\n  <head>\n    <meta charset=\"utf-8\" />\n    <title>Workbox Integration Tests</title>\n\n    <!-- Uncomment when locally running the module versions. -->\n    <!-- <script>self.process = self.process || {env: {NODE_ENV: 'dev'}};</script> -->\n\n    <script src=\"/node_modules/sinon/pkg/sinon.js\"></script>\n    <script src=\"/node_modules/chai/chai.js\"></script>\n    <script>\n      self.expect = self.chai.expect;\n    </script>\n  </head>\n  <body>\n    <p>You need to manually register a service worker</p>\n\n    <script type=\"module\">\n      import {Workbox} from '/__WORKBOX/buildFile/workbox-window';\n\n      // Expose on the global object so it can be referenced by webdriver.\n      window.Workbox = Workbox;\n\n      // Returns a promise that resolves when both the activated and controlling\n      // events have fired on the Workbox object.\n      window.activatedAndControlling = (wb) => {\n        const activatedPromise = new Promise((resolve) => {\n          wb.addEventListener('activated', () => resolve());\n        });\n        const controllingPromise = new Promise((resolve) => {\n          wb.addEventListener('controlling', () => resolve());\n        });\n        return Promise.all([activatedPromise, controllingPromise]);\n      };\n    </script>\n  </body>\n</html>\n"
  },
  {
    "path": "test/workbox-window/window/sw-error.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nthisShouldThrow(); // eslint-disable-line\n"
  },
  {
    "path": "test/workbox-window/window/sw-message-reply.js",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\naddEventListener('install', () => skipWaiting());\n\naddEventListener('message', async (event) => {\n  if (event.data.type === 'RESPOND_TO_MESSAGE') {\n    event.ports[0].postMessage('Reply from SW!');\n  } else if (event.data.type === 'POST_MESSAGE_BACK') {\n    const windows = await clients.matchAll({\n      type: 'window',\n      includeUncontrolled: true,\n    });\n    for (const win of windows) {\n      const channel = new MessageChannel();\n      win.postMessage('postMessage from SW!', [channel.port1]);\n    }\n  } else if (event.data.type === 'BROADCAST_BACK') {\n    const bc = new BroadcastChannel('workbox');\n    bc.postMessage('BroadcastChannel from SW!');\n  }\n});\n"
  },
  {
    "path": "test/workbox-window/window/test-Workbox.mjs",
    "content": "/*\n  Copyright 2019 Google LLC\n\n  Use of this source code is governed by an MIT-style\n  license that can be found in the LICENSE file or at\n  https://opensource.org/licenses/MIT.\n*/\n\nimport {Workbox} from '/__WORKBOX/buildFile/workbox-window';\n\nconst isDev = () => {\n  return (\n    self.process &&\n    self.process.env &&\n    self.process.env.NODE_ENV !== 'production'\n  );\n};\n\nconst sleep = async (ms) => {\n  return new Promise((resolve) => setTimeout(resolve, ms));\n};\n\nconst waitUntil = async (condition, timeout = 2000) => {\n  const startTime = performance.now();\n  while (!condition()) {\n    if (performance.now() > startTime + timeout) {\n      const error = new Error(`Timed out after ${timeout}ms.`);\n      console.error(error);\n      throw error;\n    }\n    await sleep(100);\n  }\n};\n\nconst nextEvent = (obj, eventType) => {\n  return new Promise((resolve) => obj.addEventListener(eventType, resolve));\n};\n\nconst uniq = (() => {\n  const timestamp = Date.now();\n  let uid = 0;\n  return (scriptURL) => {\n    const url = new URL(scriptURL, location);\n    url.searchParams.set('_id', `${++uid}-${timestamp}`);\n\n    return url.toString();\n  };\n})();\n\nconst stubAlreadyControllingSW = async (scriptURL) => {\n  await navigator.serviceWorker.register(scriptURL);\n  await waitUntil(() => {\n    return (\n      navigator.serviceWorker.controller &&\n      navigator.serviceWorker.controller.scriptURL.endsWith(scriptURL)\n    );\n  });\n};\n\nconst updateVersion = async (version, scriptURL) => {\n  // Dynamically update the version string in the SW script.\n  await fetch(`/__WORKBOX/updateTemplate`, {\n    method: 'POST',\n    headers: {'content-type': 'application/json'},\n    body: JSON.stringify({version}),\n  });\n\n  // If a script URL is passed, wait until a request for that script returns\n  // the newly set version.\n  let tries = 0;\n  if (scriptURL) {\n    while (++tries < 10) {\n      const resp = await fetch(scriptURL);\n      const text = await resp.text();\n      if (text.indexOf(version) > -1) {\n        return;\n      } else {\n        await sleep(100);\n      }\n    }\n  }\n  throw new Error('No updated version found after 10 retries');\n};\n\nconst assertMatchesWorkboxEvent = (event, props) => {\n  for (const [key, value] of Object.entries(props)) {\n    if (key === 'originalEvent' && typeof value !== 'undefined') {\n      expect(event.originalEvent.type, `${key} doesn't match`).to.equal(\n        value.type,\n      );\n    } else {\n      expect(event[key], `${key} doesn't match`).to.equal(value);\n    }\n  }\n};\n\nconst sandbox = sinon.createSandbox();\n\ndescribe(`[workbox-window] Workbox`, function () {\n  // Since it's not possible to completely unregister a controlling SW from\n  // a page (without closing all clients, including the current window), it's\n  // also not possible to run unit tests all from a fresh start in a single\n  // page load.\n  // Thus, to make these tests as predictable as possible, we start all unit\n  // tests with a controlling SW and only test the things that don't need to\n  // assert fresh-install behavior. Anything that does must be tested with\n  // integration tests.\n  beforeEach(async function () {\n    const scriptURL = uniq('sw-clients-claim.js.njk');\n    await updateVersion('1.0.0', scriptURL);\n    await stubAlreadyControllingSW(scriptURL);\n\n    sandbox.restore();\n    sandbox.spy(console, 'debug');\n    sandbox.spy(console, 'log');\n    sandbox.spy(console, 'warn');\n    sandbox.spy(console, 'error');\n  });\n\n  afterEach(async function () {\n    sandbox.restore();\n  });\n\n  describe(`constructor`, function () {\n    it(`creates an instance of the Workbox class`, async function () {\n      const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n      expect(wb).to.be.instanceOf(Workbox);\n    });\n\n    it(`does not register a SW`, function (done) {\n      sandbox.spy(navigator.serviceWorker, 'register');\n\n      new Workbox(uniq('sw-clients-claim.js.njk'));\n\n      // Queue a task to ensure a SW isn't registered async.\n      setTimeout(() => {\n        // Not calling addEventListener means Workbox properly detected that\n        // the window was already loaded\n        expect(navigator.serviceWorker.register.callCount).to.equal(0);\n        done();\n      }, 0);\n    });\n  });\n\n  describe(`register`, function () {\n    it(`registers a service worker if the window is loaded`, async function () {\n      sandbox.spy(navigator.serviceWorker, 'register');\n      sandbox.spy(self, 'addEventListener');\n\n      const scriptURL = uniq('sw-no-skip-waiting.js.njk');\n      const wb = new Workbox(scriptURL);\n      await wb.register();\n\n      // Not calling addEventListener means Workbox properly detected that\n      // the window was already loaded\n      expect(self.addEventListener.calledWith('load')).to.not.equal(true);\n      expect(navigator.serviceWorker.register.callCount).to.equal(1);\n      expect(navigator.serviceWorker.register.args[0][0]).to.equal(scriptURL);\n    });\n\n    it(`defers registration until after load by default`, async function () {\n      sandbox.spy(navigator.serviceWorker, 'register');\n      sandbox.spy(self, 'addEventListener');\n\n      // Stub the window not yet being loaded\n      sandbox.stub(document, 'readyState').value('loading');\n\n      // Trigger the load event in the next task.\n      setTimeout(() => self.dispatchEvent(new Event('load'), 0));\n\n      const wb = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n      await wb.register();\n\n      expect(self.addEventListener.calledWith('load')).to.equal(true);\n      expect(self.addEventListener.args[0][0]).to.equal('load');\n    });\n\n    it(`supports not deferring until load`, async function () {\n      sandbox.spy(navigator.serviceWorker, 'register');\n      sandbox.spy(self, 'addEventListener');\n\n      // Stub the window not yet being loaded\n      sandbox.stub(document, 'readyState').value('loading');\n\n      // Trigger the load event in the next task.\n      setTimeout(() => self.dispatchEvent(new Event('load'), 0));\n\n      const wb = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n      await wb.register({immediate: true});\n\n      expect(self.addEventListener.calledWith('load')).to.not.equal(true);\n    });\n\n    it(`errors when registration fails`, async function () {\n      const wb = new Workbox(uniq('sw-error.js'));\n\n      try {\n        await wb.register();\n        // We shouldn't get here because the above line should fail.\n        throw new Error('unexpected');\n      } catch (error) {\n        expect(error.name).to.equal('TypeError');\n        expect(error.message).not.to.match(/unexpected/i);\n      }\n    });\n\n    describe(`logs in development-only`, function () {\n      it(`(debug) if a SW with the same script URL is already controlling the page`, async function () {\n        if (!isDev()) this.skip();\n\n        // Gets the URL of the currently controlling SW.\n        const {scriptURL} = navigator.serviceWorker.controller;\n\n        const wb = new Workbox(scriptURL);\n        await wb.register();\n\n        expect(console.debug.callCount).to.equal(1);\n        expect(console.debug.args[0][2]).to.match(/same/i);\n      });\n\n      it(`(debug) if a SW with a different script URL is already controlling the page`, async function () {\n        if (!isDev()) this.skip();\n\n        const wb = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        await wb.register();\n\n        expect(console.debug.callCount).to.equal(1);\n        expect(console.debug.args[0][2]).to.match(/different/i);\n        expect(console.debug.args[0][2]).to.match(/new/i);\n      });\n\n      it(`(info) when registration is successful`, async function () {\n        if (!isDev()) this.skip();\n\n        sandbox.spy(navigator.serviceWorker, 'register');\n\n        const wb = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        await wb.register();\n\n        expect(console.log.callCount).to.equal(1);\n        expect(console.log.args[0][2]).to.match(/success/i);\n      });\n\n      it(`(warn) when the registered SW is not in scope for the current page`, async function () {\n        if (!isDev()) this.skip();\n\n        sandbox.spy(navigator.serviceWorker, 'register');\n\n        const wb = new Workbox(uniq('/out-of-scope/sw-clients-claim.js.njk'));\n        await wb.register();\n\n        expect(console.warn.callCount).to.equal(1);\n        expect(console.warn.args[0][2]).to.include('scope');\n      });\n\n      it(`(warn) when a service worker is installed but now waiting`, async function () {\n        if (!isDev()) this.skip();\n\n        const wb = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        await wb.register();\n\n        await waitUntil(() => console.warn.callCount === 1);\n        expect(console.warn.args[0][2]).to.match(/waiting/i);\n      });\n\n      it(`(error) when registration fails`, async function () {\n        if (!isDev()) this.skip();\n\n        const wb = new Workbox(uniq('sw-error.js'));\n\n        try {\n          await wb.register();\n          // We shouldn't get here because the above line should fail.\n          throw new Error('unexpected');\n        } catch (error) {\n          expect(error.name).to.equal('TypeError');\n          expect(console.error.callCount).to.equal(1);\n          expect(console.error.args[0][2].message).to.equal(error.message);\n        }\n      });\n\n      it(`(error) if calling register twice`, async function () {\n        if (!isDev()) this.skip();\n\n        const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n        await wb.register();\n        await wb.active;\n\n        await wb.register();\n        expect(console.error.callCount).to.equal(1);\n        expect(console.error.args[0][2]).to.match(/cannot re-register/i);\n      });\n    });\n  });\n\n  describe(`update`, function () {\n    it(`calls update on the registration`, async function () {\n      const scriptURL = navigator.serviceWorker.controller.scriptURL;\n      const wb = new Workbox(scriptURL);\n      const reg = await wb.register();\n\n      sandbox.stub(reg, 'update');\n\n      await wb.update();\n\n      expect(reg.update.callCount).to.equal(1);\n    });\n\n    it(`triggers an updatefound event if the SW was updated`, async function () {\n      const scriptURL = navigator.serviceWorker.controller.scriptURL;\n\n      const wb = new Workbox(scriptURL);\n\n      const reg = await wb.register();\n      const updatefoundPromise = new Promise((resolve) => {\n        reg.addEventListener('updatefound', () => {\n          expect(reg.installing).to.not.equal(\n            navigator.serviceWorker.controller,\n          );\n          resolve();\n        });\n      });\n\n      await wb.controlling;\n\n      // Update the SW after so an update check triggers an update.\n      await updateVersion('2.0.0', scriptURL);\n\n      wb.update();\n\n      await updatefoundPromise;\n    });\n\n    describe(`logs in development-only`, function () {\n      it(`(error) if calling without registration`, async function () {\n        if (!isDev()) this.skip();\n\n        const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n\n        await wb.update();\n        expect(console.error.callCount).to.equal(1);\n        expect(console.error.args[0][2]).to.match(/cannot update/i);\n      });\n    });\n  });\n\n  describe(`active`, function () {\n    it(`resolves as soon as the registered SW is active`, async function () {\n      const controllerBeforeTest = navigator.serviceWorker.controller;\n      const scriptURL = controllerBeforeTest.scriptURL;\n      const wb = new Workbox(scriptURL);\n\n      // Registering using the same script URL that's already active won't\n      // trigger an update.\n      const reg = await wb.register();\n      const sw = await wb.active;\n\n      expect(sw).to.equal(reg.active);\n      expect(sw).to.equal(controllerBeforeTest);\n    });\n\n    it(`waits for an update if the scriptURLs don't match`, async function () {\n      const controllerBeforeTest = navigator.serviceWorker.controller;\n      const scriptURL = uniq('sw-clients-claim.js.njk');\n      const wb = new Workbox(scriptURL);\n\n      // Registering using a different script URL should trigger an update,\n      // and `.active` shouldn't resolve until after the update.\n      const reg = await wb.register();\n      const sw = await wb.active;\n\n      expect(sw).to.equal(reg.active);\n      expect(sw).to.not.equal(controllerBeforeTest);\n    });\n  });\n\n  describe(`controlling`, function () {\n    it(`resolves as soon as the registered SW is controlling`, async function () {\n      const controllerBeforeTest = navigator.serviceWorker.controller;\n      const scriptURL = controllerBeforeTest.scriptURL;\n      const wb = new Workbox(scriptURL);\n\n      // Registering using the same script URL that's already controlling\n      // won't trigger an update.\n      await wb.register();\n      const sw = await wb.controlling;\n\n      expect(sw).to.equal(navigator.serviceWorker.controller);\n      expect(sw).to.equal(controllerBeforeTest);\n    });\n\n    it(`waits for an update if the scriptURLs don't match`, async function () {\n      const controllerBeforeTest = navigator.serviceWorker.controller;\n      const scriptURL = uniq('sw-clients-claim.js.njk');\n      const wb = new Workbox(scriptURL);\n\n      // Registering using a different script URL should trigger an update,\n      // and `.controlling` shouldn't resolve until after the update.\n      await wb.register();\n      const sw = await wb.controlling;\n\n      expect(sw).to.equal(navigator.serviceWorker.controller);\n      expect(sw).to.not.equal(controllerBeforeTest);\n    });\n  });\n\n  describe(`getSW`, function () {\n    it(`resolves as soon as it has a reference to the SW registered by this instance`, async function () {\n      const wb = new Workbox(uniq('sw-skip-waiting-deferred.js.njk'));\n\n      // Intentionally do not await `register()`, so we can test that\n      // `getSW()` does in its implementation.\n      wb.register();\n\n      const reg = await navigator.serviceWorker.getRegistration();\n      const sw = await wb.getSW();\n\n      // This SW defers calling skip waiting, so our SW should match the\n      // installing service worker.\n      expect(sw).to.equal(reg.installing);\n    });\n\n    it(`resolves before updating if a SW with the same script URL is already controlling`, async function () {\n      const scriptURL = navigator.serviceWorker.controller.scriptURL;\n      const wb = new Workbox(scriptURL);\n\n      // Registering using the same script URL that's already active won't\n      // trigger an update.\n      wb.register();\n\n      const sw = await wb.getSW();\n      expect(sw).to.equal(navigator.serviceWorker.controller);\n    });\n\n    it(`resolves before updating if a SW with the same script URL is already waiting to install`, async function () {\n      const scriptURL = uniq('sw-no-skip-waiting.js.njk');\n\n      const wb1 = new Workbox(scriptURL);\n      const reg1 = await wb1.register();\n\n      await nextEvent(wb1, 'waiting');\n      expect(reg1.waiting.scriptURL).to.equal(scriptURL);\n\n      // Stub the controlling SW's scriptURL so it matches the SW that is\n      // about to be waiting. This is done to assert that if a matching\n      // controller *and* waiting SW are found at registration time, the\n      // `getSW()` method resolves to the waiting SW.\n      sandbox\n        .stub(navigator.serviceWorker.controller, 'scriptURL')\n        .value(scriptURL);\n\n      const wb2 = new Workbox(scriptURL);\n      const reg2Promise = wb2.register();\n\n      const sw = await wb2.getSW();\n      const reg2 = await reg2Promise;\n      expect(sw).to.equal(reg2.waiting);\n    });\n\n    it(`resolves as soon as an an update is found (if not already resolved)`, async function () {\n      const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n      wb.register();\n\n      const sw = await wb.getSW();\n      expect(sw.state).to.equal('installing');\n    });\n\n    it(`resolves to the new SW after an update is found`, async function () {\n      const scriptURL = navigator.serviceWorker.controller.scriptURL;\n\n      // Update the SW after it's controlling so both an original compatible\n      // controller is found **and** and update is found. We need to assert\n      // that the `getSW()` method resolves to the correct SW in both cases.\n      await updateVersion('2.0.0', scriptURL);\n\n      const wb = new Workbox(scriptURL);\n\n      // Registering using the same script URL that's already active won't\n      // necessarily trigger an update, so we have to also call update below.\n      const regPromise = wb.register();\n\n      const controllingSW = await wb.getSW();\n      expect(controllingSW).to.equal(navigator.serviceWorker.controller);\n\n      const reg = await regPromise;\n\n      // Force an update check.\n      reg.update();\n\n      await nextEvent(wb, 'controlling');\n\n      const installedSW = await wb.getSW();\n      expect(installedSW).to.equal(reg.active);\n      expect(installedSW).to.not.equal(controllingSW);\n    });\n  });\n\n  describe(`messageSW`, function () {\n    it(`postMessages the registered service worker`, async function () {\n      const wb = new Workbox(uniq('sw-message-reply.js'));\n      await wb.register();\n\n      const messageSpy = sandbox.spy();\n      navigator.serviceWorker.addEventListener('message', messageSpy);\n\n      wb.messageSW({type: 'POST_MESSAGE_BACK'});\n      await waitUntil(() => messageSpy.called);\n\n      expect(messageSpy.args[0][0].data).to.equal('postMessage from SW!');\n    });\n\n    it(`returns a promise that resolves with the SW's response (if any)`, async function () {\n      const wb = new Workbox(uniq('sw-message-reply.js'));\n      wb.register();\n\n      const response = await wb.messageSW({type: 'RESPOND_TO_MESSAGE'});\n      expect(response).to.equal('Reply from SW!');\n    });\n\n    it(`awaits registration if registration hasn't run`, async function () {\n      const wb = new Workbox(uniq('sw-message-reply.js'));\n      setTimeout(() => wb.register(), 100);\n\n      const response = await wb.messageSW({type: 'RESPOND_TO_MESSAGE'});\n      expect(response).to.equal('Reply from SW!');\n    });\n  });\n\n  describe(`messageSkipWaiting`, function () {\n    it(`posts the expected message to the waiting service worker`, async function () {\n      const scriptURL = uniq('sw-skip-waiting-on-message.js.njk');\n      const wb = new Workbox(scriptURL);\n\n      const waitingSWPromise = new Promise((resolve) => {\n        wb.addEventListener('waiting', (event) => resolve(event.sw));\n      });\n\n      await wb.register();\n      const waitingSW = await waitingSWPromise;\n\n      const postMessageSpy = sandbox.spy(waitingSW, 'postMessage');\n\n      const controllingSWPromise = new Promise((resolve) => {\n        wb.addEventListener('controlling', (event) => resolve(event.sw));\n      });\n\n      wb.messageSkipWaiting();\n\n      // Confirm that the right postMessage() is sent.\n      expect(postMessageSpy.callCount).to.eql(1);\n      expect(postMessageSpy.firstCall.args[0]).to.eql({type: 'SKIP_WAITING'});\n      expect(postMessageSpy.firstCall.args[1][0]).to.be.instanceOf(MessagePort);\n\n      const controllingSW = await controllingSWPromise;\n\n      // Confirm that the service worker associated with this test takes control.\n      expect(controllingSW.scriptURL).to.eql(scriptURL);\n    });\n\n    it(`does nothing if there's no waiting service worker`, async function () {\n      const wb = new Workbox(uniq('sw-skip-waiting.js.njk'));\n      await wb.register();\n\n      // This should be a no-op.\n      // Just ensure that there's no exceptions thrown, etc.\n      wb.messageSkipWaiting();\n    });\n  });\n\n  describe(`events`, function () {\n    describe(`message`, function () {\n      it(`fires when a postMessage is received from the SW`, async function () {\n        const wb = new Workbox(uniq('sw-message-reply.js'));\n        await wb.register();\n        await wb.getSW();\n\n        const messageSpy = sandbox.spy();\n        wb.addEventListener('message', messageSpy);\n\n        wb.messageSW({type: 'POST_MESSAGE_BACK'});\n        await nextEvent(wb, 'message');\n\n        const wbEvent = messageSpy.args[0][0];\n        assertMatchesWorkboxEvent(wbEvent, {\n          data: 'postMessage from SW!',\n          originalEvent: {type: 'message'},\n          ports: wbEvent.originalEvent.ports,\n          target: wb,\n          type: 'message',\n        });\n      });\n\n      it(`can receive a message prior to calling register but buffers them until after registration`, async function () {\n        const scriptURL = navigator.serviceWorker.controller.scriptURL;\n        const wb = new Workbox(scriptURL);\n\n        const messageSpy = sandbox.spy();\n        wb.addEventListener('message', messageSpy);\n\n        // Simulate a message event sent from the controlling service worker\n        // at page load time (prior to calling `register()`);\n        navigator.serviceWorker.dispatchEvent(\n          new MessageEvent('message', {\n            data: 'postMessage from during page load!',\n            source: navigator.serviceWorker.controller,\n          }),\n        );\n\n        expect(messageSpy.notCalled).to.be.true;\n\n        await wb.register();\n\n        expect(messageSpy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(messageSpy.args[0][0], {\n          type: 'message',\n          target: wb,\n          sw: navigator.serviceWorker.controller,\n          data: 'postMessage from during page load!',\n          originalEvent: {type: 'message'},\n        });\n      });\n\n      it(`does not dispatch messages received from non-own service workers`, async function () {\n        const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n\n        const messageSpy = sandbox.spy();\n        wb.addEventListener('message', messageSpy);\n\n        // Simulate a message event sent from the controlling service worker\n        // at page load time (prior to calling `register()`);\n        navigator.serviceWorker.dispatchEvent(\n          new MessageEvent('message', {\n            data: 'postMessage from during page load!',\n            source: navigator.serviceWorker.controller,\n          }),\n        );\n\n        expect(messageSpy.notCalled).to.be.true;\n\n        wb.register();\n        await wb.controlling;\n\n        expect(messageSpy.notCalled).to.be.true;\n      });\n    });\n\n    describe(`installed`, function () {\n      it(`fires the first time the registered SW is installed`, async function () {\n        const scriptURL = uniq('sw-clients-claim.js.njk');\n\n        const wb1 = new Workbox(scriptURL);\n        const installed1Spy = sandbox.spy();\n        wb1.addEventListener('installed', installed1Spy);\n\n        await wb1.register();\n        await nextEvent(wb1, 'installed');\n\n        // Create a second instance for the same SW script so it won't be\n        // installed this time.\n        const wb2 = new Workbox(scriptURL);\n        const installed2Spy = sandbox.spy();\n        wb2.addEventListener('installed', installed2Spy);\n        await wb2.register();\n\n        // Create a third instance for a different script to assert the\n        // callback runs again, but only for own instances.\n        const wb3 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const installed3Spy = sandbox.spy();\n        wb3.addEventListener('installed', installed3Spy);\n        await wb3.register();\n        await nextEvent(wb3, 'installed');\n\n        expect(installed1Spy.callCount).to.equal(2);\n        assertMatchesWorkboxEvent(installed1Spy.args[0][0], {\n          type: 'installed',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          isUpdate: true,\n          isExternal: false,\n        });\n        assertMatchesWorkboxEvent(installed1Spy.args[1][0], {\n          type: 'installed',\n          target: wb1,\n          sw: await wb3.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: true,\n        });\n\n        expect(installed2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(installed2Spy.args[0][0], {\n          type: 'installed',\n          target: wb2,\n          sw: await wb3.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: true,\n        });\n\n        expect(installed3Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(installed3Spy.args[0][0], {\n          type: 'installed',\n          target: wb3,\n          sw: await wb3.getSW(),\n          originalEvent: {type: 'statechange'},\n          isUpdate: true,\n          isExternal: false,\n        });\n      });\n    });\n\n    describe(`waiting`, function () {\n      it(`runs if the registered service worker is waiting`, async function () {\n        const wb1 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        const waiting1Spy = sandbox.spy();\n        wb1.addEventListener('waiting', waiting1Spy);\n\n        const wb2 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const waiting2Spy = sandbox.spy();\n        wb2.addEventListener('waiting', waiting2Spy);\n\n        const wb3 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        const waiting3Spy = sandbox.spy();\n        wb3.addEventListener('waiting', waiting3Spy);\n\n        await wb1.register();\n        await nextEvent(wb1, 'waiting');\n        await wb2.register();\n        await wb3.register();\n        await nextEvent(wb3, 'waiting');\n\n        expect(waiting1Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(waiting1Spy.args[0][0], {\n          type: 'waiting',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          wasWaitingBeforeRegister: undefined,\n          isExternal: false,\n        });\n\n        expect(waiting2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(waiting1Spy.args[0][0], {\n          type: 'waiting',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          wasWaitingBeforeRegister: undefined,\n          isExternal: false,\n        });\n\n        expect(waiting3Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(waiting3Spy.args[0][0], {\n          type: 'waiting',\n          target: wb3,\n          sw: await wb3.getSW(),\n          originalEvent: {type: 'statechange'},\n          wasWaitingBeforeRegister: undefined,\n          isExternal: false,\n        });\n      });\n\n      it(`runs if a service worker was already waiting at registration time`, async function () {\n        const scriptURL = uniq('sw-no-skip-waiting.js.njk');\n\n        const wb1 = new Workbox(scriptURL);\n        await wb1.register();\n        await nextEvent(wb1, 'waiting');\n\n        // Register a new instance for the already waiting script.\n        const wb2 = new Workbox(scriptURL);\n        const waiting2Spy = sandbox.spy();\n        wb2.addEventListener('waiting', waiting2Spy);\n        await wb2.register();\n\n        console.log(waiting2Spy.args);\n\n        expect(waiting2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(waiting2Spy.args[0][0], {\n          type: 'waiting',\n          target: wb2,\n          sw: await wb1.getSW(),\n          originalEvent: undefined,\n          isUpdate: undefined,\n          wasWaitingBeforeRegister: true,\n        });\n      });\n    });\n\n    describe(`activated`, function () {\n      it(`runs the first time the registered SW is activated`, async function () {\n        const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const activated1Spy = sandbox.spy();\n        wb1.addEventListener('activated', activated1Spy);\n        await wb1.register();\n        await nextEvent(wb1, 'activated');\n\n        const wb2 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        const activated2Spy = sandbox.spy();\n        wb2.addEventListener('activated', activated2Spy);\n        await wb2.register();\n\n        const wb3 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const activated3Spy = sandbox.spy();\n        wb3.addEventListener('activated', activated3Spy);\n        await wb3.register();\n        await nextEvent(wb3, 'activated');\n\n        expect(activated1Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(activated1Spy.args[0][0], {\n          type: 'activated',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: false,\n        });\n\n        expect(activated2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(activated1Spy.args[0][0], {\n          type: 'activated',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: false,\n        });\n\n        expect(activated3Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(activated3Spy.args[0][0], {\n          type: 'activated',\n          target: wb3,\n          sw: await wb3.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: false,\n        });\n      });\n    });\n\n    describe(`controlling`, function () {\n      it(`runs the first time the registered SW is controlling`, async function () {\n        const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const controlling1Spy = sandbox.spy();\n        wb1.addEventListener('controlling', controlling1Spy);\n        await wb1.register();\n        await nextEvent(wb1, 'controlling');\n\n        const wb2 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        const controlling2Spy = sandbox.spy();\n        wb2.addEventListener('controlling', controlling2Spy);\n        await wb2.register();\n\n        const wb3 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const controlling3Spy = sandbox.spy();\n        wb3.addEventListener('controlling', controlling3Spy);\n        await wb3.register();\n        await nextEvent(wb3, 'controlling');\n\n        // NOTE(philipwalton): because these unit tests always start with a\n        // controlling SW, this test doesn't really cover the case where a SW\n        // is active but not yet controlling the page (which can happen\n        // the first time a page registers a SW). This case is tested in the\n        // integration tests.\n\n        expect(controlling1Spy.callCount).to.equal(2);\n        assertMatchesWorkboxEvent(controlling1Spy.args[0][0], {\n          isExternal: false,\n          isUpdate: true,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb1.getSW(),\n          target: wb1,\n          type: 'controlling',\n        });\n        assertMatchesWorkboxEvent(controlling1Spy.args[1][0], {\n          isExternal: true,\n          isUpdate: true,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb1.getSW(),\n          target: wb1,\n          type: 'controlling',\n        });\n\n        // This will be an \"external\" event, due to wb3's SW taking control.\n        // wb2's SW never controls, because it's stuck in waiting.\n        expect(controlling2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(controlling2Spy.args[0][0], {\n          isExternal: true,\n          isUpdate: true,\n          type: 'controlling',\n        });\n\n        expect(controlling3Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(controlling3Spy.args[0][0], {\n          isExternal: false,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb3.getSW(),\n          target: wb3,\n          type: 'controlling',\n        });\n      });\n\n      it(`runs every time the registered SW is updated`, async function () {\n        const scriptURL = uniq('sw-skip-waiting.js.njk');\n        const wb1 = new Workbox(scriptURL);\n        const controlling1Spy = sandbox.spy();\n        wb1.addEventListener('controlling', controlling1Spy);\n        await wb1.register();\n        await nextEvent(wb1, 'controlling');\n\n        await updateVersion('2.0.0', scriptURL);\n\n        wb1.update();\n        await nextEvent(wb1, 'controlling');\n\n        await updateVersion('3.0.0', scriptURL);\n\n        wb1.update();\n        await nextEvent(wb1, 'controlling');\n\n        expect(controlling1Spy.callCount).to.equal(3);\n        assertMatchesWorkboxEvent(controlling1Spy.args[0][0], {\n          isExternal: false,\n          isUpdate: true,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb1.getSW(),\n          target: wb1,\n          type: 'controlling',\n        });\n        assertMatchesWorkboxEvent(controlling1Spy.args[1][0], {\n          isExternal: true,\n          isUpdate: true,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb1.getSW(),\n          target: wb1,\n          type: 'controlling',\n        });\n        assertMatchesWorkboxEvent(controlling1Spy.args[2][0], {\n          isExternal: true,\n          isUpdate: true,\n          originalEvent: {type: 'controllerchange'},\n          sw: await wb1.getSW(),\n          target: wb1,\n          type: 'controlling',\n        });\n      });\n    });\n\n    describe(`redundant`, function () {\n      it(`runs if the registered SW becomes redundant`, async function () {\n        const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const redundantSpy = sandbox.spy();\n        wb1.addEventListener('redundant', redundantSpy);\n\n        await wb1.register();\n        await wb1.controlling;\n\n        const wb2 = new Workbox(uniq('sw-skip-waiting.js.njk'));\n\n        await wb2.register();\n        await wb2.controlling;\n\n        expect(redundantSpy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(redundantSpy.args[0][0], {\n          type: 'redundant',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n        });\n      });\n\n      it(`runs in the case where the registered SW was already controlling`, async function () {\n        const controllerBeforeTest = navigator.serviceWorker.controller;\n        const scriptURL = controllerBeforeTest.scriptURL;\n        const wb1 = new Workbox(scriptURL);\n\n        const redundantSpy = sandbox.spy();\n        wb1.addEventListener('redundant', redundantSpy);\n\n        await wb1.register();\n        await wb1.controlling;\n\n        const wb2 = new Workbox(uniq('sw-skip-waiting.js.njk'));\n\n        await wb2.register();\n        await wb2.controlling;\n\n        expect(redundantSpy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(redundantSpy.args[0][0], {\n          type: 'redundant',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n        });\n      });\n    });\n\n    describe(`isExternal logic`, function () {\n      it(`runs when an external SW is found and installed`, async function () {\n        const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n        const externalInstalled1Spy = sandbox.spy();\n        wb1.addEventListener('installed', externalInstalled1Spy);\n        await wb1.register();\n        await wb1.controlling;\n\n        const wb2 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n        const externalInstalled2Spy = sandbox.spy();\n        wb2.addEventListener('installed', externalInstalled2Spy);\n        await wb2.register();\n        await nextEvent(wb2, 'installed');\n\n        expect(externalInstalled1Spy.callCount).to.equal(2);\n        assertMatchesWorkboxEvent(externalInstalled1Spy.args[0][0], {\n          type: 'installed',\n          target: wb1,\n          sw: await wb1.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: false,\n        });\n        assertMatchesWorkboxEvent(externalInstalled1Spy.args[1][0], {\n          type: 'installed',\n          target: wb1,\n          sw: await wb2.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: true,\n        });\n\n        expect(externalInstalled2Spy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(externalInstalled2Spy.args[0][0], {\n          type: 'installed',\n          target: wb2,\n          sw: await wb2.getSW(),\n          originalEvent: {type: 'statechange'},\n          isExternal: false,\n        });\n      });\n\n      it(`runs when an updated version of the registered SW is found after the update timeout`, async function () {\n        const clock = sandbox.useFakeTimers({\n          toFake: ['performance'],\n        });\n\n        const scriptURL = navigator.serviceWorker.controller.scriptURL;\n\n        const wb = new Workbox(scriptURL);\n        const reg = await wb.register();\n        await wb.controlling;\n\n        // Update the SW after so an update check triggers an update.\n        await updateVersion('2.0.0', scriptURL);\n\n        let updatedSW;\n        reg.addEventListener('updatefound', () => {\n          updatedSW = reg.installing;\n        });\n\n        const externalInstalledSpy = sandbox.spy();\n        wb.addEventListener('installed', externalInstalledSpy);\n\n        // Let more than an hour pass.\n        clock.tick(60001);\n\n        wb.update();\n        await waitUntil(() => externalInstalledSpy.callCount === 1);\n\n        expect(externalInstalledSpy.callCount).to.equal(1);\n        assertMatchesWorkboxEvent(externalInstalledSpy.args[0][0], {\n          type: 'installed',\n          target: wb,\n          sw: updatedSW,\n          originalEvent: {type: 'statechange'},\n          isExternal: true,\n        });\n      });\n    });\n\n    it(`runs when an external SW is waiting`, async function () {\n      const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n      const externalWaiting1Spy = sandbox.spy();\n      wb1.addEventListener('waiting', externalWaiting1Spy);\n      await wb1.register();\n      await nextEvent(wb1, 'controlling');\n\n      const wb2 = new Workbox(uniq('sw-no-skip-waiting.js.njk'));\n      const externalWaiting2Spy = sandbox.spy();\n      wb2.addEventListener('waiting', externalWaiting2Spy);\n      await wb2.register();\n      await nextEvent(wb2, 'waiting');\n\n      expect(externalWaiting1Spy.callCount).to.equal(1);\n      assertMatchesWorkboxEvent(externalWaiting1Spy.args[0][0], {\n        type: 'waiting',\n        target: wb1,\n        sw: await wb2.getSW(),\n        originalEvent: {type: 'statechange'},\n      });\n\n      expect(externalWaiting2Spy.callCount).to.equal(1);\n      assertMatchesWorkboxEvent(externalWaiting2Spy.args[0][0], {\n        type: 'waiting',\n        target: wb2,\n        sw: await wb2.getSW(),\n        originalEvent: {type: 'statechange'},\n      });\n    });\n\n    it(`runs when an updated version of the registered SW is found after the update timeout and is waiting to activate`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['performance'],\n      });\n\n      const scriptURL = uniq('sw-skip-waiting-on-message.js.njk');\n      const wb = new Workbox(scriptURL);\n      const reg = await wb.register();\n\n      wb.messageSW({type: 'SKIP_WAITING'});\n      await wb.controlling;\n\n      // Update the SW after so an update check triggers an update.\n      await updateVersion('2.0.0', scriptURL);\n\n      let updatedSW;\n      reg.addEventListener('updatefound', () => {\n        updatedSW = reg.installing;\n      });\n\n      const externalWaitingSpy = sandbox.spy();\n      wb.addEventListener('waiting', externalWaitingSpy);\n\n      // Let more than an hour pass.\n      clock.tick(60001);\n\n      wb.update();\n      await waitUntil(() => externalWaitingSpy.callCount === 1);\n\n      expect(externalWaitingSpy.callCount).to.equal(1);\n      assertMatchesWorkboxEvent(externalWaitingSpy.args[0][0], {\n        type: 'waiting',\n        target: wb,\n        sw: updatedSW,\n        originalEvent: {type: 'statechange'},\n      });\n    });\n\n    it(`runs when an external SW is found and activated`, async function () {\n      const wb1 = new Workbox(uniq('sw-clients-claim.js.njk'));\n      await wb1.register();\n      await nextEvent(wb1, 'activated');\n\n      const externalActivated1Spy = sandbox.spy();\n      wb1.addEventListener('activated', externalActivated1Spy);\n\n      const wb2 = new Workbox(uniq('sw-skip-waiting.js.njk'));\n      await wb2.register();\n      await nextEvent(wb2, 'activated');\n\n      assertMatchesWorkboxEvent(externalActivated1Spy.args[0][0], {\n        type: 'activated',\n        target: wb1,\n        sw: await wb2.getSW(),\n        originalEvent: {type: 'statechange'},\n        isExternal: true,\n      });\n    });\n\n    it(`runs when an updated version of the registered SW is found after the update timeout and has activated`, async function () {\n      const clock = sandbox.useFakeTimers({\n        toFake: ['performance'],\n      });\n\n      const scriptURL = navigator.serviceWorker.controller.scriptURL;\n\n      const wb = new Workbox(scriptURL);\n      const reg = await wb.register();\n      await wb.controlling;\n\n      // Update the SW after so an update check triggers an update.\n      await updateVersion('2.0.0', scriptURL);\n\n      let updatedSW;\n      reg.addEventListener('updatefound', () => {\n        updatedSW = reg.installing;\n      });\n\n      const externalActivatedSpy = sandbox.spy();\n      wb.addEventListener('activated', externalActivatedSpy);\n\n      // Let more than an hour pass.\n      clock.tick(60001);\n\n      wb.update();\n      await waitUntil(() => externalActivatedSpy.callCount === 1);\n\n      expect(externalActivatedSpy.callCount).to.equal(1);\n      assertMatchesWorkboxEvent(externalActivatedSpy.args[0][0], {\n        type: 'activated',\n        target: wb,\n        sw: updatedSW,\n        originalEvent: {type: 'statechange'},\n      });\n    });\n\n    describe(`removeEventListener()`, function () {\n      it(`will register and then unregister event listeners of a given type`, function () {\n        const eventType = 'testEventType';\n        const event = {type: eventType};\n        const eventListener1 = sandbox.stub();\n        const eventListener2 = sandbox.stub();\n\n        const wb = new Workbox(uniq('sw-clients-claim.js.njk'));\n        wb.addEventListener(eventType, eventListener1);\n        wb.addEventListener(eventType, eventListener2);\n\n        wb.dispatchEvent(event);\n        expect(eventListener1.calledOnceWith(event)).to.be.true;\n        expect(eventListener2.calledOnceWith(event)).to.be.true;\n\n        wb.removeEventListener(eventType, eventListener2);\n        wb.dispatchEvent(event);\n\n        // The remaining stub should be called again.\n        expect(eventListener1.calledTwice).to.be.true;\n        // Make sure the removed stub was called only once.\n        expect(eventListener2.calledOnce).to.be.true;\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declaration\": true,\n    \"lib\": [\"es2020\", \"webworker\"],\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"noFallthroughCasesInSwitch\": true,\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"preserveConstEnums\": true,\n    \"strict\": true,\n    \"target\": \"es2017\",\n    \"tsBuildInfoFile\": \"./tsconfig.tsbuildinfo\"\n  },\n  \"files\": [\"./infra/type-overrides.d.ts\"],\n  \"exclude\": [],\n  \"references\": [\n    {\"path\": \"./packages/workbox-broadcast-update/\"},\n    {\"path\": \"./packages/workbox-background-sync/\"},\n    {\"path\": \"./packages/workbox-build/\"},\n    {\"path\": \"./packages/workbox-cacheable-response/\"},\n    {\"path\": \"./packages/workbox-cli/\"},\n    {\"path\": \"./packages/workbox-core/\"},\n    {\"path\": \"./packages/workbox-expiration/\"},\n    {\"path\": \"./packages/workbox-navigation-preload/\"},\n    {\"path\": \"./packages/workbox-precaching/\"},\n    {\"path\": \"./packages/workbox-range-requests/\"},\n    {\"path\": \"./packages/workbox-recipes/\"},\n    {\"path\": \"./packages/workbox-routing/\"},\n    {\"path\": \"./packages/workbox-strategies/\"},\n    {\"path\": \"./packages/workbox-streams/\"},\n    {\n      \"path\": \"./packages/workbox-webpack-plugin/\"\n    },\n    {\"path\": \"./packages/workbox-window/\"}\n  ]\n}\n"
  },
  {
    "path": "typescript.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  parser: '@typescript-eslint/parser',\n  parserOptions: {\n    project: ['./tsconfig.json', './packages/**/tsconfig.json'],\n    tsconfigRootDir: __dirname,\n  },\n  plugins: ['@typescript-eslint', 'jsdoc'],\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/eslint-recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:@typescript-eslint/recommended-requiring-type-checking',\n  ],\n  // Partially borrowed from https://github.com/google/gts/blob/8bd78c4c78526a72400f618a95a987d2a7c1a8db/.eslintrc.json\n  rules: {\n    '@typescript-eslint/ban-ts-ignore': 'off',\n    '@typescript-eslint/camelcase': 'off',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/interface-name-prefix': 'off',\n    '@typescript-eslint/no-empty-function': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-misused-promises': 'off',\n    '@typescript-eslint/no-non-null-assertion': 'off',\n    '@typescript-eslint/no-use-before-define': 'off',\n    '@typescript-eslint/no-var-requires': 'off',\n    '@typescript-eslint/prefer-readonly': 'error',\n    '@typescript-eslint/prefer-regexp-exec': 'off',\n    '@typescript-eslint/require-await': 'off',\n    '@typescript-eslint/unbound-method': 'off',\n    '@typescript-eslint/no-unsafe-argument': 'off',\n    'block-scoped-var': 'error',\n    'eol-last': 'error',\n    'eqeqeq': 'error',\n    'indent': 'off',\n    'no-cond-assign': 'off',\n    'no-dupe-class-members': 'off',\n    'no-var': 'error',\n    'no-warning-comments': 'off',\n    'operator-linebreak': 'off',\n    'prefer-const': 'error',\n    'prefer-spread': 'off',\n    'space-before-function-paren': 'off',\n  },\n  ignorePatterns: ['**/_version.ts', 'test/**'],\n};\n"
  }
]