[
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "content": "## Code of Conduct\n\n### Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.\n\n### Our Standards\n\nExamples of behavior that contributes to a positive environment for our community include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the overall community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or advances of any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others’ private information, such as a physical or email address, without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a professional setting\n\n### Enforcement Responsibilities\n\nProject maintainers are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.\n\n### Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.\n\n### Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported to the project team responsible for enforcement at [foss@plasmo.com](mailto:foss@plasmo.com). All complaints will be reviewed and investigated promptly and fairly.\n\nAll project maintainers are obligated to respect the privacy and security of the reporter of any incident.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n### Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1,\navailable at [https://www.contributor-covenant.org/version/2/1/code_of_conduct/][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: https://www.contributor-covenant.org/version/2/1\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing to Plasmo\n\nTo contribute to [our examples](https://github.com/PlasmoHQ/examples/), please see **[Adding examples](#adding-examples)** below.\n\n## Contributing\n\n1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it:\n\n   ```bash\n   git clone git@github.com:<org>/plasmo.git --recurse-submodules\n   ```\n\n   **NOTE:** Replace `<org>` with your GitHub username or organization.\n\n1. Work on your fork's `main` branch, then [open a PR](https://github.com/PlasmoHQ/plasmo/compare). Please ensure the PR name follows the naming convention:\n\n   `feat: some new feature`\n\n   Replacing `feat` with `fix`, `bug` or `doc` accordingly\n\n## Adding examples\n\nWhen you add an example to the [examples](https://github.com/PlasmoHQ/examples/) repository:\n\n- Use `pnpm create plasmo --exp` to create the example.\n- The name of the example should have a `with-*` prefix.\n- To add additional notes, add a `## Notes` section at the start of the generated readme.\n- Your PR should be pointed to the [examples project](https://github.com/PlasmoHQ/examples/).\n\n## Developing\n\nThe development branch is `main`, and this is the branch that all pull\nrequests should be made against.\n\nTo develop locally:\n\n1. Install [pnpm](https://pnpm.io/)\n   - DO NOT install pnpm as a global npm dependency, we need pnpm to be linked directly to your $PATH.\n   - Recommended installation method is with corepack or with brew (on macOS)\n   - If installed with brew, you might need to include the pnpm $PATH to your debugger\n2. Install the dependencies with:\n\n   ```\n   pnpm i\n   ```\n\n3. Start developing and watch for code changes:\n\n   ```\n   pnpm dev:cli\n   ```\n\n## Developing with your local version of Plasmo\n\n### As global link\n\n1. Link `plasmo` to your local registry:\n\n   ```sh\n   cd plasmo/cli/plasmo\n   pnpm link --global\n   ```\n\n2. Invoke plasmo directly:\n\n   ```sh\n   plasmo init\n   plasmo dev\n   plasmo build\n   ```\n\n3. To revert the linking later on:\n\n   ```sh\n   pnpm rm -g plasmo\n   ```\n\nNote: The `create-plasmo` CLI tool is not meant to be run locally.\nIf you have already linked it, please run\n\n```sh\npnpm -g unlink create-plasmo\n```\n\nto unlink it.\n\n## Building\n\nYou can build the project, including all type definitions, with:\n\n```bash\npnpm build\n```\n\n## Naming convention\n\n### Files and directories\n\nAny files that require attention for reading should be `UPPER_CASE`. Examples:\n\n- README.md\n- LICENSE\n- SECURITY.md\n- CONTRIBUTING.md\n\nDirectory and source file should use `kebab-case`, unless required by tooling. Examples:\n\n- cli/plasmo/src/features/extension-devtools/plasmo-extension-manifest.ts\n\n### Code\n\n| Concept              | Naming convention       |\n| -------------------- | ----------------------- |\n| Local constants      | `UPPER_CASE`            |\n| Enum namespace       | `PascalCase`            |\n| Enum values          | `PascalCase`            |\n| TS types             | `PascalCase`            |\n| TS fields            | `camelCase`             |\n| React component      | `PascalCase`            |\n| React hook           | `camelCase`             |\n| Local variable       | `camelCase`             |\n| Unused argument      | `_paddedCamelCase`      |\n| Template Placeholder | `__snake_case_padded__` |\n| Functions            | `camelCase`             |\n| API Routes           | `kebab-case`            |\n\n## For Core Maintainers / Admin\n\nPlasmo has 2 deployed environments:\n\n| env name | purpose        | requirement           |\n| -------- | -------------- | --------------------- |\n| lab      | For WIP test   | Admin deploy directly |\n| latest   | Stable release | Merge to `stable`     |\n\nReviewer approves and merges PRs to `main` branch -> deploys to `latest`\n\n> NOTE: Please make sure to use the `Squash and Merge` strategy\n\nFor `hotfix`, the workflow is:\n\n1. Creates a `hotfix-FFFF` branch off of `stable` and a PR to `stable`\n\n   ```sh\n   git checkout stable\n   git checkout -b hotfix-FFFF\n   ```\n\n   PR name: `hotfix: some quick patch`\n\n   `FFFF` is an issue number\n\n1. Admin reviews, approves and merges `hotfix-FFFF` to `main` -> deploys to `latest`\n\n### Merge strategy\n\n1. Admin review PR\n1. If the rough idea is good, code owner season the PR or guide the author to make it better\n1. Merge and deploy following the table below:\n\n| From       | To     | Strategy         | Deploy to |\n| ---------- | ------ | ---------------- | --------- |\n| `feat-*`   | `main` | Squash and Merge | latest    |\n| `hotfix-*` | `main` | Squash and Merge | latest    |\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: PlasmoHQ\n\n# patreon: # Replace with a single Patreon username\n# open_collective: # Replace with a single Open Collective username\n# ko_fi: # Replace with a single Ko-fi username\n# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\n# community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\n# liberapay: # Replace with a single Liberapay username\n# issuehunt: # Replace with a single IssueHunt username\n# otechie: # Replace with a single Otechie username\n# lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\n# custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/0.rfc.yml",
    "content": "name: ⚡ Request for Comments\ndescription: File an RFC for Feature Request/Enhancement/Refactor\ntitle: \"[RFC] \"\nlabels: [\"enhancement\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Thank you for taking the time to fill out this RFC!** 🥳\n\n  - type: textarea\n    id: description\n    attributes:\n      label: How do you envision this feature/change to look/work like?\n      description: Please be as detailed as possible, providing any relevant context.\n      placeholder: The framework should read icon-1024.png and transforms them into smaller icons...\n    validations:\n      required: true\n\n  - type: textarea\n    id: purpose\n    attributes:\n      label: What is the purpose of this change/feature? Why?\n      description: Please provide a simple summary/abstraction.\n      placeholder: |\n        The current image is not versatile enough, i.e it is too small.\n    validations:\n      required: true\n\n  - type: textarea\n    id: examples\n    attributes:\n      label: (OPTIONAL) Example implementations\n      description: If you have any examples of how this feature/change works, please list them here.\n    validations:\n      required: false\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: (OPTIONAL) Contribution\n      description: Would you be willing to create a PR to solve this issue?\n      options:\n        - label: I would like to contribute to this RFC via a PR\n          required: false\n\n  - type: checkboxes\n    attributes:\n      label: Verify canary release\n      description: \"`plasmo@canary` is the canary version of Plasmo framework that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.\"\n      options:\n        - label: I verified that the issue exists in `plasmo` canary release\n          required: true\n\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md).\n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n        - label: I checked the [current issues](https://github.com/PlasmoHQ/plasmo/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement+) for duplicate problems.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1.bug.yml",
    "content": "name: 🐛 Bug Report\ndescription: File a bug report\ntitle: \"[BUG] \"\nlabels: [\"bug\", \"triage\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Thank you for taking the time to fill out this bug report!** 🥳\n\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened?\n      description: Also tell us, what did you expect to happen?\n      placeholder: Tell us what you see!\n      value: \"A bug happened!\"\n    validations:\n      required: true\n\n  # - type: checkboxes\n  #   attributes:\n  #     label: Verify canary release\n  #     description: '`plasmo@canary` is the canary version of Plasmo framework that ships daily. It includes all features and fixes that have not been released to the stable version yet. Think of canary as a public beta. Some issues may already be fixed in the canary version, so please verify that your issue reproduces before opening a new issue.'\n  #     options:\n  #       - label: I verified that the issue exists in `plasmo` canary release\n  #         required: true\n\n  - type: dropdown\n    id: version\n    attributes:\n      label: Version\n      description: What version of the framework are you using?\n      options:\n        - Latest\n        - Canary\n    validations:\n      required: true\n\n  - type: dropdown\n    id: operating-system\n    attributes:\n      label: What OS are you seeing the problem on?\n      multiple: true\n      options:\n        - Windows\n        - MacOSX\n        - Linux\n        - Other\n\n  - type: dropdown\n    id: browsers\n    attributes:\n      label: What browsers are you seeing the problem on?\n      multiple: true\n      options:\n        - Chrome\n        - Microsoft Edge\n        - Opera\n        - Safari\n        - Firefox\n\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.\n      render: Shell\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: (OPTIONAL) Contribution\n      description: Would you be willing to create a PR to solve this issue?\n      options:\n        - label: I would like to fix this BUG via a PR\n          required: false\n\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md).\n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n        - label: I checked the [current issues](https://github.com/PlasmoHQ/plasmo/issues?q=is%3Aopen+is%3Aissue+label%3Abug) for duplicate problems.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2.example.yml",
    "content": "name: 📓 Request/Improve an Example\ndescription: Request or Improve a Plasmo Framework with-* example\ntitle: \"[EXP] \"\nlabels: [\"documentation\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        **Thank you for taking the time to fill out this example request!** 🥳\n\n  - type: textarea\n    attributes:\n      label: What is the example you wish to see?\n      description: \"Example: I would like to see more examples of how to use `X-Framework`.\"\n    validations:\n      required: true\n\n  - type: textarea\n    attributes:\n      label: Is there any context that might help us understand?\n      description: A clear description of any added context that might help us understand.\n    validations:\n      required: false\n\n  - type: checkboxes\n    id: terms\n    attributes:\n      label: Code of Conduct\n      description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md).\n      options:\n        - label: I agree to follow this project's Code of Conduct\n          required: true\n        - label: I checked the [current issues](https://github.com/PlasmoHQ/plasmo/issues) for duplicate problems.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Join our Discord server\n    url: https://www.plasmo.com/s/d\n    about: Ask questions and discuss with other community members\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--\nThanks for opening a PR! Your contribution is much appreciated.\nIn order to make sure your PR is handled as smoothly as possible we request that you follow the checklist sections below.\n-->\n\n## Details\n\nThis PR ...\n\n### Code of Conduct\n\n- [ ] I agree to follow this project's [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md)\n- [ ] I agree to license this contribution under the MIT LICENSE\n- [ ] I checked the [current PR](https://github.com/PlasmoHQ/plasmo/pulls) for duplication.\n\n## Contacts\n\n- (OPTIONAL) Discord ID:\n\nIf your PR is accepted, we will award you with the `Contributor` role on Discord server.\n\nTo join the server, visit: https://www.plasmo.com/s/d\n"
  },
  {
    "path": ".github/SECURITY.md",
    "content": "Contact: security@plasmo.com\nExpires: 2100-01-01T00:00:00.000Z\nAcknowledgments: https://www.plasmo.com/security/hall-of-fame\n"
  },
  {
    "path": ".github/workflows/test-examples.yml",
    "content": "name: Test examples\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n    types: [opened, synchronize]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [20.x]\n    steps:\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - uses: actions/checkout@master\n        with:\n          submodules: \"recursive\"\n      - name: Setup pnpm\n        uses: pnpm/action-setup@v4.1.0\n        with:\n          run_install: false\n\n      - name: Get pnpm store directory\n        id: pnpm-cache\n        shell: bash\n        run: |\n          echo \"STORE_PATH=$(pnpm store path)\" >> $GITHUB_OUTPUT\n\n      - uses: actions/cache@v4.2.3\n        name: Setup pnpm cache\n        with:\n          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: \"Build local version of Plasmo\"\n        run: |\n          pnpm i --no-frozen-lockfile\n          pnpm run build:packages\n          pnpm run build:api\n          pnpm i\n          pnpm run build:cli\n          pnpm i\n\n      - name: \"Build all examples\"\n        run: pnpm run build:examples\n\n      - name: \"Test all examples\"\n        run: pnpm run test:examples\n          \n"
  },
  {
    "path": ".github/workflows/test-package.yml",
    "content": "name: Test package manager execution\non:\n  release:\n    types: [published]\n  workflow_dispatch:\n    inputs:\n      tag:\n        description: \"Release tag to test, i.e latest, 1.0.0. Default latest\"\n        required: false\n        default: latest\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [20.x]\n        package-manager:\n          [{ name: \"pnpm\", exec: \"pnpm dlx\" }, { name: \"npm\", exec: \"npx -y\" }]\n    steps:\n      - uses: actions/setup-node@v4\n        with:\n          node-version: ${{ matrix.node-version }}\n      - name: \"Install package manager\"\n        run: npm install -g ${{ matrix.package-manager.name }}\n      - name: Check if Plasmo command works\n        run: ${{ matrix.package-manager.exec }} plasmo@${{ github.event.inputs.tag }} version\n      - name: Check if plasmo init works at all\n        run: yes \"lab\" | ${{ matrix.package-manager.exec }} plasmo@${{ github.event.inputs.tag }} init --verbose\n      - name: Check if building is possible and if it built\n        run: |\n          pushd lab\n          ${{ matrix.package-manager.name }} run build\n          pushd build\n          popd\n          timeout 10 ${{ matrix.package-manager.name }} run dev || code=$?; if [[ $code -ne 124 && $code -ne 0 ]]; then exit $code; fi\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# testing\ncoverage\n\n# next.js\n.next/\nout/\nbuild\ndist/\n\n# electron bundle\n**/resources/app\n\n# misc\n.DS_Store\n*.pem\n.idea\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# local env files\n.env*.local\n\n# turbo\n.turbo\n\n.tsbuildinfo\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"packages/rps\"]\n\tpath = packages/rps\n\turl = https://github.com/PlasmoHQ/rps.git\n\tbranch = main\n[submodule \"packages/puro\"]\n\tpath = packages/puro\n\turl = https://github.com/PlasmoHQ/puro.git\n\tbranch = main\n[submodule \"packages/gcp-refresh-token\"]\n\tpath = packages/gcp-refresh-token\n\turl = https://github.com/PlasmoHQ/gcp-refresh-token.git\n\tbranch = main\n[submodule \"packages/use-hashed-state\"]\n\tpath = packages/use-hashed-state\n\turl = https://github.com/PlasmoHQ/use-hashed-state.git\n\tbranch = main\n[submodule \"examples\"]\n\tpath = examples\n\turl = https://github.com/PlasmoHQ/examples.git\n[submodule \"packages/permission-ui\"]\n\tpath = packages/permission-ui\n\turl = https://github.com/PlasmoHQ/permission-ui.git\n[submodule \"packages/utils\"]\n\tpath = packages/utils\n\turl = https://github.com/PlasmoHQ/plasmo-utils.git\n[submodule \"packages/constants\"]\n\tpath = packages/constants\n\turl = https://github.com/PlasmoHQ/plasmo-constants.git\n[submodule \"packages/config\"]\n\tpath = packages/config\n\turl = https://github.com/PlasmoHQ/plasmo-config.git\n[submodule \"api/storage\"]\n\tpath = api/storage\n\turl = https://github.com/PlasmoHQ/storage.git\n\tbranch = main\n"
  },
  {
    "path": ".npmrc",
    "content": "save-workspace-protocol = true\nprefer-workspace-packages = true\nsave-exact = true\nlink-workspace-packages = true\nstrict-peer-dependencies = false\ngit-tag-version = false\ncommit-hooks = false"
  },
  {
    "path": ".prettierrc.mjs",
    "content": "/**\n * @type {import('prettier').Options}\n */\nexport default {\n  printWidth: 80,\n  tabWidth: 2,\n  useTabs: false,\n  semi: false,\n  singleQuote: false,\n  trailingComma: \"none\",\n  bracketSpacing: true,\n  bracketSameLine: true,\n  plugins: [\"@ianvs/prettier-plugin-sort-imports\"],\n  importOrder: [\n    \"<BUILTIN_MODULES>\", // Node.js built-in modules\n    \"<THIRD_PARTY_MODULES>\", // Imports not matched by other special words or groups.\n    \"\", // Empty line\n    \"^@plasmo/(.*)$\",\n    \"\",\n    \"^@plasmohq/(.*)$\",\n    \"\",\n    \"^@plasmo-static-common/(.*)$\",\n    \"\",\n    \"^~(.*)$\",\n    \"\",\n    \"^[./]\",\n    \"\",\n    \"__plasmo_import_module__\",\n    \"__plasmo_mount_content_script__\"\n  ]\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"esbenp.prettier-vscode\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"editor.formatOnSave\": true,\n  \"files.exclude\": {\n    \"**/.git\": true,\n    \"**/.svn\": true,\n    \"**/.hg\": true,\n    \"**/CVS\": true,\n    \"**/.DS_Store\": true,\n    \"**/Thumbs.db\": true,\n    \"**/node_modules\": true,\n    \"**/.turbo\": true,\n    \"**/.next\": true,\n    \"**/*.log\": true\n  },\n  \"[svg]\": {\n    \"editor.defaultFormatter\": \"jock.svg\"\n  },\n  \"git.detectSubmodulesLimit\": 99\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\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": "api/messaging/.gitignore",
    "content": "node_modules\n\n# Lockfiles - See https://github.com/PlasmoHQ/p1asm0\npnpm-lock.yaml\npackage-lock.json\nyarn.lock\n\n.turbo\n\nkey.json\ndist/\n"
  },
  {
    "path": "api/messaging/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\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": "api/messaging/jest.config.mjs",
    "content": "/**\n * @type {import('@jest/types').Config.InitialOptions}\n */\n\nconst config = {\n  clearMocks: true,\n  testEnvironment: \"jsdom\",\n  extensionsToTreatAsEsm: [\".ts\"],\n  transform: {\n    \"^.+.ts?$\": [\n      \"ts-jest\",\n      {\n        useESM: true,\n        isolatedModules: true\n      }\n    ]\n  },\n  testMatch: [\"**/*.test.ts\"],\n  verbose: true\n}\nexport default config\n"
  },
  {
    "path": "api/messaging/package.json",
    "content": "{\n  \"name\": \"@plasmohq/messaging\",\n  \"version\": \"0.7.2\",\n  \"description\": \"Type-safe, zero-config messaging library for modern browser extensions\",\n  \"type\": \"module\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \"./hook\": {\n      \"types\": \"./dist/hook.d.ts\",\n      \"import\": \"./dist/hook.js\",\n      \"require\": \"./dist/hook.cjs\"\n    },\n    \"./relay\": {\n      \"types\": \"./dist/relay.d.ts\",\n      \"import\": \"./dist/relay.js\",\n      \"require\": \"./dist/relay.cjs\"\n    },\n    \"./port\": {\n      \"types\": \"./dist/port.d.ts\",\n      \"import\": \"./dist/port.js\",\n      \"require\": \"./dist/port.cjs\"\n    },\n    \"./pub-sub\": {\n      \"types\": \"./dist/pub-sub.d.ts\",\n      \"import\": \"./dist/pub-sub.js\",\n      \"require\": \"./dist/pub-sub.cjs\"\n    },\n    \"./background\": {\n      \"types\": \"./dist/background.d.ts\",\n      \"import\": \"./dist/background.js\",\n      \"require\": \"./dist/background.cjs\"\n    },\n    \"./message\": {\n      \"types\": \"./dist/message.d.ts\",\n      \"import\": \"./dist/message.js\",\n      \"require\": \"./dist/message.cjs\"\n    },\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \"message\": [\n        \"./dist/message.d.ts\"\n      ],\n      \"hook\": [\n        \"./dist/hook.d.ts\"\n      ],\n      \"relay\": [\n        \"./dist/relay.d.ts\"\n      ],\n      \"port\": [\n        \"./dist/port.d.ts\"\n      ],\n      \"pub-sub\": [\n        \"./dist/pub-sub.d.ts\"\n      ],\n      \"background\": [\n        \"./dist/background.d.ts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"tsup\": {\n    \"entry\": [\n      \"src/index.ts\",\n      \"src/port.ts\",\n      \"src/pub-sub.ts\",\n      \"src/relay.ts\",\n      \"src/message.ts\",\n      \"src/background.ts\",\n      \"src/hook.ts\"\n    ],\n    \"format\": [\n      \"esm\",\n      \"cjs\"\n    ],\n    \"target\": \"esnext\",\n    \"platform\": \"node\",\n    \"splitting\": false,\n    \"bundle\": true\n  },\n  \"scripts\": {\n    \"dev\": \"run-p dev:*\",\n    \"dev:compile\": \"tsup --watch --sourcemap --dts-resolve\",\n    \"dev:test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch\",\n    \"build\": \"tsup --dts-resolve --minify --clean\",\n    \"test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest\",\n    \"prepublishOnly\": \"pnpm build\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"contributors\": [\n    \"@louisgv\",\n    \"@ColdSauce\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\",\n    \"directory\": \"api/messaging\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"react-hook\",\n    \"browser-extension\",\n    \"chrome-extension\"\n  ],\n  \"peerDependencies\": {\n    \"react\": \"^16.8.6 || ^17 || ^18 || ^19.0.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"react\": {\n      \"optional\": true\n    }\n  },\n  \"devDependencies\": {\n    \"@jest/globals\": \"29.7.0\",\n    \"@jest/types\": \"29.6.3\",\n    \"@testing-library/react\": \"16.2.0\",\n    \"@testing-library/dom\": \"10.4.0\",\n    \"@types/chrome\": \"0.0.312\",\n    \"@types/node\": \"22.13.13\",\n    \"@types/react\": \"19.0.12\",\n    \"canvas\": \"3.1.0\",\n    \"cross-env\": \"7.0.3\",\n    \"jest\": \"29.7.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"react\": \"19.0.0\",\n    \"react-dom\": \"19.0.0\",\n    \"rimraf\": \"6.0.1\",\n    \"ts-jest\": \"29.3.0\",\n    \"tsup\": \"8.4.0\",\n    \"typescript\": \"5.8.2\"\n  },\n  \"dependencies\": {\n    \"nanoid\": \"5.1.5\"\n  }\n}\n"
  },
  {
    "path": "api/messaging/src/background.ts",
    "content": "import type { PlasmoMessaging, PortName } from \"./index\"\nimport { getExtRuntime } from \"./utils\"\n\nexport const getPortMap = (): Map<PortName, chrome.runtime.Port> =>\n  globalThis.__plasmoInternalPortMap\n\nexport const getPort = (name: PortName): chrome.runtime.Port => {\n  const portMap = getPortMap()\n  const port = portMap.get(name)\n  if (!port) {\n    throw new Error(`Port ${name} not found`)\n  }\n  return port\n}\n\ngetExtRuntime().onMessage.addListener(\n  (request: PlasmoMessaging.InternalRequest, _sender, sendResponse) => {\n    switch (request.__PLASMO_INTERNAL_SIGNAL__) {\n      case \"__PLASMO_MESSAGING_PING__\": {\n        sendResponse(true)\n        break\n      }\n    }\n\n    return true\n  }\n)\n"
  },
  {
    "path": "api/messaging/src/hook.ts",
    "content": "import { useEffect, useRef, useState } from \"react\"\n\nimport { relayMessage, type MessageName, type PlasmoMessaging } from \"./index\"\nimport { listen as messageListen } from \"./message\"\nimport { listen as portListen } from \"./port\"\nimport { relay } from \"./relay\"\n\n/**\n * Used in any extension context to listen and send messages to background.\n */\nexport const useMessage = <RequestBody, ResponseBody>(\n  handler: PlasmoMessaging.Handler<string, RequestBody, ResponseBody>\n) => {\n  const [data, setData] = useState<RequestBody>()\n\n  useEffect(\n    () =>\n      messageListen<RequestBody, ResponseBody>(async (req, res) => {\n        setData(req.body)\n        await handler(req, res)\n      }),\n    [handler]\n  )\n\n  return {\n    data\n  }\n}\n\nexport const usePort: PlasmoMessaging.PortHook = (name) => {\n  const portRef = useRef<chrome.runtime.Port>(undefined)\n  const reconnectRef = useRef(0)\n  const [data, setData] = useState()\n\n  useEffect(() => {\n    if (!name) {\n      return null\n    }\n\n    const { port, disconnect } = portListen(\n      name,\n      (msg) => {\n        setData(msg)\n      },\n      () => {\n        reconnectRef.current = reconnectRef.current + 1\n      }\n    )\n\n    portRef.current = port\n    return disconnect\n  }, [\n    name,\n    reconnectRef.current // This is needed to force a new port ref\n  ])\n\n  return {\n    data,\n    send: (body) => {\n      portRef.current.postMessage({\n        name,\n        body\n      })\n    },\n    listen: (handler) => portListen(name, handler)\n  }\n}\n\n/**\n * TODO: Perhaps add a way to detect if this hook is being used inside CS?\n */\nexport function useMessageRelay<RequestBody = any>(\n  req: PlasmoMessaging.Request<MessageName, RequestBody>\n) {\n  useEffect(() => relayMessage(req), [])\n}\n\nexport const useRelay: PlasmoMessaging.RelayFx = (req, onMessage) => {\n  const relayRef = useRef<() => void>(undefined)\n\n  useEffect(() => {\n    relayRef.current = relay(req, onMessage)\n    return relayRef.current\n  }, [])\n\n  return () => relayRef.current?.()\n}\n"
  },
  {
    "path": "api/messaging/src/index.ts",
    "content": "import { relay as rawRelay, sendViaRelay as rawSendViaRelay } from \"./relay\"\nimport type { MessageName, PlasmoMessaging } from \"./types\"\nimport { getActiveTab, getExtRuntime, getExtTabs } from \"./utils\"\n\nexport type {\n  PlasmoMessaging,\n  MessageName,\n  PortName,\n  PortsMetadata,\n  MessagesMetadata,\n  OriginContext\n} from \"./types\"\n\n/**\n * Send to Background Service Workers from Content Scripts or Extension pages.\n * `extensionId` is required to send a message from a Content Script in the main world\n */\n// TODO: Add a framework runtime check, using a global variable\nexport const sendToBackground: PlasmoMessaging.SendFx<MessageName> = async (\n  req\n) => {\n  return getExtRuntime().sendMessage(req.extensionId ?? null, req)\n}\n\n/**\n * Send to Content Scripts from Extension pages or Background Service Workers.\n * Default to active tab if no tabId is provided in the request\n */\nexport const sendToContentScript: PlasmoMessaging.SendFx = async (req) => {\n  const tabId =\n    typeof req.tabId === \"number\" ? req.tabId : (await getActiveTab())?.id\n\n  if (!tabId) {\n    throw new Error(\"No active tab found to send message to.\")\n  }\n\n  return getExtTabs().sendMessage(tabId, req)\n}\n\n/**\n * @deprecated Renamed to `sendToContentScript`\n */\n\nexport const sendToActiveContentScript = sendToContentScript\n\n/**\n * Any request sent to this relay get send to background, then emitted back as a response\n */\nexport const relayMessage: PlasmoMessaging.MessageRelayFx = (req) =>\n  rawRelay(req, sendToBackground)\n\n/**\n * @deprecated Migrated to `relayMessage`\n */\nexport const relay = relayMessage\n\nexport const sendToBackgroundViaRelay: PlasmoMessaging.SendFx<MessageName> =\n  rawSendViaRelay\n\n/**\n * @deprecated Migrated to `sendToBackgroundViaRelay`\n */\nexport const sendViaRelay = sendToBackgroundViaRelay\n"
  },
  {
    "path": "api/messaging/src/message.ts",
    "content": "import { type PlasmoMessaging } from \"./index\"\nimport { getExtRuntime } from \"./utils\"\n\nexport const listen = <RequestBody, ResponseBody>(\n  handler: PlasmoMessaging.Handler<string, RequestBody, ResponseBody>\n) => {\n  const metaListener = async (\n    req: any,\n    sender: chrome.runtime.MessageSender,\n    sendResponse: (response?: ResponseBody) => void\n  ) => {\n    await handler?.(\n      {\n        ...req,\n        sender\n      },\n      {\n        send: (p) => sendResponse(p)\n      }\n    )\n  }\n\n  const listener = (\n    req: any,\n    sender: chrome.runtime.MessageSender,\n    sendResponse: (response?: ResponseBody) => void\n  ) => {\n    metaListener(req, sender, sendResponse)\n    return true // Synchronous return to indicate this is an async listener\n  }\n\n  getExtRuntime().onMessage.addListener(listener)\n  return () => {\n    getExtRuntime().onMessage.removeListener(listener)\n  }\n}\n"
  },
  {
    "path": "api/messaging/src/port.ts",
    "content": "import type { PortName } from \"./index\"\nimport { getExtRuntime } from \"./utils\"\n\nconst portMap = new Map<PortName, chrome.runtime.Port>()\n\nexport const getPort = (name: PortName) => {\n  const port = portMap.get(name)\n  if (!!port) {\n    return port\n  }\n  const newPort = getExtRuntime().connect({ name })\n  portMap.set(name, newPort)\n  return newPort\n}\n\nexport const removePort = (name: PortName) => {\n  portMap.delete(name)\n}\n\nexport const listen = <ResponseBody = any>(\n  name: PortName,\n  handler: (msg: ResponseBody) => Promise<void> | void,\n  onReconnect?: () => void\n) => {\n  const port = getPort(name)\n\n  function reconnectHandler() {\n    removePort(name)\n    onReconnect?.()\n  }\n\n  port.onMessage.addListener(handler)\n  port.onDisconnect.addListener(reconnectHandler)\n\n  return {\n    port,\n    disconnect: () => {\n      port.onMessage.removeListener(handler)\n      port.onDisconnect.removeListener(reconnectHandler)\n    }\n  }\n}\n"
  },
  {
    "path": "api/messaging/src/pub-sub.ts",
    "content": "import { getExtRuntime } from \"./utils\"\n\nexport type PubSubMessage = {\n  from?: number\n  to?: number\n  payload: any\n}\n\n// Only usable from BGSW\nexport const getHubMap = (): Map<number, chrome.runtime.Port> =>\n  globalThis.__plasmoInternalHubMap\n\n// Only usable by BGSW\nexport const startHub = () => {\n  const runtime = getExtRuntime()\n  if (!runtime.onConnectExternal) {\n    throw new Error(\n      \"onConnect External not available. You need externally_connectable entry possibly\"\n    )\n  }\n\n  globalThis.__plasmoInternalHubMap = new Map()\n  const hub = getHubMap()\n\n  runtime.onConnectExternal.addListener((port) => {\n    const tabId = port.sender.tab.id\n    if (!hub.has(tabId)) {\n      hub.set(tabId, port)\n      port.onMessage.addListener((message) => {\n        broadcast({ from: tabId, payload: message })\n      })\n      port.onDisconnect.addListener(() => {\n        //TODO - Should we log?\n        hub.delete(tabId)\n      })\n    }\n  })\n}\n\n// Only usable by BGSW\nexport const broadcast = (pubSubMessage: PubSubMessage) => {\n  const hub = getHubMap()\n  hub.forEach((port, tabId) => {\n    const skipBroadcast = tabId === pubSubMessage.from\n    if (skipBroadcast) {\n      return\n    }\n    port.postMessage({ ...pubSubMessage, to: tabId })\n  })\n}\n\nexport const connectToHub = (extensionId: string) => {\n  const runtime = getExtRuntime()\n  if (!runtime.connect) {\n    throw new Error(\n      \"runtime.connect not available. You need to use startHub in BGSW\"\n    )\n  }\n  const port = runtime.connect(extensionId)\n  return port\n}\n"
  },
  {
    "path": "api/messaging/src/relay.test.ts",
    "content": "import { beforeEach, describe, expect, jest, test } from \"@jest/globals\"\n\nimport type { PlasmoMessaging } from \"./types\"\n\nconst { relay, sendViaRelay } = await import(\"./relay\")\n\nclass MessagePortMock {\n  callbacks = new Set<Function>()\n  addEventListener = (_message, callback) => {\n    this.callbacks.add(callback)\n  }\n  removeEventListener = (_message, callback) => {\n    this.callbacks.delete(callback)\n  }\n  postMessage = (data) => {\n    const event = {\n      data,\n      source: globalThis.window\n    }\n\n    this.callbacks.forEach((callback) => {\n      callback(event)\n    })\n  }\n  clear() {\n    this.callbacks.clear()\n  }\n}\n\n/**\n * Message port callbacks happen synchronously\n * But promises get resolved in event queue\n */\nconst waitForMicroTasks = () => Promise.resolve()\n\ndescribe(\"sendViaRelay\", () => {\n  const port = new MessagePortMock()\n  const req: PlasmoMessaging.Request = {\n    name: \"test\",\n    body: { foo: \"bar\" },\n    relayId: \"1\"\n  }\n\n  beforeEach(() => {\n    port.clear()\n  })\n\n  test(\"posts message to provided message port\", (done) => {\n    port.addEventListener(\"message\", (event) => {\n      expect(event.data).toMatchObject(req)\n      done()\n    })\n\n    sendViaRelay(req, port)\n    expect(port.callbacks.size).toBe(2)\n  })\n\n  test(\"appends random instanceId to relayed request\", (done) => {\n    port.addEventListener(\"message\", (event) => {\n      expect(Object.hasOwn(event.data, \"instanceId\")).toBeTruthy()\n      done()\n    })\n\n    sendViaRelay(req, port)\n  })\n\n  test(\"only resolves body with matching instanceId\", (done) => {\n    let response = null\n\n    port.addEventListener(\"message\", async (event) => {\n      if (event.data.relayed) {\n        return\n      }\n\n      port.postMessage({\n        ...event.data,\n        body: { bar: \"foo\" },\n        relayed: true,\n        instanceId: \"123\"\n      })\n      await waitForMicroTasks()\n\n      expect(response).toEqual(null)\n\n      port.postMessage({\n        ...event.data,\n        body: { bar: \"foo\" },\n        relayed: true\n      })\n      await waitForMicroTasks()\n\n      expect(response).toEqual({ bar: \"foo\" })\n\n      done()\n    })\n\n    sendViaRelay(req, port).then((res) => (response = res))\n  })\n})\n\ndescribe(\"relay\", () => {\n  const port = new MessagePortMock()\n  const req: PlasmoMessaging.Request = {\n    name: \"test\",\n    relayId: \"1\"\n  }\n  const handler = (data) => {\n    return Promise.resolve({ echo: data.body })\n  }\n\n  beforeEach(() => {\n    port.clear()\n  })\n\n  test(\"returns cleanup function\", () => {\n    const cleanup = relay(req, handler, port)\n\n    expect(port.callbacks.size).toBe(1)\n\n    cleanup()\n\n    expect(port.callbacks.size).toBe(0)\n  })\n\n  test(\"does not handle relayed messages\", () => {\n    const notCalledHandler = jest.fn((data) => Promise.resolve(data))\n    relay(req, notCalledHandler, port)\n\n    port.postMessage({ ...req, relayed: true })\n\n    expect(notCalledHandler).not.toBeCalled()\n  })\n\n  test(\"posts back resolution result and instanceId\", (done) => {\n    relay(req, handler, port)\n\n    const mockedReq = { ...req, instanceId: \"123\", body: { foo: \"bar\" } }\n\n    port.addEventListener(\"message\", async (event) => {\n      if (!event.data.relayed) {\n        return\n      }\n\n      expect(event.data.body).toEqual({ echo: mockedReq.body })\n      expect(event.data.instanceId).toEqual(mockedReq.instanceId)\n\n      done()\n    })\n\n    port.postMessage(mockedReq)\n  })\n})\n"
  },
  {
    "path": "api/messaging/src/relay.ts",
    "content": "import { nanoid } from \"nanoid\"\n\nimport type { PlasmoMessaging } from \"./index\"\nimport { isSameOrigin } from \"./utils\"\n\n/**\n * Raw relay abstracting window.postMessage\n */\nexport const relay: PlasmoMessaging.RelayFx = (\n  req,\n  onMessage,\n  messagePort = globalThis.window\n) => {\n  const relayHandler = async (\n    event: MessageEvent<PlasmoMessaging.RelayMessage>\n  ) => {\n    if (isSameOrigin(event, req) && !event.data.relayed) {\n      const relayPayload = {\n        name: req.name,\n        relayId: req.relayId,\n        body: event.data.body\n      }\n\n      const backgroundResponse = await onMessage?.(relayPayload)\n\n      messagePort.postMessage(\n        {\n          name: req.name,\n          relayId: req.relayId,\n          instanceId: event.data.instanceId,\n          body: backgroundResponse,\n          relayed: true\n        },\n        {\n          targetOrigin: req.targetOrigin || \"/\"\n        }\n      )\n    }\n  }\n\n  messagePort.addEventListener(\"message\", relayHandler)\n  return () => messagePort.removeEventListener(\"message\", relayHandler)\n}\n\nexport const sendViaRelay: PlasmoMessaging.SendFx = (\n  req,\n  messagePort = globalThis.window\n) =>\n  new Promise((resolve, _reject) => {\n    const instanceId = nanoid()\n    const abortController = new AbortController()\n    messagePort.addEventListener(\n      \"message\",\n      (event: MessageEvent<PlasmoMessaging.RelayMessage>) => {\n        if (\n          isSameOrigin(event, req) &&\n          event.data.relayed &&\n          event.data.instanceId === instanceId\n        ) {\n          resolve(event.data.body)\n          abortController.abort()\n        }\n      },\n      {\n        signal: abortController.signal\n      }\n    )\n\n    messagePort.postMessage(\n      {\n        ...req,\n        instanceId\n      } as PlasmoMessaging.RelayMessage,\n      {\n        targetOrigin: req.targetOrigin || \"/\"\n      }\n    )\n  })\n"
  },
  {
    "path": "api/messaging/src/types.ts",
    "content": "export interface MessagesMetadata {}\nexport interface PortsMetadata {}\n\nexport type MessageName = keyof MessagesMetadata\nexport type PortName = keyof PortsMetadata\n\nexport type InternalSignal = \"__PLASMO_MESSAGING_PING__\"\n\nexport namespace PlasmoMessaging {\n  export type Request<TName = any, TBody = any> = {\n    name: TName\n\n    extensionId?: string\n    port?: chrome.runtime.Port\n    sender?: chrome.runtime.MessageSender\n\n    body?: TBody\n    tabId?: number\n    relayId?: string\n\n    // Target origin to send the message to (for relay), default to \"/\"\n    targetOrigin?: string\n  }\n\n  export type RelayMessage<TName = any, TBody = any> = Request<TName, TBody> & {\n    /**\n     * Used to resolve corresponding window.postMessage messages\n     */\n    instanceId: string\n    relayed: boolean\n  }\n\n  export type InternalRequest = {\n    __PLASMO_INTERNAL_SIGNAL__: InternalSignal\n  }\n\n  export type Response<TBody = any> = {\n    send: (body: TBody) => void\n  }\n\n  export type InternalHandler = (request: InternalRequest) => void\n\n  export type Handler<\n    RequestName = string,\n    RequestBody = any,\n    ResponseBody = any\n  > = (\n    request: Request<RequestName, RequestBody>,\n    response: Response<ResponseBody>\n  ) => void | Promise<void> | boolean\n\n  export type PortHandler<RequestBody = any, ResponseBody = any> = Handler<\n    PortName,\n    RequestBody,\n    ResponseBody\n  >\n\n  export type MessageHandler<RequestBody = any, ResponseBody = any> = Handler<\n    MessageName,\n    RequestBody,\n    ResponseBody\n  >\n\n  export interface SendFx<TName = string> {\n    <RequestBody = any, ResponseBody = any>(\n      request: Request<TName, RequestBody>,\n      messagePort?:\n        | Pick<\n            MessagePort,\n            \"addEventListener\" | \"removeEventListener\" | \"postMessage\"\n          >\n        | Window\n    ): Promise<ResponseBody>\n  }\n\n  export interface RelayFx {\n    <RelayName = any, RequestBody = any, ResponseBody = any>(\n      request: Request<RelayName, RequestBody>,\n      onMessage?: (\n        request: Request<RelayName, RequestBody>\n      ) => Promise<ResponseBody>,\n      messagePort?:\n        | Pick<\n            MessagePort,\n            \"addEventListener\" | \"removeEventListener\" | \"postMessage\"\n          >\n        | Window\n    ): () => void\n  }\n\n  export interface MessageRelayFx {\n    <RequestBody = any>(request: Request<MessageName, RequestBody>): () => void\n  }\n\n  export interface PortHook {\n    <TRequestBody = Record<string, any>, TResponseBody = any>(\n      name: PortName\n    ): {\n      data?: TResponseBody\n      send: (payload: TRequestBody) => void\n      listen: <T = TResponseBody>(\n        handler: (msg: T) => void\n      ) => {\n        port: chrome.runtime.Port\n        disconnect: () => void\n      }\n    }\n  }\n}\n\nexport type OriginContext =\n  | \"background\"\n  | \"extension-page\"\n  | \"sandbox-page\"\n  | \"content-script\"\n  | \"window\"\n"
  },
  {
    "path": "api/messaging/src/utils.ts",
    "content": "import type { PlasmoMessaging } from \"./index\"\n\nconst extTabs = (globalThis.browser?.tabs ||\n  globalThis.chrome?.tabs) as typeof chrome.tabs\n\nexport const getExtRuntime = () => {\n  // TODO: Move this to a broader utils package later on\n  const extRuntime = (globalThis.browser?.runtime ||\n    globalThis.chrome?.runtime) as typeof chrome.runtime\n  \n  if (!extRuntime) {\n    throw new Error(\"Extension runtime is not available\")\n  }\n  return extRuntime\n}\n\nexport const getExtTabs = () => {\n  if (!extTabs) {\n    throw new Error(\"Extension tabs API is not available\")\n  }\n  return extTabs\n}\n\nexport const getActiveTab = async () => {\n  const extTabs = getExtTabs()\n  const [tab] = await extTabs.query({\n    active: true,\n    currentWindow: true\n  })\n  return tab as chrome.tabs.Tab | undefined\n}\n\nexport const isSameOrigin = (\n  event: MessageEvent,\n  req: any\n): req is PlasmoMessaging.Request =>\n  !req.__internal &&\n  event.source === globalThis.window &&\n  event.data.name === req.name &&\n  (req.relayId === undefined || event.data.relayId === req.relayId)\n\nexport const getRuntimeContext = () => {\n  // If chrome API available but they cannot access\n  // OR we can mark them directly (?), by injecting a tag at runtime itself\n}\n"
  },
  {
    "path": "api/messaging/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"resolveJsonModule\": true,\n    \"sourceMap\": true,\n    \"strict\": false,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": false,\n    \"moduleResolution\": \"node\",\n\n    \"module\": \"ESNext\",\n    \"target\": \"ESNext\",\n    \"allowJs\": true,\n    \"verbatimModuleSyntax\": true\n  },\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "api/persistent/.gitignore",
    "content": "node_modules\n\n# Lockfiles - See https://github.com/PlasmoHQ/p1asm0\npnpm-lock.yaml\npackage-lock.json\nyarn.lock\n\n.turbo\n\nkey.json\ndist/\n"
  },
  {
    "path": "api/persistent/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\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": "api/persistent/README.md",
    "content": "# Plasmo Persistent runtime\n\nThis library contains a couple of hacks to keep the BGSW alive for MV3 transitioning.\n\nUsage in a background service worker:\n\n```ts\nimport { keepAlive } from \"@plasmohq/persistent/background\"\n\nkeepAlive()\n```\n"
  },
  {
    "path": "api/persistent/package.json",
    "content": "{\n  \"name\": \"@plasmohq/persistent\",\n  \"version\": \"0.0.6\",\n  \"description\": \"A couple of hacks to keep the BGSW alive in a library\",\n  \"type\": \"module\",\n  \"module\": \"./src/index.ts\",\n  \"types\": \"./src/index.ts\",\n  \"typesVersions\": {\n    \"*\": {\n      \"background\": [\n        \"./src/background.ts\"\n      ]\n    }\n  },\n  \"publishConfig\": {\n    \"module\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\",\n    \"typesVersions\": {\n      \"*\": {\n        \"background\": [\n          \"./dist/background.d.ts\"\n        ]\n      }\n    }\n  },\n  \"exports\": {\n    \"./background\": {\n      \"types\": \"./dist/background.d.ts\",\n      \"import\": \"./dist/background.js\",\n      \"require\": \"./dist/background.cjs\"\n    },\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"dev\": \"run-p dev:*\",\n    \"dev:compile\": \"tsup --watch\",\n    \"dev:test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch\",\n    \"build\": \"tsup\",\n    \"test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest\",\n    \"prepublishOnly\": \"pnpm build\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"contributors\": [\n    \"@louisgv\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\",\n    \"directory\": \"api/persistent\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"browser-extension\",\n    \"chrome-extension\"\n  ],\n  \"devDependencies\": {\n    \"@jest/globals\": \"29.7.0\",\n    \"@jest/types\": \"29.6.3\",\n    \"@plasmo/config\": \"workspace:*\",\n    \"@types/chrome\": \"0.0.312\",\n    \"@types/node\": \"22.13.13\",\n    \"cross-env\": \"7.0.3\",\n    \"jest\": \"29.7.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"ts-jest\": \"29.3.0\",\n    \"tsup\": \"8.4.0\",\n    \"typescript\": \"5.8.2\"\n  }\n}\n"
  },
  {
    "path": "api/persistent/src/background.ts",
    "content": "export const keepAlive = () => {\n  const extRuntime = (globalThis.browser?.runtime ||\n    globalThis.chrome?.runtime) as typeof chrome.runtime\n  const _keepAlive = () => setInterval(extRuntime.getPlatformInfo, 24_000) // 24 seconds\n  extRuntime.onStartup.addListener(_keepAlive)\n  _keepAlive()\n}\n"
  },
  {
    "path": "api/persistent/src/index.ts",
    "content": "export const life = 42\n"
  },
  {
    "path": "api/persistent/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/utils\",\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "api/persistent/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig((opt) => {\n  const isProd = !opt.watch\n  return {\n    entry: [\"src/index.ts\", \"src/background.ts\"],\n\n    format: [\"esm\", \"cjs\"],\n\n    target: \"esnext\",\n    platform: \"node\",\n    splitting: false,\n    bundle: true,\n    dts: true,\n\n    watch: opt.watch,\n    sourcemap: !isProd,\n    minify: isProd,\n    clean: isProd\n  }\n})\n"
  },
  {
    "path": "api/selector/.gitignore",
    "content": "node_modules\n\n# Lockfiles - See https://github.com/PlasmoHQ/p1asm0\npnpm-lock.yaml\npackage-lock.json\nyarn.lock\n\n.turbo\n\nkey.json\ndist/\n"
  },
  {
    "path": "api/selector/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\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": "api/selector/package.json",
    "content": "{\n  \"name\": \"@plasmohq/selector\",\n  \"version\": \"0.0.7\",\n  \"description\": \"Powerful Selector API with Dedicated Monitoring Supports\",\n  \"type\": \"module\",\n  \"module\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \"./hook\": {\n      \"types\": \"./dist/hook.d.ts\",\n      \"import\": \"./dist/hook.js\",\n      \"require\": \"./dist/hook.cjs\"\n    },\n    \"./monitor\": {\n      \"types\": \"./dist/monitor.d.ts\",\n      \"import\": \"./dist/monitor.js\",\n      \"require\": \"./dist/monitor.cjs\"\n    },\n    \"./background\": {\n      \"types\": \"./dist/background.d.ts\",\n      \"import\": \"./dist/background.js\",\n      \"require\": \"./dist/background.cjs\"\n    },\n    \".\": {\n      \"types\": \"./dist/index.d.ts\",\n      \"import\": \"./dist/index.js\",\n      \"require\": \"./dist/index.cjs\"\n    }\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \"hook\": [\n        \"./dist/hook.d.ts\"\n      ],\n      \"monitor\": [\n        \"./dist/monitor.d.ts\"\n      ],\n      \"background\": [\n        \"./dist/background.d.ts\"\n      ]\n    }\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"scripts\": {\n    \"dev\": \"run-p dev:*\",\n    \"dev:compile\": \"tsup --watch\",\n    \"dev:test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest --watch\",\n    \"build\": \"tsup\",\n    \"test\": \"cross-env NODE_OPTIONS=--experimental-vm-modules jest\",\n    \"prepublishOnly\": \"pnpm build\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"contributors\": [\n    \"@louisgv\",\n    \"@ColdSauce\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\",\n    \"directory\": \"api/selector\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"react-hook\",\n    \"browser-extension\",\n    \"chrome-extension\"\n  ],\n  \"peerDependencies\": {\n    \"react\": \"^16.8.6 || ^17 || ^18 || ^19.0.0\"\n  },\n  \"peerDependenciesMeta\": {\n    \"react\": {\n      \"optional\": true\n    }\n  },\n  \"devDependencies\": {\n    \"@jest/globals\": \"29.7.0\",\n    \"@jest/types\": \"29.6.3\",\n    \"@testing-library/react\": \"16.2.0\",\n    \"@types/chrome\": \"0.0.312\",\n    \"@types/node\": \"22.13.13\",\n    \"@types/react\": \"19.0.12\",\n    \"cross-env\": \"7.0.3\",\n    \"jest\": \"29.7.0\",\n    \"jest-environment-jsdom\": \"29.7.0\",\n    \"react\": \"19.0.0\",\n    \"react-dom\": \"19.0.0\",\n    \"rimraf\": \"6.0.1\",\n    \"ts-jest\": \"29.3.0\",\n    \"tsup\": \"8.4.0\",\n    \"typescript\": \"5.8.2\"\n  }\n}\n"
  },
  {
    "path": "api/selector/src/background.ts",
    "content": "import type { SelectorMessage } from \"./types\"\n\n// Simple cache, it won't persist, but it will do for now\nconst softCache = new Set()\n\nasync function selectorMessageHandler(\n  message: SelectorMessage,\n  monitorId: string,\n  sample: number\n) {\n  switch (message.name) {\n    case \"plasmo:selector:invalid\": {\n      if (!monitorId) {\n        return\n      }\n\n      const body = JSON.stringify({\n        monitorId,\n        payload: message.payload\n      })\n\n      if (softCache.has(body) || Math.random() > sample) {\n        return\n      }\n\n      try {\n        softCache.add(body)\n        await fetch(\n          `${process.env.ITERO_MONITOR_API_BASE_URI}/api/selector/invalid`,\n          {\n            method: \"POST\",\n            headers: {\n              \"Content-Type\": \"application/json\"\n            },\n            body\n          }\n        )\n      } catch {}\n    }\n  }\n}\n\n/**\n * @param monitorId id of the monitor to send invalid selectors to\n * @param sample percentage of invalid selectors to send to the monitor, default 47%\n */\nexport const init = ({ monitorId = \"\", sample = 0.47 }) => {\n  chrome.runtime.onMessage.addListener((message: SelectorMessage) => {\n    selectorMessageHandler(message, monitorId, sample)\n    return true\n  })\n}\n"
  },
  {
    "path": "api/selector/src/hook.ts",
    "content": "export const useSelector = () => {}\n"
  },
  {
    "path": "api/selector/src/index.ts",
    "content": "import type { SelectorMessage } from \"./types\"\n\nasync function sendInvalidSelectors(selectors: string[]) {\n  try {\n    return await chrome?.runtime?.sendMessage({\n      name: \"plasmo:selector:invalid\",\n      payload: {\n        selectors,\n        url: window.location.href\n      }\n    } as SelectorMessage)\n  } catch {}\n}\n\nexport const querySelectors = (selectors: string[]) => {\n  const result: Element[] = []\n  const invalidSelectors: string[] = []\n  for (const selector of selectors) {\n    const element = document.querySelector(selector)\n    if (!element) {\n      invalidSelectors.push(selector)\n    } else {\n      result.push(element)\n    }\n  }\n\n  if (invalidSelectors.length > 0) {\n    sendInvalidSelectors(invalidSelectors)\n  }\n\n  return result\n}\n\nexport const querySelector = (selector: string) => {\n  const element = document.querySelector(selector)\n  if (!element) {\n    sendInvalidSelectors([selector])\n  }\n\n  return element\n}\n\nexport const querySelectorAll = (selector: string) => {\n  const elements = document.querySelectorAll(selector)\n\n  if (elements.length === 0) {\n    sendInvalidSelectors([selector])\n  }\n\n  return elements\n}\n"
  },
  {
    "path": "api/selector/src/monitor.ts",
    "content": "export {}\n\ndocument.querySelector = new Proxy(document.querySelector, {\n  apply: (target, thisArg, args) => {}\n})\n"
  },
  {
    "path": "api/selector/src/types.ts",
    "content": "export type SelectorMessage = {\n  name: \"plasmo:selector:invalid\"\n  payload: {\n    selectors: string[]\n    url: string\n  }\n}\n"
  },
  {
    "path": "api/selector/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"resolveJsonModule\": true,\n    \"sourceMap\": true,\n    \"strict\": false,\n    \"declaration\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noImplicitReturns\": true,\n    \"esModuleInterop\": true,\n    \"noImplicitAny\": false,\n    \"moduleResolution\": \"node\",\n\n    \"module\": \"ESNext\",\n    \"target\": \"ESNext\",\n    \"allowJs\": true,\n    \"verbatimModuleSyntax\": true,\n    \"lib\": [\"DOM\"]\n  },\n  \"include\": [\"src/**/*.ts\"]\n}\n"
  },
  {
    "path": "api/selector/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig((opt) => {\n  const isProd = !opt.watch\n  return {\n    entry: [\n      \"src/index.ts\",\n      \"src/monitor.ts\",\n      \"src/background.ts\",\n      \"src/hook.ts\"\n    ],\n\n    format: [\"esm\", \"cjs\"],\n\n    target: \"esnext\",\n    platform: \"node\",\n    splitting: false,\n    bundle: true,\n    dts: true,\n\n    env: {\n      ITERO_MONITOR_API_BASE_URI: isProd\n        ? \"https://itero.plasmo.com\"\n        : \"http://localhost:3000\"\n    },\n\n    watch: opt.watch,\n    sourcemap: !isProd,\n    minify: isProd,\n    clean: isProd\n  }\n})\n"
  },
  {
    "path": "cli/create-plasmo/bin/index.mjs",
    "content": "#!/usr/bin/env node\n\nimport \"../dist/index.js\"\n"
  },
  {
    "path": "cli/create-plasmo/package.json",
    "content": "{\n  \"name\": \"create-plasmo\",\n  \"version\": \"0.90.5\",\n  \"description\": \"Create Plasmo Framework Browser Extension\",\n  \"main\": \"dist/index.js\",\n  \"bin\": \"bin/index.mjs\",\n  \"type\": \"module\",\n  \"files\": [\n    \"bin\",\n    \"dist\"\n  ],\n  \"tsup\": {\n    \"format\": \"esm\",\n    \"target\": \"esnext\",\n    \"platform\": \"node\",\n    \"splitting\": false,\n    \"bundle\": true,\n    \"minify\": true,\n    \"clean\": true,\n    \"banner\": {\n      \"js\": \"import { createRequire } from 'module';const require = createRequire(import.meta.url);\"\n    }\n  },\n  \"scripts\": {\n    \"build\": \"tsup src/index.ts\",\n    \"prepublishOnly\": \"run-s build\"\n  },\n  \"author\": \"Plasmo Corp. <support@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\",\n    \"directory\": \"cli/create-plasmo\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"plasmo\",\n    \"browser-extensions\",\n    \"framework\"\n  ],\n  \"dependencies\": {\n    \"@plasmohq/init\": \"workspace:*\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/constants\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"plasmo\": \"workspace:*\",\n    \"typescript\": \"5.8.2\"\n  }\n}\n"
  },
  {
    "path": "cli/create-plasmo/src/commands.ts",
    "content": "export const validCommandList = []\n"
  },
  {
    "path": "cli/create-plasmo/src/index.ts",
    "content": "#!/usr/bin/env node\nimport { argv, exit } from \"process\"\nimport { version } from \"plasmo/package.json\"\nimport init from \"plasmo/src/commands/init\"\n\nimport { ErrorMessage } from \"@plasmo/constants/error\"\nimport { aLog, eLog } from \"@plasmo/utils/logging\"\nimport { exitCountDown } from \"@plasmo/utils/wait\"\n\nprocess.env.APP_VERSION = version\n\nasync function main() {\n  try {\n    // In case someone pasted an essay into the cli\n    if (argv.length > 10) {\n      throw new Error(ErrorMessage.TooManyArg)\n    }\n\n    argv.splice(2, 0, \"init\")\n\n    await init()\n  } catch (e) {\n    eLog((e as Error).message || ErrorMessage.Unknown)\n    aLog(e.stack)\n    await exitCountDown(3)\n    exit(1)\n  }\n}\n\nmain()\n\nprocess.on(\"SIGINT\", () => exit(0))\nprocess.on(\"SIGTERM\", () => exit(0))\n"
  },
  {
    "path": "cli/create-plasmo/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli.json\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\", \"templates\"],\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~features/*\": [\"../plasmo/src/features/*\"],\n      \"~commands\": [\"./src/commands\"]\n    }\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/.eslintrc.js",
    "content": "module.exports = require(\"@plasmo/config/eslint-preset\")\n"
  },
  {
    "path": "cli/plasmo/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\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": "cli/plasmo/README.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  English | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n**Production Cloud:** We've built a cloud offering for browser extensions called [Itero](https://itero.plasmo.com). Check it out if you want instant beta testing and more awesome features.\n\n# Plasmo Framework\n\nThe [Plasmo](https://www.plasmo.com/) Framework is a battery-packed browser extension SDK made by hackers for hackers. Build your product and stop worrying about config files and the odd peculiarities of building browser extensions.\n\n> It's like [Next.js](https://nextjs.org/) for browser extensions!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Highlighted Features\n\n- First-class [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) Support\n- [Declarative Development](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [Content Scripts UI](https://docs.plasmo.com/csui)\n- [Tab Pages](https://docs.plasmo.com/framework/tab-pages)\n- Live-reloading + React HMR\n- [`.env*` files](https://docs.plasmo.com/framework/env)\n- [Storage API](https://docs.plasmo.com/framework/storage)\n- [Messaging API](https://docs.plasmo.com/framework/messaging)\n- [Remote code bundling](https://docs.plasmo.com/framework/remote-code) (e.g., for Google Analytics)\n- Targeting [multiple browser and manifest pairs](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [Automated deployment via BPP](https://docs.plasmo.com/framework/workflows/submit)\n- Optional support for [Svelte](https://github.com/PlasmoHQ/with-svelte) and [Vue](https://github.com/PlasmoHQ/with-vue)\n\nAnd many, many more! 🚀\n\n## System Requirements\n\n- Node.js 16.x or later\n- MacOS, Windows, or Linux\n- (Strongly Recommended) [pnpm](https://pnpm.io/)\n\n## Examples\n\nWe have examples showcasing how one can use Plasmo with [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss), and many more. To check them out, [visit our examples repository](https://github.com/PlasmoHQ/examples).\n\n## Documentation\n\nCheck out the [documentation](https://docs.plasmo.com/) to get a more in-depth view into the Plasmo Framework.\n\n## Browser Extensions Book\n\nFor a more in-depth view into how browser extensions work, and how to develop them, we highly recommend Matt Frisbie's new book [\"Building Browser Extensions\"](https://buildingbrowserextensions.com/plasmo)\n\n## Usage\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nThe road ahead is filled with many turns.\n\n- Popup changes go in `popup.tsx`\n- Options page changes go in `options.tsx`\n- Content script changes go in `content.ts`\n- Background service worker changes go in `background.ts`\n\n### Directories\n\nYou can also organize these files in their own directories:\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nFinally, you can also avoid putting source code in your root directory by putting them in a `src` sub-directory, [following this guide](https://docs.plasmo.com/framework/customization/src). Note that `assets` and other config files will still need to be in the root directory.\n\n## Supported Browsers\n\nTo see a list of supported browser targets, [please refer to our documentation here](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets).\n\n## Community\n\nThe Plasmo community can be found on [Discord](https://www.plasmo.com/s/d). This is the appropriate channel to get help with using the Plasmo Framework.\n\nOur [Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) applies to all Plasmo community channels.\n\n## Contributing\n\nPlease see the [contributing guidelines](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) to learn more.\n\nA big thanks to all of our amazing [contributors](https://github.com/PlasmoHQ/plasmo/graphs/contributors) ❤️\n\nFeel free to join the fun and send a PR!\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Examples](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## Disclaimer\n\nPlasmo is currently alpha software, and some things might change from version to version, so please be mindful and use it at your own risk.\n\n# License\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/bin/index.mjs",
    "content": "#!/usr/bin/env node\n\nimport \"../dist/index.js\"\n"
  },
  {
    "path": "cli/plasmo/i18n/README.de-DE.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"License anzeigen\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Folge PlasmoHQ auf Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Schaue unsere Live DEMO jeden Freitag an\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Trete unserem Discord server bei, um zu chatten und Unterstützung für Projekte zu bekommen\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | Deutsch | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Plasmo Framework\n\nDas [Plasmo](https://www.plasmo.com/) Framework ist ein SDK zum Erstellen von Browser-Erweiterungen, das von Hackern für Hacker entwickelt wurde. Erstelle dein Produkt, ohne dir Gedanken über Konfigurationsdateien und die seltsamen Eigenheiten der Erstellung von Browsererweiterungen machen zu müssen.\n\n> Es ist wie [Next.js](https://nextjs.org/) für Browser-Erweiterungen!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Features\n\n- Direkte Unterstützung von [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/)\n- [Deklarative Entwicklung mit automatischer Erzeugung von \"manifest.json\" (MV3)](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- Automatisches Neuladen\n- [`.env*` Datei-Unterstützung](https://docs.plasmo.com/framework/env)\n- [Bundling von externen Skripten](https://docs.plasmo.com/framework/workflows/remote-code) (z.B. für gtag4)\n- Automatisierte Bereitstellung (über [BPP](https://docs.plasmo.com/framework/workflows/submit))\n- Und viel, viel mehr! 🚀\n\n## Systemanforderungen\n\n- Node.js 16.x oder neuer\n- MacOS, Windows oder Linux\n- (Stark empfohlen) [pnpm](https://pnpm.io/)\n\n## Beispiele\n\nWir haben Beispiele, die zeigen, wie man Plasmo mit [Firebase-Authentifizierung](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase-Authentifizierung](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss) und vielen anderen verwenden kann. Um sie auszuprobieren, [besuche unser Beispiel-Repository](https://github.com/PlasmoHQ/examples).\n\n## Dokumentation\n\nSchaue dir die [Dokumentation](https://docs.plasmo.com/) an, um einen tieferen Einblick in das Plasmo Framework zu erhalten.\n\n## Nutzung\n\n```\npnpm dlx plasmo init example-dir\ncd example-dir\npnpm dev\n```\n\nDanach stehen dir alle Wege offen.\n\n- Popup-Änderungen kommen in `popup.tsx`\n- Änderungen an der Optionsseite kommen in `options.tsx`.\n- Änderungen am Inhaltsskript kommen in `content.ts`\n- Änderungen am Hintergrunddienst kommen in die Datei `background.ts`.\n\n### Ordner-Struktur\n\nDu kannst die Dateien auch in eigenen Ordnern organisieren:\n\n```\next-dir\n├───assets\n|   └───icon512.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nAußerdem kannst du auch vermeiden, dass alle Dateien im Hauptverzeichnis liegen, wenn du sie in das Unterverzeichnis `src` legen, [indem du dieser Anleitung folgst](https://docs.plasmo.com/framework/customization/src). Beachte, dass `assets` und andere Konfigurationsdateien immer noch im Hauptverzeichnis liegen müssen.\n\n## Community\n\nDie Plasmo-Community ist auf [Discord](https://www.plasmo.com/s/d) zu finden. Das ist der richtige Kanal, um Hilfe bei der Verwendung des Plasmo-Frameworks zu erhalten.\n\nUnser [Verhaltenskodex](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) gilt für alle Plasmo Community-Kanäle.\n\n## Am Projekt beteiligen\n\nSchaue dir die [Richtlinien](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) an, um mehr zu erfahren.\n\n## Information\n\nPlasmo ist derzeit eine Alphasoftware, und einige Dinge können sich von Version zu Version ändern. Sei also bitte achtsam und benutze es auf eigenes Risiko.\n\n# Lizenz\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.fr-FR.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"License anzeigen\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Folge PlasmoHQ auf Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Schaue unsere Live DEMO jeden Freitag an\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Trete unserem Discord server bei, um zu chatten und Unterstützung für Projekte zu bekommen\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | French | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Plasmo Framework\n\nLe [Plasmo](https://www.plasmo.com/) Framework est un SDK pour la création d'extensions de navigateur, développé par des hackers pour des hackers. Créez votre produit sans vous soucier des fichiers de configuration et des étranges particularités de la création d'extensions de navigateur.\n\n> C'est comme [Next.js](https://nextjs.org/) pour les extensions de navigateur !\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Fonctionnalités\n\n- Prise en charge de [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) de première classe\n- [Développement déclaratif avec création automatique de \"manifest.json\" (MV3)](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- Chargement en temps réel\n- [Content Scripts UI](https://docs.plasmo.com/csui)\n- [Fichiers `.env*`](https://docs.plasmo.com/framework/env)\n- [Regroupement de codes distants](https://docs.plasmo.com/framework/remote-code) (par exemple pour gtag4)\n- Cibler [plusieurs paires de navigateurs et de manifestes](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [Déploiement automatisé via BPP](https://docs.plasmo.com/framework/workflows/submit)\n- [Svelte](https://github.com/PlasmoHQ/with-svelte) ou [Vue](https://github.com/PlasmoHQ/with-vue)\n- Et beaucoup, beaucoup plus! 🚀\n\n## Configuration requise\n\n- Node.js 16.x ou plus récent\n- MacOS, Windows ou Linux\n- (Fortement recommandé) [pnpm](https://pnpm.io/)\n\n## Examples\n\nNous avons des exemples qui montrent comment utiliser Plasmo avec [l'authentification Firebase](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss), et bien d'autres. Pour les essayer, [visitez notre référentiel d'exemples](https://github.com/PlasmoHQ/examples).\n\n## Documentation\n\nConsultez la [documentation](https://docs.plasmo.com/) pour obtenir une vue plus approfondie du cadre Plasmo.\n\n## Utilisation\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nLa route qui nous attend est pleine de virages.\n\n- Les modifications de popup viennent dans `popup.tsx`.\n- Les modifications de la page d'options viennent dans `options.tsx`.\n- Les modifications du script de contenu se trouvent dans `content.ts`.\n- Les modifications du service d'arrière-plan vont dans le fichier `background.ts`.\n\n### Structure des dossiers\n\nVous pouvez également organiser ces fichiers dans leurs propres répertoires :\n\n```\next-dir\n├───assets\n|   └───icon512.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nEnfin, vous pouvez aussi éviter de placer le code source dans votre répertoire racine en le plaçant dans un sous-répertoire `src`, [en suivant ce guide](https://docs.plasmo.com/framework/customization/src). Notez que `assets` et les autres fichiers de configuration devront toujours être dans le répertoire racine.\n\n## Communauté\n\nLa communauté Plasmo se trouve sur [Discord](https://www.plasmo.com/s/d). C'est le réseau approprié pour obtenir de l'aide sur l'utilisation du cadre Plasmo.\n\nNotre [code de conduite](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) s'applique à tous les canaux de la communauté Plasmo.\n\n## Contribution\n\nVeuillez consulter les [directives de contribution](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) pour en savoir plus.\n\n## Avis de non-responsabilité\n\nPlasmo est actuellement un logiciel alpha, et certaines choses peuvent changer d'une version à l'autre, alors soyez attentifs et utilisez-le à vos propres risques.\n\n# License\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.id-ID.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | Indonesian | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Framework Plasmo\n\nFramework [Plasmo](https://www.plasmo.com/) adalah SDK ekstensi browser penuh daya yang dibuat oleh hacker untuk hacker. Bangun produk Anda dan berhenti khawatir terhadap file konfigurasi dan berbagai macam anomali dalam membuat ekstensi browser.\n\n> Seperti [Next.js](https://nextjs.org/) untuk ekstensi browser!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Fitur Utama\n\n- First-class [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) Support\n- [Declarative Development](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [Content Scripts UI](https://docs.plasmo.com/csui)\n- [Tab Pages](https://docs.plasmo.com/framework/tab-pages)\n- Live-reloading + React HMR\n- [`.env*` files](https://docs.plasmo.com/framework/env)\n- [Storage API](https://docs.plasmo.com/framework/storage)\n- [Messaging API](https://docs.plasmo.com/framework/messaging)\n- [Remote code bundling](https://docs.plasmo.com/framework/remote-code) (contohnya: untuk Google Analytics)\n- Penargetan [beberapa browser dan manifest](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [Deployment otomatis melalui BPP](https://docs.plasmo.com/framework/workflows/submit)\n- Dukungan opsional untuk [Svelte](https://github.com/PlasmoHQ/with-svelte) dan [Vue](https://github.com/PlasmoHQ/with-vue)\n\nDan masih banyak lagi! 🚀\n\n## Kebutuhan Sistem\n\n- Node.js 16.x atau lebih tinggi\n- MacOS, Windows, atau Linux\n- (Sangat direkomendasikan) [pnpm](https://pnpm.io/)\n\n## Examples\n\nKami memiliki contoh yang menunjukkan bagaimana anda dapat menggunakan Plasmo [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss), dan banyak masih banyak lagi. Untuk mencobanya, [kunjungi repositori contoh kami](https://github.com/PlasmoHQ/examples).\n\n## Dokumentasi\n\nLihat [dokumentasi](https://docs.plasmo.com/) untuk mendapatkan gambaran yang lebih mendalam mengenai Framework Plasmo.\n\n## Browser Extensions Book\n\nUntuk gambaran yang lebih mendalam tentang cara kerja ekstensi browser, dan cara mengembangkannya, kami sangat merekomendasikan buku baru Matt Frisbie [\"Building Browser Extensions\"](https://buildingbrowserextensions.com/plasmo)\n\n## Cara Penggunaan\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nJalan di depan dipenuhi dengan banyak pilihan.\n\n- Mengubah Popup lakukan di `popup.tsx`\n- Mengubah Options page lakukan di `options.tsx`\n- Mengubah Content script lakukan di `content.ts`\n- Mengubah Background service worker lakukan di `background.ts`\n\n### Direktori\n\nAnda juga dapat mengatur file-file ini di direktori mereka sendiri:\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nTerakhir, Anda juga dapat menghindari menempatkan kode sumber di direktori root dengan menempatkannya di sub-direktori `src`, [mengikuti panduan ini](https://docs.plasmo.com/framework/customization/src). Perhatikan bahwa `assets` dan file konfigurasi lainnya masih perlu berada di direktori root.\n\n## Browser yang Didukung\n\nUntuk melihat daftar target browser yang didukung, [lihat dokumentasi kami di sini](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets).\n\n## Komunitas\n\nKomunitas Plasmo dapat ditemukan di [Discord](https://www.plasmo.com/s/d). Ini adalah saluran yang tepat untuk mendapatkan bantuan dalam menggunakan Plasmo Framework.\n\n[Pedoman Perilaku](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) kami berlaku untuk semua saluran komunitas Plasmo.\n\n## Berkontribusi\n\nSilahkan lihat [pedoman kontribusi](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) untuk mempelajari lebih lanjut.\n\nTerima kasih banyak untuk semua yang luar biasa [kontributor](https://github.com/PlasmoHQ/plasmo/graphs/contributors) ❤️\n\nJangan ragu untuk ikut bersenang-senang dan mengirim PR!\n\n### Framework Plasmo\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Examples](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## Disclaimer\n\nPlasmo saat ini adalah perangkat lunak alpha, dan beberapa hal mungkin berubah dari versi ke versi, jadi harap berhati-hati dan gunakan dengan risiko Anda sendiri.\n\n# Lisensi\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.ja-JP.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | 日本語 | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n**Production Cloud:** 私たちはブラウザ拡張機能向けのクラウドサービス「Itero」を開始しました。即時のベータテストやより素晴らしい機能が必要なら、ぜひチェックしてください。\n\n# Plasmo Framework\n\n[Plasmo](https://www.plasmo.com/) Framework は、すべての開発者のためのブラウザ拡張機能のSDKです。拡張機能のconfigファイルやビルドにおける面倒な独自仕様に悩まされずに拡張機能を作りましょう！\n\n> ブラウザ拡張機能における[Next.js](https://nextjs.org/)\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## 主な機能\n\n- [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) の全面サポート\n- [宣言型開発](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [Contents Scripts UI](https://docs.plasmo.com/csui)\n- [Tab Pages](https://docs.plasmo.com/framework/tab-pages)\n- ライブリロード + React HMR\n- [`.env*` ファイル](https://docs.plasmo.com/framework/env)\n- [Storage API](https://docs.plasmo.com/framework/storage)\n- [Messaging API](https://docs.plasmo.com/framework/messaging)\n- [リモートコードバンドル](https://docs.plasmo.com/framework/remote-code) (Google Analyticsなど)\n- [複数ブラウザ・マニフェスト対応](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [BPPによる自動デプロイ](https://docs.plasmo.com/framework/workflows/submit)\n- [Svelte](https://github.com/PlasmoHQ/with-svelte)、 [Vue](https://github.com/PlasmoHQ/with-vue) にも対応\n\n他にもたくさんの機能があります！ 🚀\n\n## システム要件\n\n- Node.js 16.x 以上\n- MacOS, Windows, Linux のいずれか\n- [pnpm](https://pnpm.io/)(推奨)\n\n## 例\n\n[Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss) などと組み合わせた例を[こちらのリポジトリ](https://github.com/PlasmoHQ/examples)で紹介しています。\n\n## ドキュメント\n\nさらに詳しく知りたい場合は、[ドキュメント](https://docs.plasmo.com/)をご覧ください。\n\n## ブラウザ拡張機能についての書籍\n\nブラウザ拡張機能の動作や開発方法についてさらに深く学びたい場合、Matt Frisbie氏の書籍[『Building Browser Extensions』](https://buildingbrowserextensions.com/plasmo)がおすすめです。\n\n## 使い方\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\n変更したい部分によって、以下のファイルを編集してください。\n\n- ポップアップ → `popup.tsx`\n- 設定ページ → `options.tsx`\n- コンテンツスクリプト → `content.ts`\n- バックグランドサービスワーカー → `background.ts`\n\n### ディレクトリ構造\n\nこれらのファイルはそれぞれのディレクトリに分けて整理することもできます。\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nまた、ルートディレクトリに置きたくない場合は、`src` ディレクトリを作成して、そこにソースコードを置くこともできます。詳しくは[こちらのガイド](https://docs.plasmo.com/framework/customization/src)をご覧ください。\n\nただし、`assets` やconfigファイルはルートディレクトリに置く必要があります。\n\n## 対応しているブラウザ\n\n対応しているブラウザのリストは、[こちらのドキュメント](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets)をご覧ください。\n\n## コミュニティ\n\n[Discord](https://www.plasmo.com/s/d)にPlasmoのコミュニティがあります。Plasmo Framework に関するヘルプはこちらでお願いします。\n\n[Code of Conduct](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md)は、全てのPlasmoコミュニティに適用されます。\n\n## コントリビュート\n\n詳しくは[コントリビュートガイドライン](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md)をご覧ください。\n\n素晴らしい[コントリビューターの方々](https://github.com/PlasmoHQ/plasmo/graphs/contributors)に感謝します❤️\n\nぜひ気軽に参加してPRを送ってください！\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Examples](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## 免責事項\n\nPlasmoは現在α版のソフトウェアです。バージョンアップによって変更される可能性がありますので、ご注意いただき自己責任で使用してください。\n\n# ライセンス\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.ko-KR.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | 한국어\n</p>\n\n**Production Cloud:** [Itero](https://itero.plasmo.com)라는 브라우저 확장 프로그램용 클라우드 서비스를 구축했습니다. 즉시 베타 테스트 및 더 많은 멋진 기능을 원하시면 확인해보세요.\n\n\n# Plasmo 프레임워크\n\n[Plasmo](https://www.plasmo.com/) 프레임워크는 개발자들을 위해 만들어진 강력한 브라우저 확장 프로그램 SDK입니다. 이를 통해 제품을 만들 때 설정 파일과 브라우저 확장 프로그램 개발의 특이한 부분에 대해 걱정하지 않고 진행할 수 있습니다.\n\n> 브라우저 확장 프로그램을 위한 [Next.js](https://nextjs.org/)와 같습니다!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## 주요 기능\n\n- [React](https://reactjs.org/) 및 [Typescript](https://www.typescriptlang.org/)를 위한 first-class 지원\n- [선언적 개발](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [콘텐츠 스크립트 UI](https://docs.plasmo.com/csui)\n- [탭 페이지](https://docs.plasmo.com/framework/tab-pages)\n- 라이브 리로딩 및 React HMR\n- [`.env*` 파일](https://docs.plasmo.com/framework/env)\n- [Storage API](https://docs.plasmo.com/framework/storage)\n- [Messaging API](https://docs.plasmo.com/framework/messaging)\n- [원격 코드 번들링](https://docs.plasmo.com/framework/remote-code) (ex. Google Analytics를 위한)\n- [여러 브라우저 및 매니페스트 타겟팅](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [BPP를 통한 자동 배포](https://docs.plasmo.com/framework/workflows/submit)\n- [Svelte](https://github.com/PlasmoHQ/with-svelte) 및 [Vue](https://github.com/PlasmoHQ/with-vue)의 선택적 지원\n\n이 외에도 많은 기능이 있습니다! 🚀\n\n## 시스템 요구 사항\n\n- Node.js 16.x 이상\n- MacOS, Windows 또는 Linux\n- (매우 권장) [pnpm](https://pnpm.io/)\n\n## 예제\n\n[Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss)와 함께 Plasmo를 사용하는 방법을 보여주는 예제를 제공하고 있습니다. 이를 확인하려면 [예제 레포지토리](https://github.com/PlasmoHQ/examples)를 방문해보세요.\n\n## 문서\n\nPlasmo 프레임워크에 대해 더 자세히 알아보려면 [문서](https://docs.plasmo.com/)를 확인하세요.\n\n\n## 브라우저 확장 프로그램 관련 책\n\n브라우저 확장 프로그램이 작동하는 방식과 개발하는 방법에 대해 더 자세히 알아보려면 Matt Frisbie의 새 책 [\"Building Browser Extensions\"](https://buildingbrowserextensions.com/plasmo)을 강력히 추천합니다.\n\n## 사용법\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\n변경하고자 하는 부분에 따라 아래 파일을 편집해 주세요.\n\n- 팝업 → `popup.tsx` 파일\n- 옵션 페이지 → `options.tsx`\n- 콘텐츠 스크립트 → `content.ts`\n- 백그라운드 서비스 워커 → `background.ts`\n\n### 폴더 구조\n\n이 파일들을 각각의 디렉토리에 정리해서 넣을 수도 있습니다.\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\n마지막으로, 소스 코드를 루트 디렉토리에 넣지 않고 [이 가이드를 따라](https://docs.plasmo.com/framework/customization/src) `src` 하위 디렉토리에 넣을 수도 있습니다. 그러나 `assets` 및 기타 구성 파일은 여전히 루트 디렉토리에 있어야 합니다.\n\n## 지원하는 브라우저\n\n지원하는 브라우저 목록을 확인하려면 [이 문서](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets)를 참조하세요.\n\n## 커뮤니티\n\nPlasmo 커뮤니티는 [Discord](https://www.plasmo.com/s/d)에서 찾을 수 있습니다. Plasmo 프레임워크 사용에 관한 도움을 받기에 적절한 채널입니다.\n\n[행동 강령(Code of Conduct)](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md)은 모든 Plasmo 커뮤니티 채널에 적용됩니다.\n\n## 기여\n\n자세한 내용은 [기여 가이드라인(Contributing Guidelines)](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md)을 참조하세요.\n\n훌륭한 [컨트리뷰터](https://github.com/PlasmoHQ/plasmo/graphs/contributors) 여러분들께 큰 감사를 드립니다 ❤️\n\n자유롭게 참여하고 PR을 보내주세요!\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Examples](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## 면책 조항\n\nPlasmo는 현재 알파 버전의 소프트웨어이며, 버전 간에 일부 변경 사항이 있을 수 있으므로 주의하고 사용하십시오.\n\n# 라이선스\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.ru-RU.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | Русский | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Plasmo Framework\n\nФреймворк [Plasmo](https://www.plasmo.com/) это SDK для разработки кроссплатформерных расширений для браузера, созданное хакерами для хакеров. Разрабатывайте расширения и перестаньте беспокоится о конфигах и специфичных особенностях браузерных расширений.\n\n> Это как [Next.js](https://nextjs.org/) для браузерных расширений!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Главные особенности\n\n- Первоклассная поддержка [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/)\n- [Декларативная настройка \"manifest.json\" (MV3)](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [Контент скрипты с поддержкой UI](https://docs.plasmo.com/csui)\n- [Вкладки расширения](https://docs.plasmo.com/framework/tab-pages)\n- Активная перезагрузка + React HMR\n- [`.env*` файлы](https://docs.plasmo.com/framework/env)\n- [API для хранения информации](https://docs.plasmo.com/framework/storage)\n- [API для общения между различными частями расширения](https://docs.plasmo.com/framework/messaging)\n- [Подключение удаленного кода](https://docs.plasmo.com/framework/remote-code) (e.g., for Google Analytics)\n- Поддержка [кроссплатфоменности и различных видов манифеста](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [Автоматическое развертывание с помощью BPP](https://docs.plasmo.com/framework/workflows/submit)\n- Дополнительная поддержка [Svelte](https://github.com/PlasmoHQ/with-svelte) и [Vue](https://github.com/PlasmoHQ/with-vue)\n\nИ многое, многое другое! 🚀\n\n## Системные требования\n\n- Node.js 16.x или выше\n- MacOS, Windows, или Linux\n- (Настоятельно рекомендуется) [pnpm](https://pnpm.io/)\n\n## Примеры\n\nУ нас есть примеры, показывающие, как можно использовать Plasmo с [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss), и многое другое. Чтобы посмотреть, [посетите наш репозиторий примеров](https://github.com/PlasmoHQ/examples).\n\n## Документация\n\nОзнакомьтесь с [documentation](https://docs.plasmo.com/) чтобы получить более глубокое представление о Plasmo Framework.\n\n## Книга расширений браузера\n\nДля более подробного ознакомления с тем, как работают расширения браузера и как их разрабатывать, мы настоятельно рекомендуем новую книгу Мэтта Фрисби [\"Building Browser Extensions\"](https://buildingbrowserextensions.com/plasmo)\n\n## Использование\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nДальнейший путь наполнен возможностями.\n\n- Изменение Popup в `popup.tsx`\n- Редактирование страницы настроек расширения в `options.tsx`\n- Настройка контент скриптов в `content.ts`\n- Изменение Background service worker в `background.ts`\n\n### Каталоги\n\nВы также можете структурировать эти файлы в собственных каталогах:\n\n```\nпапка-расширения\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nНаконец, вы также можете избежать размещения исходного кода в вашем корневом каталоге, поместив их в подкаталог `src`, [следуя этому руководству](https://docs.plasmo.com/framework/customization/src). Обратите внимание что `assets` и другие конфигурационные файлы по-прежнему должны находиться в корневом каталоге.\n\n## Поддерживаемые браузеры\n\nЧтобы просмотреть список поддерживаемых браузеров, [пожалуйста, обратитесь к нашей документации здесь](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets).\n\n## Сообщество\n\nСообщество Plasmo можно найти в [Discord](https://www.plasmo.com/s/d). Это подходящий канал для получения помощи в использовании Plasmo Framework.\n\nНаш [Кодекс поведения](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) применяется ко всем каналам сообщества Plasmo.\n\n## Внести свой вклад\n\nПожалуйста, ознакомьтесь с [рекомендациями по контрибьютингу](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) чтобы узнать больше.\n\nБольшое спасибо всем нашим удивительным [помощникам](https://github.com/PlasmoHQ/plasmo/graphs/contributors) ❤️\n\nНе стесняйтесь присоединиться к веселью и отправить PR!\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Примеры Plasmo](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## Дисклеймер\n\nВ настоящее время Plasmo является альфа-версией программного обеспечения, и некоторые вещи могут меняться от версии к версии, поэтому, пожалуйста, будьте внимательны и используйте его на свой страх и риск.\n\n# Лицензия\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.tr-TR.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | Turkish | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Plasmo Framework\n\n[Plasmo](https://www.plasmo.com/) Framework, hacker ruhlu yazılımcılar tarafından hacker ruhlu yazılımcılar için yapılmış pille dolu bir tarayıcı uzantısı geliştirme kiti'dir.\n\n> Tarayıcı uzantılarının [Next.js](https://nextjs.org/)'i gibi.\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Öne Çıkan Özellikler\n\n- First-class [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) Desteği\n- [Declarative Geliştirme](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [Content Scripts UI](https://docs.plasmo.com/csui)\n- [Sekme Sayfaları](https://docs.plasmo.com/framework/tab-pages)\n- Canlı-reloading + React HMR\n- [`.env*` dosyaları](https://docs.plasmo.com/framework/env)\n- [Storage API'ı](https://docs.plasmo.com/framework/storage)\n- [Messaging API'ı](https://docs.plasmo.com/framework/messaging)\n- [Remote code bundle'lama](https://docs.plasmo.com/framework/remote-code) (örn: Google Analytics için)\n- [Birden çok tarayıcı ve manifest eşi](https://docs.plasmo.com/framework/workflows/build#with-specific-target) hedefleme\n- [BPP ile otomatik deploy](https://docs.plasmo.com/framework/workflows/submit)\n- İsteğe bağlı [Svelte](https://github.com/PlasmoHQ/with-svelte) ve [Vue](https://github.com/PlasmoHQ/with-vue) desteği\n\nVe daha, daha fazlası! 🚀\n\n## Sistem Gereksinimleri\n\n- Node.js 16.x ve üzeri\n- MacOS, Windows veya Linux\n- (Şiddetle Tavsiye) [pnpm](https://pnpm.io/)\n\n## Örnekler\n\nPlasmo'nun [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss) ve çok daha fazlası ile nasıl kullanılabileceğini gösteren örneklerimiz mevcut. Bunları görmek için [örnekler repomuzu ziyaret edin](https://github.com/PlasmoHQ/examples).\n\n## Dökümantasyon\n\nPlasmo Framework'u hakkında daha derinlemesine bilgi edinmek için [dökümantasyon](https://docs.plasmo.com/)'a göz atın.\n\n## Tarayıcı Uzantıları Kitabı\n\nTarayıcı uzantılarının nasıl çalıştığına ve nasıl geliştirileceğine dair daha derinlemesine bir bakış için Matt Frisbie'nin yeni kitabı \"[Building Browser Extensions](https://buildingbrowserextensions.com/plasmo)\"ı şiddetle tavsiye ediyoruz.\n\n## Kullanım\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nÖnümüzdeki yol birçok virajla dolu.\n\n- Popup değişiklikleri `popup.tsx` dosyasına eklenir\n- Seçenekler sayfası değişiklikleri `options.tsx` dosyasına eklenir\n- Content script değişiklikleri `content.ts` dosyasına eklenir\n- Arka plan hizmet çalışanı değişiklikleri `background.ts` dosyasına eklenir\n\n### Dizinler\n\nBu dosyaları kendi dizinlerine sahip olacak şekilde de düzenleyebilirsiniz:\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nSon olarak, kaynak kodunu kök dizinine koymak yerine `src` alt dizinine koymak için [bu kılavuzu izleyebilirsin](https://docs.plasmo.com/framework/customization/src). `assets`'lerinizin ve diğer config dosyalarının yine de kök dizininde olması gerekeceğini unutmayın.\n\n## Desteklenen Tarayıcılar\n\nDesteklenen tarayıcı hedeflerinin bir listesini görmek için [lütfen buradaki dökümantasyon'a bakın](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets).\n\n## Topluluk\n\nPlasmo topluluğu [Discord](https://www.plasmo.com/s/d)'da. Bu Plasmo Framework'ü kullanma konusunda yardım almak için uygun bir kanaldır.\n\n[Davranış Kurallarımız](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) tüm Plasmo topluluk kanalları için geçerlidir.\n\n## Katkıda bulunma\n\nDaha fazla bilgi edinmek için lütfen [katkıda bulunma yönergelerine](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) bakın.\n\nKatkıda bulunan tüm harika [katılımcılarımıza](https://github.com/PlasmoHQ/plasmo/graphs/contributors) çok teşekkür ederiz ❤️\n\nEğlenceye katılmaktan ve PR göndermekten çekinmeyin!\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Örnekleri](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## Sorumluluk Reddi\n\nPlasmo şu anda alfa yazılımıdır ve bazı şeyler sürümden sürüme değişebilir, bu nedenle lütfen dikkatli olun ve riski size ait olacak şekilde kullanın.\n\n# Lisans\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.vi-VN.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"Xem License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Theo dõi PlasmoHQ trên Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Xem trực tiếp DEMO mỗi thứ Sáu\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Tham gia Discord để chat về Plasmo\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | <a href=\"/cli/plasmo/i18n/README.zh-CN.md\">简体中文</a> | Tiếng Việt | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n# Plasmo Framework\n\n[Plasmo](https://www.plasmo.com/) là một framework dùng để xây dựng ứng dụng mở rộng cho trình duyệt web (browser extension) với nhiều tính năng tối ưu hóa, tạo bởi hackers cho hackers. Xây dựng sản phẩm mà không phải lo lắng về config và những dị thù khi làm việc với extension.\n\n> Giống như [Next.js](https://nextjs.org/) cho extension!\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## Tính năng\n\n- [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/)\n- [Tự động hóa `manifest.json` với MV3](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- Tự động reload trình duyệt\n- [`.env*` file](https://docs.plasmo.com/framework/env)\n- [Content Scripts UI](https://docs.plasmo.com/csui)\n- [Gói mã nguồn online](https://docs.plasmo.com/framework/workflows/remote-code) (e.g for gtag4)\n- [Tự động xuất bản với BPP](https://docs.plasmo.com/framework/workflows/submit)\n- [Tạo extension cho mọi trình duyệt](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- Dùng với [Svelte](https://github.com/PlasmoHQ/with-svelte) hoặc [Vue](https://github.com/PlasmoHQ/with-vue)\n- Và nhiều hơn nữa! 🚀\n\n## Yêu cầu hệ thống\n\n- Node.js 16.x trở lên\n- MacOS, Windows, hoặc Linux\n- (Khuyến khích) [pnpm](https://pnpm.io/)\n\n## Ví dụ\n\nChúng tôi có các ví dụ giới thiệu cách bạn có thể sử dụng Plasmo với [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss), và nhiều hơn nữa. Để xem chúng, hãy [truy cập kho ví dụ của chúng tôi](https://github.com/PlasmoHQ/examples).\n\n## Tài liệu\n\nXem [tài liệu](https://docs.plasmo.com/) để nhìn chuyên sâu hơn.\n\n## Cách sử dụng\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\nCon đường phía trước còn nhiều trông gai.\n\n- Thay đổi popup trong `popup.tsx`\n- Thay đổi trang Options trong `options.tsx`\n- Thay đổi Content script trong `content.ts`\n- Thay đổi dịch vụ nền (Background service worker) trong `background.ts`\n\n### Thư mục\n\nBạn có thể sắp xếp các tệp này trong thư mục riêng của chúng:\n\n```\next-dir\n├───assets\n|   └───icon512.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\nCuối cùng, bạn cũng có thể tránh đặt mã nguồn vào thư mục gốc của mình bằng cách đặt chúng vào thư mục con `src`, [làm theo hướng dẫn này](https://docs.plasmo.com/framework/customization/src). Lưu ý, thư mục `assets` và các tệp config vẫn cần phải ở trong thư mục gốc.\n\n## Cộng đồng\n\nCộng đồng Plasmo có thể được tìm thấy trên [Discord](https://www.plasmo.com/s/d). Đây là kênh thích hợp để nhận trợ giúp về việc sử dụng Plasmo Framework.\n\n[Quy tắc ứng xử](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) của chúng tôi áp dụng cho tất cả các kênh cộng đồng của Plasmo.\n\n## Đóng góp\n\nVui lòng xem [hướng dẫn đóng góp](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) để tìm hiểu thêm.\n\n## Tuyên bố từ chối trách nhiệm\n\nPlasmo hiện là phần mềm alpha và một số thứ có thể thay đổi từ phiên bản này sang phiên bản khác. Xin lưu ý, Plasmo sẽ không chịu trách nhiệm nếu bạn gặp rủi ro khi xử dụng phần mềm này.\n\n# Giấy phép bản quyền\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/i18n/README.zh-CN.md",
    "content": "<p align=\"center\">\n  <a href=\"https://plasmo.com\">\n    <img alt=\"plasmo logo\" width=\"75%\" src=\"https://www.plasmo.com/assets/banner-black-on-white.png\" />\n  </a>\n</p>\n\n<p align=\"center\">\n  <a aria-label=\"License\" href=\"/cli/plasmo/LICENSE\">\n    <img alt=\"See License\" src=\"https://img.shields.io/npm/l/plasmo\"/>\n  </a>\n  <a aria-label=\"NPM\" href=\"https://www.npmjs.com/package/plasmo\">\n    <img alt=\"NPM Install\" src=\"https://img.shields.io/npm/v/plasmo?logo=npm\"/>\n  </a>\n  <a aria-label=\"Twitter\" href=\"https://www.twitter.com/plasmohq\">\n    <img alt=\"Follow PlasmoHQ on Twitter\" src=\"https://img.shields.io/twitter/follow/plasmohq?logo=twitter\"/>\n  </a>\n  <a aria-label=\"Twitch Stream\" href=\"https://www.twitch.tv/plasmohq\">\n    <img alt=\"Watch our Live DEMO every Friday\" src=\"https://img.shields.io/twitch/status/plasmohq?logo=twitch&logoColor=white\"/>\n  </a>\n  <a aria-label=\"Discord\" href=\"https://www.plasmo.com/s/d\">\n    <img alt=\"Join our Discord for support and chat about our projects\" src=\"https://img.shields.io/discord/946290204443025438?logo=discord&logoColor=white\"/>\n  </a>\n</p>\n\n<p align=\"center\">\n  <a href=\"/cli/plasmo/README.md\">English</a> | 简体中文 | <a href=\"/cli/plasmo/i18n/README.vi-VN.md\">Tiếng Việt</a> | <a href=\"/cli/plasmo/i18n/README.de-DE.md\">Deutsch</a> | <a href=\"/cli/plasmo/i18n/README.fr-FR.md\">French</a> | <a href=\"/cli/plasmo/i18n/README.id-ID.md\">Indonesian</a> | <a href=\"/cli/plasmo/i18n/README.ru-RU.md\">Русский</a> | <a href=\"/cli/plasmo/i18n/README.tr-TR.md\">Turkish</a> | <a href=\"/cli/plasmo/i18n/README.ja-JP.md\">日本語</a> | <a href=\"/cli/plasmo/i18n/README.ko-KR.md\">한국어</a>\n</p>\n\n**云服务:** 我们为浏览器扩展程序构建了一个云服务，叫 [Itero](https://itero.plasmo.com) 。如果你想要进行即时beta测试并体验更多很棒的功能，可以去尝试一下。\n\n# Plasmo 框架\n\n[Plasmo](https://www.plasmo.com/) 框架是一款黑客为黑客打造的功能强大的浏览器扩展程序软件开发工具包（SDK）。使用 Plasmo 来构建你的浏览器扩展程序，不需要操心扩展的配置文件和构建时的一些奇怪特性。\n\n> 它就像浏览器扩展界的 [Next.js](https://nextjs.org/) ！\n\n![CLI Demo](https://www.plasmo.com/assets/plasmo-cli-demo.gif)\n\n## 特性\n\n- 一流的 [React](https://reactjs.org/) + [Typescript](https://www.typescriptlang.org/) 支持\n- [声明式开发（自动生成 manifest.json）](https://docs.plasmo.com/framework#where-is-the-manifestjson-file)\n- [将UI组件渲染到网页](https://docs.plasmo.com/csui)\n- [扩展内置页面](https://docs.plasmo.com/framework/tab-pages)\n- 扩展热重载 + React 模块热更新\n- [`.env*` 文件](https://docs.plasmo.com/framework/env)\n- [扩展储存 API](https://docs.plasmo.com/framework/storage)\n- [扩展通信 API](https://docs.plasmo.com/framework/messaging)\n- [远程代码打包](https://docs.plasmo.com/framework/remote-code) (例如 Google Analytics)\n- 支持[多个浏览器和manifest版本](https://docs.plasmo.com/framework/workflows/build#with-specific-target)\n- [通过BPP进行自动部署](https://docs.plasmo.com/framework/workflows/submit)\n- 可选 [Svelte](https://github.com/PlasmoHQ/with-svelte) 或 [Vue](https://github.com/PlasmoHQ/with-vue) 进行开发\n\n还有更多的功能！🚀\n\n## 系统要求\n\n- Node.js 16.x 及以上\n- MacOS，Windows，或 Linux\n- (强烈推荐) [pnpm](https://pnpm.io/)\n\n## 代码示例\n\n我们有一些展示如何集成 [Firebase Authentication](https://github.com/PlasmoHQ/examples/tree/main/with-firebase-auth), [Redux](https://github.com/PlasmoHQ/examples/tree/main/with-redux), [Supabase authentication](https://github.com/PlasmoHQ/examples/tree/main/with-supabase), [Tailwind](https://github.com/PlasmoHQ/examples/tree/main/with-tailwindcss) 以及更多技术的代码示例。如果想要浏览全部代码示例，请[访问示例仓库](https://github.com/PlasmoHQ/examples)。\n\n## 文档\n\n阅读 [文档](https://docs.plasmo.com/) 以更深入地了解 Plasmo 框架。\n\n## 浏览器扩展书籍\n\n为了更深入了解浏览器扩展工作原理和开发方法，我们强烈推荐 Matt Frisbie 的新书 [\"Building Browser Extensions\"](https://buildingbrowserextensions.com/plasmo)。\n\n## 使用\n\n```\npnpm create plasmo example-dir\ncd example-dir\npnpm dev\n```\n\n注意\n\n- Popup 页面改动应在 `popup.tsx`\n- Options 页面改动应在 `options.tsx`\n- Content script 改动应在 `content.ts`\n- Background service worker 改动应在 `background.ts`\n\n### 目录\n\n您还可以在它们各自的目录中组织这些文件：\n\n```\next-dir\n├───assets\n|   └───icon.png\n├───popup\n|   ├───index.tsx\n|   └───button.tsx\n├───options\n|   ├───index.tsx\n|   ├───utils.ts\n|   └───input.tsx\n├───contents\n|   ├───site-one.ts\n|   ├───site-two.ts\n|   └───site-three.ts\n...\n```\n\n此外，您也能够将代码放到 `src` 子目录，而不将它们放到根目录，请[参阅该指南](https://docs.plasmo.com/framework/customization/src)。注意 `assets` 和其他配置文件仍须在根目录下。\n\n## 支持的浏览器\n\n要查看支持的浏览器列表，[请参考我们此处的文档](https://docs.plasmo.com/framework/workflows/faq#what-are-the-officially-supported-browser-targets).\n\n## 社区\n\n可以在 [Discord](https://www.plasmo.com/s/d) 找到 Plasmo 社区。这是获得 Plasmo 框架使用帮助的恰当渠道。\n\n我们的 [行为守则](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CODE_OF_CONDUCT.md) 适用于所有 Plasmo 社区频道。\n\n## 贡献\n\n请参阅 [贡献指南](https://github.com/PlasmoHQ/plasmo/blob/main/.github/CONTRIBUTING.md) 以了解更多内容。\n\n非常感谢所有的 [贡献者](https://github.com/PlasmoHQ/plasmo/graphs/contributors) ❤️\n\n欢迎发送PR加入我们的行列！\n\n### Plasmo Framework\n\n<a href=\"https://github.com/PlasmoHQ/plasmo/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/plasmo\" />\n</a>\n\n### [Plasmo Examples](https://github.com/PlasmoHQ/examples)\n\n<a href=\"https://github.com/PlasmoHQ/examples/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/examples\" />\n</a>\n\n### [Plasmo Storage](https://github.com/PlasmoHQ/storage)\n\n<a href=\"https://github.com/PlasmoHQ/storage/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/storage\" />\n</a>\n\n### [Browser Platform Publisher](https://github.com/PlasmoHQ/bpp)\n\n<a href=\"https://github.com/PlasmoHQ/bpp/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=PlasmoHQ/bpp\" />\n</a>\n\n## 免责声明\n\nPlasmo 当前仍为 alpha 软件，且不同版本间可能存在修改，所以在使用过程中请留意，风险自负。\n\n# 协议\n\n[MIT](https://github.com/PlasmoHQ/plasmo/blob/main/LICENSE) ⭐ [Plasmo](https://www.plasmo.com)\n"
  },
  {
    "path": "cli/plasmo/index.mjs",
    "content": "import { argv, exit } from \"process\"\nimport { build, context } from \"esbuild\"\nimport fse from \"fs-extra\"\n\nconst watch = argv.includes(\"-w\")\n\n/** @type import('esbuild').BuildOptions */\nconst commonConfig = {\n  sourcemap: watch ? \"inline\" : false,\n  minify: !watch,\n  logLevel: watch ? \"info\" : \"warning\",\n\n  bundle: true\n}\n\nasync function main() {\n  const config = await fse.readJson(\"package.json\")\n  const define = {\n    \"process.env.APP_VERSION\": `\"${config.version}\"`\n  }\n\n  /** @type import('esbuild').BuildOptions */\n  const opts = {\n    ...commonConfig,\n    entryPoints: [\"src/index.ts\"],\n    external: Object.keys(config.dependencies),\n    platform: \"node\",\n    format: \"esm\",\n    define,\n    banner: {\n      js: \"import { createRequire } from 'module';const require = createRequire(import.meta.url);\"\n    },\n    outfile: \"dist/index.js\"\n  }\n\n  if (watch) {\n    const ctx = await context(opts)\n    await ctx.watch()\n  } else {\n    await build(opts)\n  }\n}\n\nmain()\n\nprocess.on(\"SIGINT\", () => exit(0))\nprocess.on(\"SIGTERM\", () => exit(0))\n"
  },
  {
    "path": "cli/plasmo/package.json",
    "content": "{\n  \"name\": \"plasmo\",\n  \"version\": \"0.90.5\",\n  \"description\": \"The Plasmo Framework CLI\",\n  \"publishConfig\": {\n    \"types\": \"dist/type.d.ts\"\n  },\n  \"types\": \"src/type.ts\",\n  \"main\": \"dist/index.js\",\n  \"bin\": \"bin/index.mjs\",\n  \"type\": \"module\",\n  \"files\": [\n    \"bin/index.mjs\",\n    \"dist/index.js\",\n    \"dist/type.d.ts\",\n    \"templates\"\n  ],\n  \"scripts\": {\n    \"dev\": \"node index.mjs -w\",\n    \"build\": \"node index.mjs\",\n    \"type\": \"tsup src/type.ts --format esm --dts-only --dts-resolve\",\n    \"prepublishOnly\": \"run-p type build\",\n    \"lint\": \"run-p lint:*\",\n    \"lint:type\": \"tsc --noemit\",\n    \"lint:code\": \"eslint src/**/*.ts\"\n  },\n  \"author\": \"Plasmo Corp. <support@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\",\n    \"directory\": \"cli/plasmo\"\n  },\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"plasmo\",\n    \"browser-extensions\",\n    \"framework\"\n  ],\n  \"dependencies\": {\n    \"@expo/spawn-async\": \"1.7.2\",\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/fs\": \"2.9.3\",\n    \"@parcel/package-manager\": \"2.9.3\",\n    \"@parcel/watcher\": \"2.5.1\",\n    \"@plasmohq/init\": \"workspace:*\",\n    \"@plasmohq/parcel-config\": \"workspace:*\",\n    \"@plasmohq/parcel-core\": \"workspace:*\",\n    \"buffer\": \"6.0.3\",\n    \"chalk\": \"5.4.1\",\n    \"change-case\": \"5.4.4\",\n    \"dotenv\": \"16.4.7\",\n    \"dotenv-expand\": \"12.0.1\",\n    \"events\": \"3.3.0\",\n    \"fast-glob\": \"3.3.3\",\n    \"fflate\": \"0.8.2\",\n    \"get-port\": \"7.1.0\",\n    \"got\": \"14.4.6\",\n    \"ignore\": \"7.0.3\",\n    \"inquirer\": \"12.5.0\",\n    \"is-path-inside\": \"4.0.0\",\n    \"json5\": \"2.2.3\",\n    \"mnemonic-id\": \"3.2.7\",\n    \"node-object-hash\": \"3.1.1\",\n    \"package-json\": \"10.0.1\",\n    \"process\": \"0.11.10\",\n    \"semver\": \"7.7.1\",\n    \"sharp\": \"0.33.5\",\n    \"tempy\": \"3.1.0\",\n    \"typescript\": \"5.8.2\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/constants\": \"workspace:*\",\n    \"@plasmo/framework-shared\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"vue\": \"3.5.13\"\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/commands/build.ts",
    "content": "import { getNonFlagArgvs } from \"@plasmo/utils/argv\"\nimport { hasFlag } from \"@plasmo/utils/flags\"\nimport { iLog, sLog } from \"@plasmo/utils/logging\"\n\nimport { getBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\nimport { nextNewTab } from \"~features/extra/next-new-tab\"\nimport { checkNewVersion } from \"~features/framework-update/version-tracker\"\nimport { createParcelBuilder } from \"~features/helpers/create-parcel-bundler\"\nimport { printHeader } from \"~features/helpers/print\"\nimport { createManifest } from \"~features/manifest-factory/create-manifest\"\nimport { zipBundle } from \"~features/manifest-factory/zip\"\n\nasync function build() {\n  printHeader()\n  checkNewVersion()\n\n  process.env.NODE_ENV = \"production\"\n\n  const [internalCmd] = getNonFlagArgvs(\"build\")\n\n  if (internalCmd === \"next-new-tab\") {\n    await nextNewTab()\n    return\n  }\n\n  iLog(\"Prepare to bundle the extension...\")\n\n  const bundleConfig = getBundleConfig()\n\n  iLog(\"Building for target:\", bundleConfig.target)\n\n  const plasmoManifest = await createManifest(bundleConfig)\n\n  const bundler = await createParcelBuilder(plasmoManifest, {\n    mode: \"production\",\n    shouldDisableCache: true,\n    shouldContentHash: false,\n    defaultTargetOptions: {\n      shouldOptimize: true,\n      shouldScopeHoist: hasFlag(\"--hoist\")\n    }\n  })\n\n  const result = await bundler.run()\n  sLog(`Finished in ${result.buildTime}ms!`)\n\n  await plasmoManifest.postBuild()\n\n  if (hasFlag(\"--zip\")) {\n    await zipBundle(plasmoManifest.commonPath)\n  }\n}\n\nexport default build\n"
  },
  {
    "path": "cli/plasmo/src/commands/dev.ts",
    "content": "import {\n  BuildSocketEvent,\n  getBuildSocket\n} from \"@plasmo/framework-shared/build-socket\"\nimport { getFlag, isVerbose } from \"@plasmo/utils/flags\"\nimport { eLog, iLog, sLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { getBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\nimport { createProjectWatcher } from \"~features/extension-devtools/project-watcher\"\nimport { checkNewVersion } from \"~features/framework-update/version-tracker\"\nimport { createParcelBuilder } from \"~features/helpers/create-parcel-bundler\"\nimport { startLoading, stopLoading } from \"~features/helpers/loading-animation\"\nimport { printHeader } from \"~features/helpers/print\"\nimport { createManifest } from \"~features/manifest-factory/create-manifest\"\n\nasync function dev() {\n  printHeader()\n  checkNewVersion()\n\n  process.env.NODE_ENV = \"development\"\n\n  const rawServePort = getFlag(\"--serve-port\") || \"1012\"\n  const serveHost = getFlag(\"--serve-host\") || \"localhost\"\n  const rawHmrPort = getFlag(\"--hmr-port\") || \"1815\"\n  const hmrHost = getFlag(\"--hmr-host\") || \"localhost\"\n\n  iLog(\"Starting the extension development server...\")\n\n  const { default: getPort } = await import(\"get-port\")\n\n  const [servePort, hmrPort] = await Promise.all([\n    getPort({ port: parseInt(rawServePort) }),\n    getPort({ port: parseInt(rawHmrPort) })\n  ])\n\n  const buildWatcher = getBuildSocket(hmrHost, hmrPort)\n  vLog(\n    `Starting dev server on ${serveHost}:${servePort}, HMR on ${hmrHost}:${hmrPort}...`\n  )\n\n  const bundleConfig = getBundleConfig()\n\n  iLog(\"Building for target:\", bundleConfig.target)\n\n  const plasmoManifest = await createManifest(bundleConfig)\n\n  const projectWatcher = await createProjectWatcher(plasmoManifest)\n\n  const bundler = await createParcelBuilder(plasmoManifest, {\n    logLevel: \"verbose\",\n    shouldBundleIncrementally: true,\n    serveOptions: {\n      host: serveHost,\n      port: servePort\n    },\n    hmrOptions: {\n      host: hmrHost,\n      port: hmrPort\n    }\n  })\n\n  const { default: chalk } = await import(\"chalk\")\n\n  const bundlerWatcher = await bundler.watch(async (err, event) => {\n    if (err) {\n      stopLoading()\n      throw err\n    }\n\n    if (event === undefined) {\n      return\n    }\n\n    if (event.type === \"buildStart\") {\n      startLoading()\n      return\n    }\n\n    if (event.type === \"buildSuccess\") {\n      stopLoading()\n      sLog(`Extension re-packaged in ${chalk.bold(event.buildTime)}ms! 🚀`)\n      await plasmoManifest.postBuild()\n      buildWatcher.broadcast(BuildSocketEvent.BuildReady)\n      return\n    }\n\n    if (event.type === \"buildFailure\") {\n      stopLoading()\n      if (!isVerbose()) {\n        eLog(\n          chalk.redBright(\n            `Build failed. To debug, run ${chalk.bold(\"plasmo dev --verbose\")}.`\n          )\n        )\n      }\n      event.diagnostics.forEach((diagnostic) => {\n        eLog(chalk.redBright(diagnostic.message))\n        if (diagnostic.stack) {\n          vLog(diagnostic.stack)\n        }\n\n        diagnostic.hints?.forEach((hint) => {\n          vLog(hint)\n        })\n\n        diagnostic.codeFrames?.forEach((codeFrame) => {\n          if (codeFrame.code) {\n            vLog(codeFrame.code)\n          }\n          codeFrame.codeHighlights.forEach((codeHighlight) => {\n            if (codeHighlight.message) {\n              vLog(codeHighlight.message)\n            }\n\n            vLog(\n              chalk.underline(\n                `${codeFrame.filePath}:${codeHighlight.start.line}:${codeHighlight.start.column}`\n              )\n            )\n          })\n        })\n      })\n    }\n    process.env.__PLASMO_FRAMEWORK_INTERNAL_WATCHER_STARTED = \"true\"\n  })\n\n  const cleanup = () => {\n    projectWatcher?.unsubscribe()\n    bundlerWatcher.unsubscribe()\n  }\n\n  process.on(\"SIGINT\", cleanup)\n  process.on(\"SIGTERM\", cleanup)\n}\n\nexport default dev\n"
  },
  {
    "path": "cli/plasmo/src/commands/help.ts",
    "content": "import { printHeader, printHelp } from \"~features/helpers/print\"\n\nasync function help() {\n  printHeader()\n  printHelp()\n}\n\nexport default help\n"
  },
  {
    "path": "cli/plasmo/src/commands/index.ts",
    "content": "export const runMap = {\n  help: () => import(\"./help\"),\n\n  //#ifdef !IS_BINARY\n  start: () => import(\"./start\"),\n  init: () => import(\"./init\"),\n  dev: () => import(\"./dev\"),\n  build: () => import(\"./build\"),\n  package: () => import(\"./package\"),\n  //#endif\n\n  version: () => import(\"./version\"),\n  [\"-v\"]: () => import(\"./version\"),\n  [\"--version\"]: () => import(\"./version\")\n}\n\nexport type ValidCommand = keyof typeof runMap\n\nexport const validCommandList = Object.keys(runMap) as ValidCommand[]\n\nexport const validCommandSet = new Set(validCommandList)\n"
  },
  {
    "path": "cli/plasmo/src/commands/init.ts",
    "content": "import { resolve } from \"path\"\nimport { cwd } from \"process\"\nimport { kebabCase } from \"change-case\"\n\nimport { hasFlag } from \"@plasmo/utils/flags\"\nimport { ensureWritableAndEmpty } from \"@plasmo/utils/fs\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { getCommonPath } from \"~features/extension-devtools/common-path\"\nimport { getPackageManager } from \"~features/helpers/package-manager\"\nimport { printHeader } from \"~features/helpers/print\"\nimport { ProjectCreator } from \"~features/project-creator\"\nimport { getRawName } from \"~features/project-creator/get-raw-name\"\nimport { gitInit } from \"~features/project-creator/git-init\"\nimport { installDependencies } from \"~features/project-creator/install-dependencies\"\nimport { printReady } from \"~features/project-creator/print-ready\"\n\nasync function init() {\n  printHeader()\n\n  const isExample = hasFlag(\"--exp\")\n  const rawName = await getRawName()\n\n  const currentDirectory = cwd()\n\n  // For resolving project directory\n  const projectDirectory = resolve(\n    currentDirectory,\n    kebabCase(rawName) || rawName\n  )\n\n  vLog(\"Project directory:\", projectDirectory)\n\n  const commonPath = getCommonPath(projectDirectory)\n\n  vLog(\"Package name:\", commonPath.packageName)\n\n  if (isExample && !commonPath.packageName.startsWith(\"with-\")) {\n    throw new Error(\"Example extensions must have the `with-` prefix\")\n  }\n\n  await ensureWritableAndEmpty(projectDirectory)\n\n  const packageManager = await getPackageManager()\n  vLog(\n    `Using package manager: ${packageManager.name} ${packageManager?.version}`\n  )\n\n  const creator = new ProjectCreator(commonPath, packageManager, isExample)\n  await creator.create()\n\n  await installDependencies(projectDirectory, packageManager)\n\n  await gitInit(commonPath, projectDirectory)\n\n  await printReady(\n    projectDirectory,\n    currentDirectory,\n    commonPath,\n    packageManager\n  )\n}\n\nexport default init\n"
  },
  {
    "path": "cli/plasmo/src/commands/package.ts",
    "content": "import { hasFlag } from \"@plasmo/utils/flags\"\nimport { iLog } from \"@plasmo/utils/logging\"\n\nimport { getBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\nimport { checkNewVersion } from \"~features/framework-update/version-tracker\"\nimport { printHeader } from \"~features/helpers/print\"\nimport { createManifest } from \"~features/manifest-factory/create-manifest\"\nimport { zipBundle } from \"~features/manifest-factory/zip\"\n\nasync function packageCmd() {\n  printHeader()\n  checkNewVersion()\n\n  process.env.NODE_ENV = \"production\"\n\n  iLog(\"Prepare to package the extension bundle...\")\n\n  const bundleConfig = getBundleConfig()\n\n  const plasmoManifest = await createManifest(bundleConfig)\n\n  await zipBundle(plasmoManifest.commonPath, hasFlag(\"--with-source-maps\"))\n}\n\nexport default packageCmd\n"
  },
  {
    "path": "cli/plasmo/src/commands/start.ts",
    "content": "import { iLog } from \"@plasmo/utils/logging\"\n\nasync function start() {\n  iLog(\"Start the extension development...\")\n}\n\nexport default start\n"
  },
  {
    "path": "cli/plasmo/src/commands/version.ts",
    "content": "async function version() {\n  console.log(process.env.APP_VERSION)\n}\n\nexport default version\n"
  },
  {
    "path": "cli/plasmo/src/features/background-service-worker/bgsw-entry.ts",
    "content": "import { relative, resolve } from \"path\"\nimport { ensureDir, outputFile } from \"fs-extra\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\nimport { toPosix } from \"@plasmo/utils/path\"\n\nimport { type PlasmoManifest } from \"~features/manifest-factory/base\"\n\nexport const createBgswEntry = async (\n  { indexFilePath = \"\", withMessaging = false, withMainWorldScript = false },\n  plasmoManifest: PlasmoManifest\n) => {\n  vLog(\"Creating BGSW entry\")\n\n  const bgswStaticDirectory = resolve(\n    plasmoManifest.commonPath.staticDirectory,\n    \"background\"\n  )\n\n  const bgswEntryFilePath = resolve(bgswStaticDirectory, \"index.ts\")\n  const indexImportPath = relative(bgswStaticDirectory, indexFilePath)\n\n  const bgswCode = [\n    withMessaging && `import \"./messaging\"`,\n    indexFilePath && `import \"${toPosix(indexImportPath).slice(0, -3)}\"`,\n    withMainWorldScript && `import \"./main-world-scripts\"`\n  ]\n    .filter(Boolean)\n    .join(\"\\n\")\n\n  await ensureDir(bgswStaticDirectory)\n  await outputFile(bgswEntryFilePath, bgswCode)\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/background-service-worker/bgsw-main-world-script.ts",
    "content": "import { relative, resolve } from \"path\"\nimport { camelCase } from \"change-case\"\nimport { outputFile } from \"fs-extra\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\nimport { toPosix } from \"@plasmo/utils/path\"\n\nimport { type PlasmoManifest } from \"~features/manifest-factory/base\"\n\nexport const createBgswMainWorldInjector = async (\n  plasmoManifest: PlasmoManifest\n) => {\n  try {\n    const outputPath = resolve(\n      plasmoManifest.commonPath.staticDirectory,\n      \"background\",\n      \"main-world-scripts.ts\"\n    )\n\n    const importStatements = plasmoManifest.mainWorldScriptList.map((s) => {\n      const importMetadata = s.js.map((jsPath) => {\n        const importName = camelCase(jsPath)\n        const importPath = `url:${toPosix(\n          relative(plasmoManifest.commonPath.entryManifestPath, jsPath)\n        )}`\n\n        return {\n          importName,\n          importPath\n        }\n      })\n\n      const topImport = importMetadata\n        .map((i) => `import ${i.importName} from \"${i.importPath}\"`)\n        .join(\"\\n\")\n\n      const importScript = importMetadata.map((i) => i.importName)\n\n      const regCsScript = Object.entries(s).reduce(\n        (out, [k, v]) => {\n          if (k !== \"js\") {\n            out[camelCase(k)] = v\n          }\n          return out\n        },\n        { id: importScript.join(\"-\"), js: [] }\n      )\n\n      const regCsScriptCode = JSON.stringify(regCsScript).replace(\n        /\"js\":\\[\\]/,\n        `\"js\":[${importScript\n          .map((imSrc) => `${imSrc}.split(\"/\").pop().split(\"?\")[0]`)\n          .join(\",\")}]`\n      )\n\n      return [topImport, regCsScriptCode] as const\n    })\n\n    if (importStatements.length === 0) {\n      return false\n    }\n\n    plasmoManifest.permissionSet.add(\"scripting\")\n\n    const code = `${importStatements.map(([top]) => top).join(\"\\n\")}\nchrome.scripting.registerContentScripts([\n  ${importStatements.map(([, reg]) => reg).join(\",\\n  \")}\n]).catch(_ => {})\n`\n\n    await outputFile(outputPath, code)\n\n    return true\n  } catch (e) {\n    vLog(e.message)\n    return false\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/background-service-worker/bgsw-messaging-declaration.ts",
    "content": "import { resolve } from \"path\"\nimport { outputFile } from \"fs-extra\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\n\nexport const MESSAGING_DECLARATION = `messaging` as const\n\nconst MESSAGING_DECLARATION_FILENAME = `${MESSAGING_DECLARATION}.d.ts`\n\nexport const outputMessagingDeclaration = (\n  commonPath: CommonPath,\n  declarationCode: string\n) =>\n  outputFile(\n    resolve(commonPath.dotPlasmoDirectory, MESSAGING_DECLARATION_FILENAME),\n    declarationCode\n  )\n\nexport const createDeclarationCode = (messages: string[], ports: string[]) => `\nimport \"@plasmohq/messaging\"\n\ninterface MmMetadata {\n\\t${messages.join(\"\\n\\t\")}\n}\n\ninterface MpMetadata {\n\\t${ports.join(\"\\n\\t\")}\n}\n\ndeclare module \"@plasmohq/messaging\" {\n  interface MessagesMetadata extends MmMetadata {}\n  interface PortsMetadata extends MpMetadata {}\n}\n`\n"
  },
  {
    "path": "cli/plasmo/src/features/background-service-worker/bgsw-messaging.ts",
    "content": "import { camelCase } from \"change-case\"\nimport glob from \"fast-glob\"\nimport { outputFile } from \"fs-extra\"\nimport { join, resolve } from \"path\"\n\nimport { isWriteable } from \"@plasmo/utils/fs\"\nimport { vLog, wLog } from \"@plasmo/utils/logging\"\nimport { toPosix } from \"@plasmo/utils/path\"\n\nimport {\n  createDeclarationCode,\n  outputMessagingDeclaration\n} from \"~features/background-service-worker/bgsw-messaging-declaration\"\nimport { getRevHash } from \"~features/helpers/crypto\"\nimport { type PlasmoManifest } from \"~features/manifest-factory/base\"\n\nconst state = {\n  md5Hash: \"\"\n}\n\n// TODO: cache these?\nconst createEntryCode = (\n  importSection: string,\n  messageSection: string,\n  externalMessageSection: string,\n  portSection: string\n) => `// @ts-nocheck\nglobalThis.__plasmoInternalPortMap = new Map()\n\n${importSection}\n\nchrome.runtime.onMessageExternal.addListener((request, sender, sendResponse) => {\n  switch (request?.name) {\n    ${externalMessageSection}\n    default:\n      break\n  }\n\n  return true\n})\n\nchrome.runtime.onMessage.addListener((request, sender, sendResponse) => {\n  switch (request.name) {\n    ${messageSection}\n    default:\n      break\n  }\n\n  return true\n})\n\nchrome.runtime.onConnect.addListener(function(port) {\n  globalThis.__plasmoInternalPortMap.set(port.name, port)\n  port.onMessage.addListener(function(request) {\n    switch (port.name) {\n      ${portSection}\n      default:\n        break\n    }\n  })\n})\n\n`\n\nconst getHandlerList = async (\n  plasmoManifest: PlasmoManifest,\n  dirName: \"messages\" | \"messages/external\" | \"ports\"\n) => {\n  const handlerDir = join(\n    plasmoManifest.projectPath.backgroundDirectory,\n    dirName\n  )\n\n  if (!(await isWriteable(handlerDir))) {\n    return []\n  }\n\n  const handlerFileList = await glob(\"**/*.ts\", {\n    cwd: handlerDir,\n    onlyFiles: true,\n    ignore: dirName === \"messages\" ? [\"external\"] : []\n  })\n\n  return handlerFileList.map((filePath) => {\n    const posixFilePath = toPosix(filePath)\n    const handlerName = posixFilePath.slice(0, -3)\n    const importPath = `${dirName}/${handlerName}`\n    const importName = camelCase(importPath)\n\n    return {\n      importName,\n      name: handlerName,\n      declaration: `\"${handlerName}\" : {}`,\n      importCode: `import { default as ${importName} } from \"~background/${importPath}\"`\n    }\n  })\n}\n\nconst getMessageCode = (name: string, importName: string) => `case \"${name}\":\n  ${importName}({\n    ...request,\n    sender\n  }, {\n    send: (p) => sendResponse(p)\n  })\n  break`\n\nconst getPortCode = (name: string, importName: string) => `case \"${name}\":\n  ${importName}({\n    port,\n    ...request\n  }, {\n    send: (p) => port.postMessage(p)\n  })\n  break`\n\nexport const createBgswMessaging = async (plasmoManifest: PlasmoManifest) => {\n  try {\n    const handlerLists = await Promise.all([\n      getHandlerList(plasmoManifest, \"messages\"),\n      getHandlerList(plasmoManifest, \"messages/external\"),\n      getHandlerList(plasmoManifest, \"ports\")\n    ])\n\n    const [messageHandlerList, externalMessageHandlerList, portHandlerList] =\n      handlerLists\n\n    vLog({ messageHandlerList, externalMessageHandlerList, portHandlerList })\n\n    if (handlerLists.every((list) => list.length === 0)) {\n      return false\n    }\n\n    // check if package.json has messaging API\n    if (!(\"@plasmohq/messaging\" in plasmoManifest.dependencies)) {\n      wLog(\"@plasmohq/messaging is not installed, skipping messaging API\")\n      return false\n    }\n\n    const declarationCode = createDeclarationCode(\n      messageHandlerList.map(({ declaration }) => declaration),\n      portHandlerList.map(({ declaration }) => declaration)\n    )\n\n    const declarationMd5Hash = getRevHash(Buffer.from(declarationCode))\n\n    if (state.md5Hash === declarationMd5Hash) {\n      return true\n    }\n\n    state.md5Hash = declarationMd5Hash\n\n    const entryCode = createEntryCode(\n      [...messageHandlerList, ...externalMessageHandlerList, ...portHandlerList]\n        .map((code) => code.importCode)\n        .join(\"\\n\"),\n      messageHandlerList\n        .map((code) => getMessageCode(code.name, code.importName))\n        .join(\"\\n\"),\n      externalMessageHandlerList\n        .map((code) => getMessageCode(code.name, code.importName))\n        .join(\"\\n\"),\n      portHandlerList\n        .map((code) => getPortCode(code.name, code.importName))\n        .join(\"\\n\")\n    )\n\n    await Promise.all([\n      outputFile(\n        resolve(\n          plasmoManifest.commonPath.staticDirectory,\n          \"background\",\n          \"messaging.ts\"\n        ),\n        entryCode\n      ),\n      outputMessagingDeclaration(plasmoManifest.commonPath, declarationCode)\n    ])\n\n    return true\n  } catch (e) {\n    vLog(e.message)\n    return false\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/background-service-worker/update-bgsw-entry.ts",
    "content": "import { find } from \"@plasmo/utils/array\"\nimport { isAccessible } from \"@plasmo/utils/fs\"\n\nimport { createBgswEntry } from \"~features/background-service-worker/bgsw-entry\"\nimport { createBgswMainWorldInjector } from \"~features/background-service-worker/bgsw-main-world-script\"\nimport { createBgswMessaging } from \"~features/background-service-worker/bgsw-messaging\"\nimport { type PlasmoManifest } from \"~features/manifest-factory/base\"\n\nexport const updateBgswEntry = async (plasmoManifest: PlasmoManifest) => {\n  const [bgswIndexFilePath, withMessaging, withMainWorldScript] =\n    await Promise.all([\n      find(plasmoManifest.projectPath.backgroundIndexList, isAccessible),\n      createBgswMessaging(plasmoManifest),\n      createBgswMainWorldInjector(plasmoManifest)\n    ] as const)\n\n  const hasBgsw =\n    Boolean(bgswIndexFilePath) || withMessaging || withMainWorldScript\n\n  if (hasBgsw) {\n    await createBgswEntry(\n      {\n        indexFilePath: bgswIndexFilePath,\n        withMessaging,\n        withMainWorldScript\n      },\n      plasmoManifest\n    )\n  }\n\n  return plasmoManifest.toggleBackground(hasBgsw)\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/env/env-config.ts",
    "content": "// Forked from https://github.com/vercel/next.js/blob/canary/packages/next-env/index.ts\nimport { readFile } from \"fs/promises\"\nimport { resolve } from \"path\"\nimport { constantCase } from \"change-case\"\nimport dotenv from \"dotenv\"\nimport { expand as dotenvExpand } from \"dotenv-expand\"\n\nimport { isFile, isReadable } from \"@plasmo/utils/fs\"\nimport { eLog, iLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { getFlagMap } from \"~features/helpers/flag\"\n\nexport type Env = Record<string, string | undefined>\ntype LoadedEnvFiles = Array<{\n  name: string\n  contents: string\n}>\n\nexport const INTERNAL_ENV_PREFIX = \"PLASMO_\"\nexport const PUBLIC_ENV_PREFIX = \"PLASMO_PUBLIC_\"\n\nconst envFileSet = new Set<string>()\n\nexport class PlasmoPublicEnv {\n  data: Env\n\n  constructor(_env: Env) {\n    this.data = Object.keys(_env)\n      .filter((k) => k.startsWith(PUBLIC_ENV_PREFIX))\n      .reduce((env, key) => {\n        env[key] = _env[key]\n        return env\n      }, {} as Env)\n  }\n\n  extends(rawData: Env) {\n    iLog(\"Loaded environment variables from:\", [...envFileSet])\n    const clone = new PlasmoPublicEnv({ ...this.data })\n    clone.data[\"NODE_ENV\"] = process.env.NODE_ENV\n    Object.entries(rawData).forEach(([key, value]) => {\n      clone.data[`${INTERNAL_ENV_PREFIX}${constantCase(key)}`] = value\n    })\n    return clone\n  }\n}\n\nfunction cascadeEnv(loadedEnvFiles: LoadedEnvFiles) {\n  const parsed: dotenv.DotenvParseOutput = Object.assign({}, process.env)\n\n  for (const { contents, name } of loadedEnvFiles) {\n    try {\n      envFileSet.add(name)\n      const result = dotenvExpand({\n        ignoreProcessEnv: true,\n        parsed: dotenv.parse(contents)\n      })\n\n      if (!!result.parsed) {\n        vLog(`Loaded env from ${name}`)\n        const resultData = result.parsed || {}\n\n        for (const [envKey, envValue] of Object.entries(resultData)) {\n          if (typeof parsed[envKey] === \"undefined\") {\n            try {\n              parsed[envKey] = maybeParseJSON(envValue)\n            } catch (ex) {\n              eLog(`Failed to parse JSON directive ${envKey} in ${name}:`, ex.message)\n            }\n\n            // Pass through internal env variables\n            if (envKey.startsWith(INTERNAL_ENV_PREFIX)) {\n              process.env[envKey] = envValue\n            }\n          }\n        }\n      }\n    } catch (err) {\n      eLog(`Failed to load env from ${name}`, err)\n    }\n  }\n\n  return parsed\n}\n\nconst JSON_DIRECTIVE_RE = /^\\s*json\\((.+)\\)\\s*$/si\n\nfunction maybeParseJSON(value: string): any {\n  const match = value.match(JSON_DIRECTIVE_RE)\n  return match ? JSON.parse(match[1]) : value\n}\n\nexport const setInternalEnv = (env: Record<string, string>) => {\n  for (const [key, value] of Object.entries(env)) {\n    process.env[`${INTERNAL_ENV_PREFIX}${constantCase(key)}`] = value\n  }\n}\n\nexport const getEnvFileNames = () => {\n  const nodeEnv = process.env.NODE_ENV\n  const flagMap = getFlagMap()\n  return [\n    flagMap.envPath,\n\n    `.env.${flagMap.browser}.local`,\n    `.env.${flagMap.tag}.local`,\n    `.env.${nodeEnv}.local`,\n\n    // Don't include `.env.local` for `test` environment\n    // since normally you expect tests to produce the same\n    // results for everyone\n    nodeEnv !== \"test\" ? `.env.local` : \"\",\n\n    `.env.${flagMap.browser}`,\n    `.env.${flagMap.tag}`,\n    `.env.${nodeEnv}`,\n    \".env\"\n  ].filter((s) => !!s)\n}\n\nexport async function loadEnvConfig(dir: string) {\n  const allDotEnvEntries = await Promise.all(\n    getEnvFileNames()\n      .map((envFile) => [envFile, resolve(dir, envFile)])\n      .map(\n        async ([envFile, filePath]) =>\n          [\n            envFile,\n            filePath,\n            (await isFile(filePath)) && (await isReadable(filePath))\n          ] as const\n      )\n  )\n\n  const envFiles: LoadedEnvFiles = await Promise.all(\n    allDotEnvEntries\n      .filter(([, , isValid]) => isValid)\n      .map(async ([envFile, filePath]) => ({\n        name: envFile,\n        contents: await readFile(filePath, \"utf8\")\n      }))\n  )\n\n  const combinedEnv = cascadeEnv(envFiles)\n\n  const plasmoPublicEnv = new PlasmoPublicEnv(combinedEnv)\n\n  return { combinedEnv, plasmoPublicEnv, loadedEnvFiles: envFiles }\n}\n\nexport type EnvConfig = Awaited<ReturnType<typeof loadEnvConfig>>\n"
  },
  {
    "path": "cli/plasmo/src/features/env/env-declaration.ts",
    "content": "import { resolve } from \"path\"\nimport { outputFile } from \"fs-extra\"\n\nimport type { PlasmoManifest } from \"~features/manifest-factory/base\"\n\nexport const PROCESS_ENV_DECLARATION = `process.env` as const\nconst PROCESS_ENV_DECLARATION_FILENAME = `${PROCESS_ENV_DECLARATION}.d.ts`\n\nconst createDeclarationCode = (envKeys: string[]) => `\ndeclare namespace NodeJS {\n  interface ProcessEnv {\n${envKeys.map((e) => `\\t\\t${e}?: string`).join(\"\\n\")}\n  }\n}\n`\n\nexport async function outputEnvDeclaration({\n  commonPath,\n  publicEnv\n}: PlasmoManifest) {\n  const envKeys = Object.keys(publicEnv.data)\n\n  if (envKeys.length === 0) {\n    return\n  }\n\n  await outputFile(\n    resolve(commonPath.dotPlasmoDirectory, PROCESS_ENV_DECLARATION_FILENAME),\n    createDeclarationCode(envKeys)\n  )\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/common-path.ts",
    "content": "import { existsSync } from \"fs\"\nimport { basename, resolve } from \"path\"\nimport { cwd } from \"process\"\n\nimport { getFlagMap } from \"~features/helpers/flag\"\n\nexport const getCommonPath = (projectDirectory = cwd()) => {\n  const flagMap = getFlagMap()\n\n  process.env.PLASMO_PROJECT_DIR = projectDirectory\n\n  const packageName = basename(projectDirectory)\n\n  process.env.PLASMO_SRC_PATH = flagMap.srcPath\n\n  const srcDirectory = resolve(projectDirectory, flagMap.srcPath)\n\n  process.env.PLASMO_SRC_DIR = existsSync(srcDirectory)\n    ? srcDirectory\n    : projectDirectory\n\n  process.env.PLASMO_BUILD_PATH = flagMap.buildPath\n\n  const buildDirectory = resolve(projectDirectory, flagMap.buildPath)\n\n  process.env.PLASMO_BUILD_DIR = buildDirectory\n\n  const distDirectoryName = `${flagMap.target}-${flagMap.tag}`\n\n  const distDirectory = resolve(buildDirectory, distDirectoryName)\n\n  const dotPlasmoDirectory = resolve(projectDirectory, \".plasmo\")\n\n  const cacheDirectory = resolve(dotPlasmoDirectory, \"cache\")\n\n  return {\n    packageName,\n    projectDirectory,\n\n    buildDirectory,\n    distDirectory,\n    distDirectoryName,\n\n    sourceDirectory: process.env.PLASMO_SRC_DIR,\n    packageFilePath: resolve(projectDirectory, \"package.json\"),\n    gitIgnorePath: resolve(projectDirectory, \".gitignore\"),\n    assetsDirectory: resolve(projectDirectory, \"assets\"),\n    parcelConfig: resolve(projectDirectory, \".parcelrc\"),\n\n    dotPlasmoDirectory,\n    cacheDirectory,\n\n    plasmoVersionFilePath: resolve(cacheDirectory, \"plasmo.version.json\"),\n\n    staticDirectory: resolve(dotPlasmoDirectory, \"static\"),\n    genAssetsDirectory: resolve(dotPlasmoDirectory, \"gen-assets\"),\n    entryManifestPath: resolve(\n      dotPlasmoDirectory,\n      `${flagMap.target}.plasmo.manifest.json`\n    )\n  }\n}\n\nexport type CommonPath = ReturnType<typeof getCommonPath>\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/content-script-config.ts",
    "content": "import { readFile } from \"fs/promises\"\nimport typescript, { type Node, type VariableDeclaration } from \"typescript\"\n\nimport type { ManifestContentScript } from \"@plasmo/constants\"\nimport { eLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { parseAst } from \"./parse-ast\"\n\nconst {\n  ScriptTarget,\n  SyntaxKind,\n  createSourceFile,\n  isObjectLiteralExpression,\n  isVariableStatement\n} = typescript\n\nexport const extractContentScriptConfig = async (path: string) => {\n  try {\n    const sourceContent = await readFile(path, \"utf8\")\n    if (sourceContent.length === 0) {\n      return {\n        isEmpty: true\n      }\n    }\n\n    const sourceFile = createSourceFile(\n      path,\n      sourceContent,\n      ScriptTarget.Latest,\n      true\n    )\n\n    const variableDeclarationMap = sourceFile.statements\n      .filter(isVariableStatement)\n      .reduce(\n        (output, node) => {\n          node.declarationList.forEachChild((vd: VariableDeclaration) => {\n            output[vd.name.getText()] = vd.initializer\n          })\n\n          return output\n        },\n        {} as Record<string, Node>\n      )\n\n    const configAST = variableDeclarationMap[\"config\"]\n\n    if (!configAST || !isObjectLiteralExpression(configAST)) {\n      return null\n    }\n\n    const config = configAST.properties.reduce((output, node) => {\n      if (node.getChildCount() < 3) {\n        return output\n      }\n\n      const [keyNode, _, valueNode] = node.getChildren()\n\n      const key = keyNode.getText()\n\n      try {\n        if (valueNode.kind === SyntaxKind.Identifier) {\n          output[key] = parseAst(variableDeclarationMap[valueNode.getText()])\n        } else {\n          output[key] = parseAst(valueNode)\n        }\n      } catch (error) {\n        eLog(error)\n      }\n\n      return output\n    }, {} as ManifestContentScript)\n\n    vLog(\"Parsed config:\", config)\n\n    return {\n      config\n    }\n  } catch (error) {\n    vLog(error)\n    return null\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/generate-icons.ts",
    "content": "import { basename, resolve } from \"path\"\nimport { copy, ensureDir } from \"fs-extra\"\nimport sharp from \"sharp\"\n\nimport { find } from \"@plasmo/utils/array\"\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport { getFlagMap } from \"~features/helpers/flag\"\n\nimport type { CommonPath } from \"./common-path\"\n\nconst getIconNameVariants = (size = 512 as string | number, name = \"icon\") => [\n  `${name}${size}`,\n  `${name}-${size}`,\n  `${name}-${size}x${size}`\n]\n\n// Prefer icon -> medium -> large icon\nconst baseIconNames = [\n  \"icon\",\n  ...getIconNameVariants(),\n  ...getIconNameVariants(1024)\n]\n\n/**\n * We pick icon in this order\n * 1. tag based icon\n * 2. env and tag based icon\n * 3. plain icon\n *\n * */\nconst getPrioritizedIconPaths = (iconNames = baseIconNames) => {\n  const flagMap = getFlagMap()\n\n  return iconNames\n    .map((name) => [\n      `${name}.${flagMap.tag}.${process.env.NODE_ENV}.png`,\n      `${name}.${process.env.NODE_ENV}.png`,\n      `${name}.${flagMap.tag}.png`,\n      `${name}.png`\n    ])\n    .flat()\n}\n\nconst iconSizeList = [128, 64, 48, 32, 16]\n\n// Use this to cache the path resolving result\nconst iconState = {\n  baseIconPaths: [] as string[],\n  devProvidedIcons: {} as Record<string, string[]>\n}\n\n/**\n * Generate manifest icons from an icon in the assets directory\n * - One icon will be picked in the set { `icon`, `icon512`, `icon-512`, `icon-512x512`, `icon1024`, `icon-1024`, `icon-1024x1024` }\n * - Optionally, it will resolve `process.env.NODE_ENV` suffix, e.g: `icon.development.png`, `icon.production.png`\n * - The suffix is prioritized. Thus, if `icon512.development.png` exists, it will be picked over `icon512.png`\n * - Only png is supported\n */\nexport async function generateIcons({\n  assetsDirectory,\n  genAssetsDirectory\n}: CommonPath) {\n  // Precalculate the base icon paths\n  if (iconState.baseIconPaths.length === 0) {\n    const iconNameList = getPrioritizedIconPaths()\n    iconState.baseIconPaths = iconNameList.map((name) =>\n      resolve(assetsDirectory, name)\n    )\n  }\n\n  const baseIconPath = await find(iconState.baseIconPaths, isAccessible)\n\n  if (baseIconPath === undefined) {\n    wLog(\"No icon found in assets directory\")\n    return\n  }\n\n  await ensureDir(genAssetsDirectory)\n\n  const baseIcon = sharp(baseIconPath)\n  const baseIconBuffer = await baseIcon.toBuffer()\n\n  vLog(`${baseIconPath} found, creating resized icons`)\n\n  await Promise.all(\n    iconSizeList.map(async (width) => {\n      if (iconState.devProvidedIcons[width] === undefined) {\n        const devIconPath = getPrioritizedIconPaths(getIconNameVariants(width))\n        iconState.devProvidedIcons[width] = devIconPath.map((name) =>\n          resolve(assetsDirectory, name)\n        )\n      }\n\n      const devProvidedIcon = await find(\n        iconState.devProvidedIcons[width],\n        isAccessible\n      )\n\n      const generatedIconPath = resolve(\n        genAssetsDirectory,\n        `icon${width}.plasmo.png`\n      )\n\n      if (process.env.NODE_ENV === \"development\") {\n        if (devProvidedIcon !== undefined) {\n          if (basename(devProvidedIcon).includes(\".development.\")) {\n            return copy(devProvidedIcon, generatedIconPath)\n          } else {\n            return sharp(devProvidedIcon).grayscale().toFile(generatedIconPath)\n          }\n        } else {\n          return sharp(Buffer.from(baseIconBuffer))\n            .resize({ width, height: width })\n            .greyscale(!basename(baseIconPath).includes(\".development.\"))\n            .toFile(generatedIconPath)\n        }\n      } else {\n        return devProvidedIcon !== undefined\n          ? copy(devProvidedIcon, generatedIconPath)\n          : sharp(Buffer.from(baseIconBuffer))\n              .resize({ width, height: width })\n              .toFile(generatedIconPath)\n      }\n    })\n  )\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/get-bundle-config.ts",
    "content": "import { getFlagMap } from \"~features/helpers/flag\"\n\nexport const getBundleConfig = () => {\n  const flagMap = getFlagMap()\n  const { target, tag } = flagMap\n  const [browser, manifestVersion] = target.split(\"-\")\n\n  // Potential runtime config here\n  return {\n    tag,\n    target,\n    browser,\n    manifestVersion\n  }\n}\n\nexport type PlasmoBundleConfig = ReturnType<typeof getBundleConfig>\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/git-ignore.ts",
    "content": "export const generateGitIgnore = () => `\n# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n#cache\n.turbo\n.next\n.vercel\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n\n# local env files\n.env*\n\nout/\nbuild/\ndist/\n\n# plasmo - https://www.plasmo.com\n.plasmo\n\n# bpp - https://github.com/marketplace/actions/browser-platform-publisher\nkeys.json\n\n# typescript\n.tsbuildinfo\n`\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/package-file.ts",
    "content": "import { userInfo } from \"os\"\nimport { sentenceCase } from \"change-case\"\nimport getPackageJson, { type AbbreviatedVersion } from \"package-json\"\n\nimport type { ExtensionManifestV3 } from \"@plasmo/constants\"\n\nimport type { PackageManagerInfo } from \"~features/helpers/package-manager\"\n\nconst _generatePackage = async ({\n  name = \"plasmo-extension\",\n  version = \"0.0.1\",\n  packageManager = {} as PackageManagerInfo\n}) => {\n  const baseData = {\n    name,\n    displayName: sentenceCase(name),\n    version,\n    description: \"A basic Plasmo extension.\",\n    author: userInfo().username,\n\n    packageManager: undefined as string | undefined,\n    scripts: {\n      dev: \"plasmo dev\",\n      build: \"plasmo build\",\n      package: \"plasmo package\"\n    },\n    dependencies: {\n      plasmo: \"workspace:*\",\n      react: \"*\",\n      \"react-dom\": \"*\"\n    } as Record<string, string>,\n    devDependencies: {\n      \"@types/chrome\": \"*\",\n      \"@types/node\": \"*\",\n      \"@types/react\": \"*\",\n      \"@types/react-dom\": \"*\",\n      prettier: \"*\",\n      typescript: \"*\"\n    } as Record<string, string>,\n    manifest: {\n      // permissions: [] as ValidManifestPermission[],\n      host_permissions: [\"https://*/*\"]\n    } as Partial<ExtensionManifestV3>\n  }\n\n  if (!packageManager || !packageManager.version) {\n    delete baseData.packageManager\n  } else {\n    baseData.packageManager = `${packageManager.name}@${packageManager.version}`\n  }\n\n  return baseData\n}\n\nexport type PackageJSON = Awaited<ReturnType<typeof _generatePackage>> & {\n  homepage?: string\n  contributors?: string[]\n  peerDependencies?: Record<string, string>\n}\n\ntype GenerateArgs = Parameters<typeof _generatePackage>[0]\n\nexport const generatePackage = async (p: GenerateArgs) =>\n  (await _generatePackage(p)) as PackageJSON\n\nexport const resolveWorkspaceToLatestSemver = async (\n  dependencies: Record<string, string>\n) => {\n  const output = {} as Record<string, string>\n\n  await Promise.all(\n    Object.entries(dependencies).map(async ([key, value]) => {\n      if (key === \"plasmo\") {\n        output[key] = process.env.APP_VERSION as string\n      } else if (value === \"workspace:*\") {\n        try {\n          const remotePackageData = (await getPackageJson(key, {\n            version: \"latest\"\n          })) as unknown as AbbreviatedVersion\n          output[key] = remotePackageData.version\n        } catch {\n          output[key] = value\n        }\n      } else {\n        output[key] = value\n      }\n    })\n  )\n\n  return output\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/parse-ast.ts",
    "content": "/**\n * Copyright (c) Plasmo Corp, foss@plasmo.com, MIT Licensed\n * ---\n * Adapted from https://github.com/dword-design/ts-ast-to-literal/blob/master/src/index.js\n * Copyright (c) Sebastian Landwehr info@sebastianlandwehr.com, MIT licensed\n */\nimport typescript, {\n  type ArrayLiteralExpression,\n  type Identifier,\n  type LiteralExpression,\n  type Node,\n  type ObjectLiteralExpression,\n  type PropertyAssignment\n} from \"typescript\"\n\nconst { SyntaxKind } = typescript\n\nexport const parseAst = (node: Node) => {\n  switch (node.kind) {\n    case SyntaxKind.StringLiteral:\n      return (node as LiteralExpression).text\n    case SyntaxKind.TrueKeyword:\n      return true\n    case SyntaxKind.FalseKeyword:\n      return false\n    case SyntaxKind.NullKeyword:\n      return null\n    case SyntaxKind.NumericLiteral:\n      return parseFloat((node as LiteralExpression).text)\n    case SyntaxKind.ArrayLiteralExpression:\n      return (node as ArrayLiteralExpression).elements\n        .filter((node) => node.kind !== SyntaxKind.SpreadElement)\n        .map(parseAst)\n    case SyntaxKind.ObjectLiteralExpression:\n      return (node as ObjectLiteralExpression).properties\n        .filter(\n          (property) =>\n            property.kind === SyntaxKind.PropertyAssignment &&\n            (property.name.kind === SyntaxKind.Identifier ||\n              property.name.kind === SyntaxKind.StringLiteral)\n        )\n        .map((property: PropertyAssignment) => [\n          (property.name as Identifier).escapedText ||\n            (property.name as LiteralExpression).text,\n          parseAst(property.initializer)\n        ])\n        .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})\n    default:\n      return undefined\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/project-path.ts",
    "content": "import { resolve } from \"path\"\n\nimport { getEnvFileNames } from \"~features/env/env-config\"\nimport type { SupportedUiExt } from \"~features/manifest-factory/ui-library\"\n\nimport type { CommonPath } from \"./common-path\"\n\nexport enum WatchReason {\n  None,\n\n  EnvFile,\n\n  PackageJson,\n  AssetsDirectory,\n\n  TabsDirectory,\n\n  BackgroundIndex,\n  BackgroundDirectory,\n\n  ContentScriptIndex,\n  ContentScriptsDirectory,\n\n  NewtabIndex,\n  NewtabHtml,\n\n  SidePanelIndex,\n  SidePanelHtml,\n\n  DevtoolsIndex,\n  DevtoolsHtml,\n\n  PopupIndex,\n  PopupHtml,\n\n  OptionsIndex,\n  OptionsHtml,\n\n  SandboxIndex,\n  SandboxesDirectory\n}\n\ntype DirectoryWatchTuple = [WatchReason, string]\n\nconst getWatchReasonMap = (paths: string[], reason: WatchReason) =>\n  paths.reduce(\n    (output, path) => {\n      output[path] = reason\n      return output\n    },\n    {} as Record<string, WatchReason>\n  )\n\nexport const getProjectPath = (\n  { sourceDirectory, packageFilePath, assetsDirectory }: CommonPath,\n  browserTarget: string,\n  uiExts: SupportedUiExt[]\n) => {\n  /**\n   * only pointing to 1 particular file path\n   */\n  const getModuleList = (moduleName: string) =>\n    [\".ts\", ...uiExts, \".js\"].flatMap((ext) => [\n      resolve(sourceDirectory, `${moduleName}.${browserTarget}${ext}`),\n      resolve(sourceDirectory, `${moduleName}.${process.env.NODE_ENV}${ext}`),\n      resolve(sourceDirectory, `${moduleName}${ext}`)\n    ])\n\n  /**\n   * crawl index, and only care about one extension\n   */\n  const getIndexList = (moduleName: string, exts = [\".ts\", \".js\"]) =>\n    exts.flatMap((ext) => [\n      resolve(sourceDirectory, `${moduleName}.${browserTarget}${ext}`),\n      resolve(sourceDirectory, moduleName, `index.${browserTarget}${ext}`),\n\n      resolve(sourceDirectory, `${moduleName}.${process.env.NODE_ENV}${ext}`),\n      resolve(\n        sourceDirectory,\n        moduleName,\n        `index.${process.env.NODE_ENV}${ext}`\n      ),\n\n      resolve(sourceDirectory, `${moduleName}${ext}`),\n      resolve(sourceDirectory, moduleName, `index${ext}`)\n    ])\n\n  const popupIndexList = getIndexList(\"popup\", uiExts)\n  const optionsIndexList = getIndexList(\"options\", uiExts)\n  const devtoolsIndexList = getIndexList(\"devtools\", uiExts)\n  const newtabIndexList = getIndexList(\"newtab\", uiExts)\n  const sidepanelIndexList = getIndexList(\"sidepanel\", uiExts)\n\n  const popupHtmlList = getIndexList(\"popup\", [\".html\"])\n  const optionsHtmlList = getIndexList(\"options\", [\".html\"])\n  const devtoolsHtmlList = getIndexList(\"devtools\", [\".html\"])\n  const newtabHtmlList = getIndexList(\"newtab\", [\".html\"])\n  const sidepanelHtmlList = getIndexList(\"sidepanel\", [\".html\"])\n\n  const envFileList = getEnvFileNames().map((f) => resolve(sourceDirectory, f))\n\n  const backgroundIndexList = getIndexList(\"background\")\n\n  const contentIndexList = getModuleList(\"content\")\n  const sandboxIndexList = getModuleList(\"sandbox\")\n\n  const watchPathReasonMap = {\n    [packageFilePath]: WatchReason.PackageJson,\n\n    ...getWatchReasonMap(envFileList, WatchReason.EnvFile),\n\n    ...getWatchReasonMap(contentIndexList, WatchReason.ContentScriptIndex),\n    ...getWatchReasonMap(sandboxIndexList, WatchReason.SandboxIndex),\n\n    ...getWatchReasonMap(backgroundIndexList, WatchReason.BackgroundIndex),\n\n    ...getWatchReasonMap(popupIndexList, WatchReason.PopupIndex),\n    ...getWatchReasonMap(optionsIndexList, WatchReason.OptionsIndex),\n    ...getWatchReasonMap(devtoolsIndexList, WatchReason.DevtoolsIndex),\n    ...getWatchReasonMap(newtabIndexList, WatchReason.NewtabIndex),\n    ...getWatchReasonMap(sidepanelIndexList, WatchReason.SidePanelIndex),\n\n    ...getWatchReasonMap(popupHtmlList, WatchReason.PopupHtml),\n    ...getWatchReasonMap(optionsHtmlList, WatchReason.OptionsHtml),\n    ...getWatchReasonMap(devtoolsHtmlList, WatchReason.DevtoolsHtml),\n    ...getWatchReasonMap(newtabHtmlList, WatchReason.NewtabHtml),\n    ...getWatchReasonMap(sidepanelHtmlList, WatchReason.SidePanelHtml)\n  }\n\n  const contentsDirectory = resolve(sourceDirectory, \"contents\")\n  const sandboxesDirectory = resolve(sourceDirectory, \"sandboxes\")\n  const tabsDirectory = resolve(sourceDirectory, \"tabs\")\n  const backgroundDirectory = resolve(sourceDirectory, \"background\")\n  const watchDirectoryEntries = [\n    [WatchReason.SandboxesDirectory, sandboxesDirectory],\n    [WatchReason.TabsDirectory, tabsDirectory],\n    [WatchReason.ContentScriptsDirectory, contentsDirectory],\n    [WatchReason.BackgroundDirectory, backgroundDirectory],\n    [WatchReason.AssetsDirectory, assetsDirectory]\n  ] as Array<DirectoryWatchTuple>\n\n  const knownPathSet = new Set(Object.keys(watchPathReasonMap))\n\n  const entryFileSet = new Set([\n    ...backgroundIndexList,\n    ...contentIndexList,\n    ...sandboxIndexList,\n    ...popupIndexList,\n    ...optionsIndexList,\n    ...devtoolsIndexList,\n    ...newtabIndexList,\n    ...sidepanelIndexList\n  ])\n\n  const isEntryPath = (path: string) => entryFileSet.has(path)\n\n  return {\n    popupIndexList,\n    popupHtmlList,\n\n    optionsIndexList,\n    optionsHtmlList,\n\n    devtoolsIndexList,\n    devtoolsHtmlList,\n\n    newtabIndexList,\n    newtabHtmlList,\n\n    backgroundIndexList,\n    backgroundDirectory,\n\n    contentIndexList,\n    contentsDirectory,\n\n    sidepanelIndexList,\n    sidepanelHtmlList,\n\n    sandboxIndexList,\n    sandboxesDirectory,\n\n    tabsDirectory,\n\n    watchPathReasonMap,\n    watchDirectoryEntries,\n\n    isEntryPath,\n    knownPathSet\n  }\n}\n\nexport type ProjectPath = ReturnType<typeof getProjectPath>\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/project-watcher.ts",
    "content": "import { subscribe, type Event } from \"@parcel/watcher\"\n\nimport { PARCEL_WATCHER_BACKEND } from \"@plasmo/constants/misc\"\nimport { assertUnreachable } from \"@plasmo/utils/assert\"\nimport { hasFlag } from \"@plasmo/utils/flags\"\nimport { iLog, vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport { updateBgswEntry } from \"~features/background-service-worker/update-bgsw-entry\"\nimport { type PlasmoManifest } from \"~features/manifest-factory/base\"\n\nimport { generateIcons } from \"./generate-icons\"\nimport { WatchReason } from \"./project-path\"\n\nconst ignore = [\"node_modules\", \"build\", \".plasmo\", \"coverage\", \".git\"]\n\nexport const createProjectWatcher = async (plasmoManifest: PlasmoManifest) => {\n  if (hasFlag(\"--impulse\")) {\n    return null\n  }\n\n  const { default: isPathInside } = await import(\"is-path-inside\")\n\n  const { knownPathSet, watchPathReasonMap, watchDirectoryEntries } =\n    plasmoManifest.projectPath\n\n  vLog(\"Watching the following files:\", knownPathSet)\n\n  const getWatchReason = (path: string) => {\n    // Quick track processing files already inside watch set\n    if (knownPathSet.has(path)) {\n      return watchPathReasonMap[path]\n    }\n\n    const validEntry = watchDirectoryEntries.find(([, dir]) =>\n      isPathInside(path, dir)\n    )\n\n    if (!validEntry) {\n      return WatchReason.None\n    }\n\n    // Add new path to the watch set if they are watch directories\n    knownPathSet.add(path)\n    return (watchPathReasonMap[path] = validEntry[0])\n  }\n\n  return subscribe(\n    plasmoManifest.commonPath.projectDirectory,\n    async (err, events) => {\n      if (err) {\n        throw err\n      }\n\n      await Promise.all(\n        events.map(({ path, type }) =>\n          handleProjectFile(type, path, getWatchReason(path), plasmoManifest)\n        )\n      )\n\n      await plasmoManifest.write()\n    },\n    {\n      backend: PARCEL_WATCHER_BACKEND,\n      ignore\n    }\n  )\n}\n\nexport const handleProjectFile = async (\n  type: Event[\"type\"],\n  path: string,\n  reason: WatchReason,\n  plasmoManifest: PlasmoManifest\n) => {\n  const isEnabled = type !== \"delete\"\n\n  switch (reason) {\n    case WatchReason.None: {\n      return\n    }\n    case WatchReason.EnvFile: {\n      wLog(\"Environment file change detected, please restart the dev server.\")\n      await plasmoManifest.updateEnv()\n      return\n    }\n    case WatchReason.AssetsDirectory: {\n      iLog(\"Assets directory changed, update dynamic assets\")\n      await generateIcons(plasmoManifest.commonPath)\n      return\n    }\n    case WatchReason.PackageJson: {\n      iLog(\n        \"package.json changed, updating manifest overrides. You might need to restart the dev server.\"\n      )\n      await plasmoManifest.updatePackageData()\n      return\n    }\n    case WatchReason.BackgroundDirectory:\n    case WatchReason.BackgroundIndex: {\n      await updateBgswEntry(plasmoManifest) // TODO: Make this a soft-check instead of a file-write ops\n      return\n    }\n    case WatchReason.ContentScriptIndex:\n    case WatchReason.ContentScriptsDirectory: {\n      await plasmoManifest.toggleContentScript(path, isEnabled)\n\n      if (plasmoManifest.hasMainWorldScript) {\n        await updateBgswEntry(plasmoManifest)\n      }\n      return\n    }\n\n    case WatchReason.SandboxIndex:\n    case WatchReason.SandboxesDirectory:\n    case WatchReason.TabsDirectory: {\n      await plasmoManifest.togglePage(path, isEnabled)\n      return\n    }\n\n    case WatchReason.SidePanelIndex: {\n      plasmoManifest.toggleSidePanel(isEnabled)\n      return\n    }\n\n    case WatchReason.PopupIndex: {\n      plasmoManifest.togglePopup(isEnabled)\n      return\n    }\n    case WatchReason.OptionsIndex: {\n      plasmoManifest.toggleOptions(isEnabled)\n      return\n    }\n    case WatchReason.DevtoolsIndex: {\n      plasmoManifest.toggleDevtools(isEnabled)\n      return\n    }\n    case WatchReason.NewtabIndex: {\n      plasmoManifest.toggleNewtab(isEnabled)\n      return\n    }\n\n    case WatchReason.PopupHtml: {\n      await plasmoManifest.scaffolder.createPageHtml(\"popup\", isEnabled && path)\n      return\n    }\n    case WatchReason.OptionsHtml: {\n      await plasmoManifest.scaffolder.createPageHtml(\n        \"options\",\n        isEnabled && path\n      )\n      return\n    }\n    case WatchReason.DevtoolsHtml: {\n      await plasmoManifest.scaffolder.createPageHtml(\n        \"devtools\",\n        isEnabled && path\n      )\n      return\n    }\n    case WatchReason.NewtabHtml: {\n      await plasmoManifest.scaffolder.createPageHtml(\n        \"newtab\",\n        isEnabled && path\n      )\n      return\n    }\n    case WatchReason.SidePanelHtml: {\n      await plasmoManifest.scaffolder.createPageHtml(\n        \"sidepanel\",\n        isEnabled && path\n      )\n      return\n    }\n\n    default:\n      assertUnreachable(reason)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/strip-underscore.ts",
    "content": "import { readdir, readFile, rename, stat, writeFile } from \"fs/promises\"\nimport { join, resolve } from \"path\"\n\nconst stripFileUnderscore = async (filePath: string) => {\n  const fileContents = await readFile(resolve(filePath), \"utf8\")\n  const newFileContents = fileContents.replace(/\\/_/g, \"/\")\n  await writeFile(filePath, newFileContents, \"utf8\")\n}\n\nexport const stripUnderscore = async (dir = \"\") => {\n  const entries = await readdir(dir)\n\n  for (const entry of entries) {\n    const entryPath = join(dir, entry)\n    const entryStat = await stat(entryPath)\n    if (entryStat.isDirectory()) {\n      const newPath = entryPath.replace(\"/_\", \"/\")\n      await rename(entryPath, newPath)\n      await stripUnderscore(newPath)\n    } else {\n      const newPath = entryPath.replace(\"/_\", \"/\")\n      await rename(entryPath, newPath)\n      await stripFileUnderscore(newPath)\n    }\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/template-path.ts",
    "content": "import { dirname, resolve } from \"path\"\nimport { fileURLToPath } from \"url\"\n\nexport const getTemplatePath = () => {\n  const packagePath = dirname(fileURLToPath(import.meta.url))\n\n  const templatePath = resolve(packagePath, \"..\", \"templates\")\n  const staticTemplatePath = resolve(templatePath, \"static\")\n\n  const initTemplatePackagePath = resolve(\n    require.resolve(\"@plasmohq/init\"),\n    \"..\"\n  )\n\n  const initTemplatePath = resolve(initTemplatePackagePath, \"templates\")\n  const initEntryPath = resolve(initTemplatePackagePath, \"entries\")\n  const bppYaml = resolve(initTemplatePackagePath, \"bpp.yml\")\n\n  return {\n    templatePath,\n    initTemplatePath,\n    initEntryPath,\n    staticTemplatePath,\n    bppYaml\n  }\n}\n\nexport type TemplatePath = ReturnType<typeof getTemplatePath>\n"
  },
  {
    "path": "cli/plasmo/src/features/extension-devtools/tsconfig.ts",
    "content": "import { readFile } from \"fs/promises\"\nimport { resolve } from \"path\"\nimport { outputFile, outputJson } from \"fs-extra\"\nimport json5 from \"json5\"\n\nimport { MESSAGING_DECLARATION } from \"~features/background-service-worker/bgsw-messaging-declaration\"\nimport { PROCESS_ENV_DECLARATION } from \"~features/env/env-declaration\"\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\n\nconst DECLARATION_FILEPATH = `.plasmo/index.d.ts`\n\nconst INDEX_DECLARATION_CODE = [PROCESS_ENV_DECLARATION, MESSAGING_DECLARATION]\n  .map((e) => `import \"./${e}\"`)\n  .join(\"\\n\")\n\nexport const addDeclarationConfig = async (\n  commonPath: CommonPath,\n  filePath: string\n) => {\n  const tsconfigFilePath = resolve(commonPath.projectDirectory, \"tsconfig.json\")\n\n  const tsconfigFile = await readFile(tsconfigFilePath, \"utf8\")\n  const tsconfig = json5.parse(tsconfigFile)\n  const includeSet = new Set(tsconfig.include)\n\n  if (includeSet.has(filePath)) {\n    return\n  }\n\n  tsconfig.include = [filePath, ...includeSet]\n\n  await outputJson(tsconfigFilePath, tsconfig, {\n    spaces: 2\n  })\n}\n\nexport const outputIndexDeclaration = async (commonPath: CommonPath) => {\n  const declarationFilePath = resolve(\n    commonPath.projectDirectory,\n    DECLARATION_FILEPATH\n  )\n\n  await Promise.all([\n    outputFile(declarationFilePath, INDEX_DECLARATION_CODE),\n    addDeclarationConfig(commonPath, DECLARATION_FILEPATH)\n  ])\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extra/cache-busting.ts",
    "content": "import { lstat } from \"fs/promises\"\nimport { resolve } from \"path\"\nimport { emptyDir, ensureDir } from \"fs-extra\"\n\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\n\nexport async function cleanUpDotPlasmo({\n  dotPlasmoDirectory,\n  cacheDirectory\n}: CommonPath) {\n  await emptyDir(dotPlasmoDirectory)\n  await ensureDir(cacheDirectory)\n}\n\nexport async function cleanUpLargeCache(commonPath: CommonPath) {\n  const parcelCacheDbFilePath = resolve(\n    commonPath.cacheDirectory,\n    \"parcel\",\n    \"data.mdb\"\n  )\n\n  const hasCache = await isAccessible(parcelCacheDbFilePath)\n\n  if (!hasCache) {\n    return\n  }\n\n  const cacheDbFileSize = (await lstat(parcelCacheDbFilePath, { bigint: true }))\n    .size\n\n  const sizeInMB = cacheDbFileSize / 1024n ** 2n\n  const sizeInGB = Number(sizeInMB) / 1024\n\n  // TODO: calculate the limit based on some heuristic around the size of the project instead of a fixed value.\n  const cacheLimitGB = 1.47\n\n  if (sizeInGB > cacheLimitGB) {\n    vLog(`Busting large build cache, size: ${sizeInGB.toFixed(2)} GB`)\n    await cleanUpDotPlasmo(commonPath)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/extra/next-new-tab.ts",
    "content": "import { mkdir } from \"fs/promises\"\nimport { resolve } from \"path\"\nimport { sentenceCase } from \"change-case\"\nimport { copy, emptyDir, readJson, writeJson } from \"fs-extra\"\n\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { sLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { getCommonPath } from \"~features/extension-devtools/common-path\"\nimport type { PackageJSON } from \"~features/extension-devtools/package-file\"\nimport { stripUnderscore } from \"~features/extension-devtools/strip-underscore\"\n\nexport const generateNewTabManifest = (packageData: PackageJSON) => ({\n  name: sentenceCase(packageData.name),\n  description: packageData.description,\n  version: packageData.version,\n  manifest_version: 3,\n  chrome_url_overrides: {\n    newtab: \"./index.html\"\n  }\n})\n\nexport const nextNewTab = async () => {\n  const { projectDirectory, packageFilePath } = getCommonPath()\n\n  vLog(\"Creating a Plasmo + Nextjs based new tab extension\")\n  const out = resolve(projectDirectory, \"out\")\n\n  const { default: chalk } = await import(\"chalk\")\n\n  if (!(await isAccessible(out))) {\n    throw new Error(\n      `${chalk.bold(\n        \"out\"\n      )} directory does not exist, did you forget to run \"${chalk.underline(\n        \"next build && next export\"\n      )}\"?`\n    )\n  }\n\n  const packageData: PackageJSON = await readJson(packageFilePath)\n\n  const extensionDirectory = resolve(projectDirectory, \"extension\")\n  if (await isAccessible(extensionDirectory)) {\n    const {\n      default: { prompt }\n    } = await import(\"inquirer\")\n\n    const { answer } = await prompt({\n      type: \"confirm\",\n      name: \"answer\",\n      message: `${chalk.bold(\n        \"extension\"\n      )} directory already exists, do you want to overwrite it?`\n    })\n\n    if (!answer) {\n      throw new Error(\"Aborted\")\n    }\n\n    await emptyDir(extensionDirectory)\n  }\n\n  await mkdir(extensionDirectory)\n  await copy(out, extensionDirectory)\n  vLog(\"Extension created at:\", extensionDirectory)\n\n  await stripUnderscore(extensionDirectory)\n\n  // Create manifest.json with chrome_url_overrides with index.html\n\n  await writeJson(\n    resolve(extensionDirectory, \"manifest.json\"),\n    generateNewTabManifest(packageData),\n    {\n      spaces: 2\n    }\n  )\n\n  sLog(\"Your extension is ready in the extension/ directory\")\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/framework-update/version-tracker.ts",
    "content": "import { readJson, writeJson } from \"fs-extra\"\nimport getPackageJson, { type AbbreviatedVersion } from \"package-json\"\nimport semver from \"semver\"\n\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { aLog, eLog, vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\nimport { cleanUpDotPlasmo } from \"~features/extra/cache-busting\"\nimport { getPackageManager } from \"~features/helpers/package-manager\"\n\nexport const updateVersionFile = async (commonPath: CommonPath) => {\n  const { plasmoVersionFilePath } = commonPath\n\n  if (!(await isAccessible(plasmoVersionFilePath))) {\n    vLog(\"Plasmo version file not found, busting cache...\")\n    await cleanUpDotPlasmo(commonPath)\n  } else {\n    const cachedVersion = await readJson(plasmoVersionFilePath)\n    const semverCachedVersion = semver.coerce(cachedVersion.version)\n    const semverCurrentVersion = semver.coerce(process.env.APP_VERSION)!\n\n    if (\n      !semverCachedVersion ||\n      semverCachedVersion.major < semverCurrentVersion.major ||\n      (semverCachedVersion.major === semverCurrentVersion.major &&\n        semverCachedVersion.minor < semverCurrentVersion.minor)\n    ) {\n      vLog(\"Plasmo updated, busting cache...\")\n      await cleanUpDotPlasmo(commonPath)\n    }\n  }\n\n  await writeJson(plasmoVersionFilePath, { version: process.env.APP_VERSION })\n}\n\nexport const checkNewVersion = async () => {\n  // If the version is different, log a warning about new version is available\n  const currentVersion = process.env.APP_VERSION\n\n  // If the version is different, log a warning about new version is available\n  try {\n    // Get the latest version of plasmo\n    const latestPackageJson = (await getPackageJson(\"plasmo\", {\n      version: \"latest\"\n    })) as unknown as AbbreviatedVersion\n    const latestVersion = latestPackageJson.version\n\n    // If the version is different, log a warning about new version is available\n    if (semver.lt(currentVersion, latestVersion)) {\n      const { default: chalk } = await import(\"chalk\")\n      wLog(\n        chalk.yellowBright(\n          `A new version of plasmo is available: v${latestVersion}`\n        )\n      )\n      const updateCmd = await getUpdateCmd(latestVersion)\n      aLog(chalk.yellow(`Run ${updateCmd} to update`))\n    }\n  } catch (error) {\n    eLog('Error fetching package information for \"plasmo\"', error)\n  }\n}\n\nasync function getUpdateCmd(version = \"\") {\n  const packageManager = await getPackageManager()\n  switch (packageManager.name) {\n    case \"npm\":\n      return `\"npm i -S plasmo@${version}\"`\n    case \"pnpm\":\n      return `\"pnpm i plasmo@${version}\"`\n    case \"yarn\":\n      return `\"yarn add plasmo@${version}\"`\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/create-parcel-bundler.ts",
    "content": "import { dirname, join, resolve } from \"path\"\nimport ParcelFS from \"@parcel/fs\"\nimport ParcelPM from \"@parcel/package-manager\"\nimport { emptyDir, ensureDir, exists, readJson, writeJson } from \"fs-extra\"\n\nimport { getFlag, hasFlag } from \"@plasmo/utils/flags\"\nimport { wLog } from \"@plasmo/utils/logging\"\n\nimport { Parcel, type ParcelOptions } from \"@plasmohq/parcel-core\"\n\nimport type { PlasmoManifest } from \"~features/manifest-factory/base\"\n\nimport { getPackageManager } from \"./package-manager\"\nimport { setInternalEnv } from \"~features/env/env-config\"\n\nconst PackageInstallerMap = {\n  npm: ParcelPM.Npm,\n  yarn: ParcelPM.Yarn,\n  pnpm: ParcelPM.Pnpm\n}\n\nexport const createParcelBuilder = async (\n  { commonPath, bundleConfig, publicEnv }: PlasmoManifest,\n  { defaultTargetOptions = {}, ...options }: ParcelOptions\n) => {\n  const isProd = options.mode === \"production\"\n\n  if (isProd) {\n    await emptyDir(commonPath.distDirectory)\n  } else {\n    await ensureDir(commonPath.distDirectory)\n  }\n\n  process.env.__PLASMO_FRAMEWORK_INTERNAL_NO_MINIFY =\n    isProd && hasFlag(\"--no-minify\") ? \"true\" : \"false\"\n\n  process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS = isProd\n    ? hasFlag(\"--inline-source-maps\")\n      ? \"inline\"\n      : hasFlag(\"--source-maps\")\n      ? \"external\"\n      : \"none\"\n    : hasFlag(\"--no-source-maps\")\n    ? \"none\"\n    : \"inline\"\n\n  process.env.__PLASMO_FRAMEWORK_INTERNAL_NO_CS_RELOAD = hasFlag(\n    \"--no-cs-reload\"\n  )\n    ? \"true\"\n    : \"false\"\n\n  process.env.__PLASMO_FRAMEWORK_INTERNAL_ES_TARGET =\n    (getFlag(\"--es-target\") as any) || \"es2022\"\n\n  const pmInfo = await getPackageManager()\n\n  const inputFS = new ParcelFS.NodeFS()\n\n  const PackageInstaller = PackageInstallerMap[pmInfo.name]\n\n  const packageManager = new ParcelPM.NodePackageManager(\n    inputFS,\n    commonPath.projectDirectory,\n    new PackageInstaller()\n  )\n\n  const baseConfig = require.resolve(\"@plasmohq/parcel-config\")\n\n  let runConfig = join(dirname(baseConfig), \"run.json\")\n\n  const configJson = await readJson(baseConfig)\n\n  if (hasFlag(\"--bundle-buddy\")) {\n    configJson.reporters = [\"...\", \"@parcel/reporter-bundle-buddy\"]\n  }\n\n  await writeJson(runConfig, configJson)\n\n  if (await exists(commonPath.parcelConfig)) {\n    runConfig = commonPath.parcelConfig\n\n    if (isProd) {\n      const customConfig = await readJson(runConfig)\n\n      if (customConfig.extends !== \"@plasmohq/parcel-config\") {\n        wLog(\n          'The .parcelrc does not extend \"@plasmohq/parcel-config\", the result may be unexpected'\n        )\n      }\n    }\n\n    if (hasFlag(\"--bundle-buddy\")) {\n      wLog(\n        'The \"--bundle-buddy\" flag does not work with a custom .parcelrc file'\n      )\n    }\n  }\n\n  const engines = {\n    browsers:\n      bundleConfig.manifestVersion === \"mv2\" &&\n      bundleConfig.browser !== \"firefox\"\n        ? [\"IE 11\"]\n        : [\"last 1 Chrome version\"]\n  }\n\n  setInternalEnv(bundleConfig)\n\n  const bundler = new Parcel({\n    inputFS,\n    packageManager,\n    entries: commonPath.entryManifestPath,\n    cacheDir: resolve(commonPath.cacheDirectory, \"parcel\"),\n    config: runConfig,\n    shouldAutoInstall: true,\n\n    env: publicEnv.extends(bundleConfig).data,\n\n    defaultTargetOptions: {\n      ...defaultTargetOptions,\n      engines,\n      sourceMaps:\n        process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS !== \"none\",\n      distDir: commonPath.distDirectory\n    },\n\n    ...options\n  })\n\n  return bundler\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/crypto.ts",
    "content": "import { createHash } from \"crypto\"\n\n/**\n * Fast hash for local file revving\n * DO NOT USE FOR SENSITIVE PURPOSES\n * md5 is good enough for file-revving: https://github.com/sindresorhus/rev-hash\n */\nexport const getRevHash = (buff: Buffer) =>\n  createHash(\"md5\").update(buff).digest(\"hex\").slice(0, 18)\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/flag.ts",
    "content": "import { kebabCase } from \"change-case\"\n\nimport { getFlag } from \"@plasmo/utils/flags\"\n\nexport const getFlagMap = () => {\n  const srcPath = getFlag(\"--src-path\") || process.env.PLASMO_SRC_PATH || \"src\"\n\n  const buildPath =\n    getFlag(\"--build-path\") || process.env.PLASMO_BUILD_PATH || \"build\"\n\n  const tag =\n    getFlag(\"--tag\") ||\n    process.env.PLASMO_TAG ||\n    (process.env.NODE_ENV === \"production\" ? \"prod\" : \"dev\")\n\n  const target = kebabCase(\n    getFlag(\"--target\") || process.env.PLASMO_TARGET || \"chrome-mv3\"\n  )\n\n  const [browser, manifestVersion] = target.split(\"-\")\n\n  const entry = getFlag(\"--entry\") || \"popup\"\n\n  const envPath = getFlag(\"--env\")\n\n  return {\n    browser,\n    manifestVersion,\n    tag,\n    srcPath,\n    buildPath,\n    target,\n    entry,\n    envPath\n  }\n}\n\nconst DEV_BUILD_COMMON_ARGS = `      --target=[string]           set the target (default: chrome-mv3)\n      --tag=[string]              set the build tag (default: dev or prod depending on NODE_ENV)\n      --src-path=[path]           set the source path relative to project root (default: src)\n      --build-path=[path]         set the build path relative to project root (default: build)\n      --entry=[name]              entry point name (default: popup)\n      --env=[path]                relative path to top-level env file\n      --no-cs-reload              disable content script auto reloading`\n\nexport const flagHelp = `\n    \n    init\n\n      --entry=[name]              entry files (default: popup)\n      --with-<name>               use an example template\n\n    dev     \n\n${DEV_BUILD_COMMON_ARGS}\n\n    build\n\n${DEV_BUILD_COMMON_ARGS}\n      --zip                       zip the build output\n`\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/loading-animation.ts",
    "content": "const LOADING_TEXT = \"🔄 Building\"\nconst state = {\n  loadingInterval: null as NodeJS.Timeout | null,\n  isLoading: false,\n  dotCount: 0\n}\n\nexport const startLoading = () => {\n  if (state.isLoading) {\n    return\n  }\n  state.isLoading = true\n  process.stdout.write(LOADING_TEXT)\n  state.loadingInterval = setInterval(() => {\n    state.dotCount = (state.dotCount + 1) % 4\n    let dotString = state.dotCount === 0 ? \"   \" : \".\".repeat(state.dotCount)\n    process.stdout.write(`\\r${LOADING_TEXT}${dotString}`)\n  }, 400)\n}\n\nexport const stopLoading = () => {\n  if (!state.isLoading) {\n    return\n  }\n  state.isLoading = false\n  if (state.loadingInterval) {\n    clearInterval(state.loadingInterval)\n    state.loadingInterval = null\n  }\n  // Clear the loading text\n  process.stdout.write(\"\\r\" + \" \".repeat(20) + \"\\r\")\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/package-manager.ts",
    "content": "/**\n * Forked from https://github.com/vercel/next.js/blob/canary/packages/create-next-app/helpers/get-pkg-manager.ts\n */\nimport spawnAsync from \"@expo/spawn-async\"\nimport semver from \"semver\"\n\nexport type PackageManager = \"npm\" | \"pnpm\" | \"yarn\"\n\nexport type PackageManagerInfo = {\n  name: PackageManager\n  version?: string\n}\n\nasync function getPMInfo(name: PackageManager): Promise<PackageManagerInfo> {\n  const data = await spawnAsync(name, [\"--version\"])\n  const version = semver.valid(data.stdout.trim()) || undefined\n  return { name, version }\n}\n\nexport async function getPackageManager(): Promise<PackageManagerInfo> {\n  try {\n    const userAgent = process.env.npm_config_user_agent\n\n    if (userAgent) {\n      if (userAgent.startsWith(\"yarn\")) {\n        return { name: \"yarn\" }\n      } else if (userAgent.startsWith(\"pnpm\")) {\n        return { name: \"pnpm\" }\n      }\n    }\n    try {\n      return await getPMInfo(\"pnpm\")\n    } catch {\n      return await getPMInfo(\"yarn\")\n    }\n  } catch {\n    return await getPMInfo(\"npm\")\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/print.ts",
    "content": "import { cLog } from \"@plasmo/utils/logging\"\n\nimport { validCommandList } from \"~commands\"\nimport { flagHelp } from \"~features/helpers/flag\"\n\nexport const printHeader = () => {\n  console.log(`🟣 Plasmo v${process.env.APP_VERSION}`)\n\n  console.log(\"🔴 The Browser Extension Framework\")\n}\n\nexport const printHelp = () => {\n  cLog(\"🟠 CMDS\", validCommandList.join(\" | \"))\n\n  cLog(\"🟡 OPTS\", flagHelp)\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/prompt.ts",
    "content": "import inquirer from \"inquirer\"\n\nexport const quickPrompt = async (label = \"\", defaultValue = \"\") => {\n  const { data } = await inquirer.prompt({\n    name: \"data\",\n    prefix: \"🟡\",\n    message: label,\n    default: defaultValue\n  })\n\n  return data as string\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/helpers/traverse.ts",
    "content": "import { iLog } from \"@plasmo/utils/logging\"\n\nconst defaultTransformer = (target: any) =>\n  iLog({\n    target\n  })\n\n/**\n * Traverse a target object and apply a transformer function to each value.\n * Retains only defined values.\n */\nexport const definedTraverse = (\n  target: any,\n  transformer = defaultTransformer\n): any => {\n  if (Array.isArray(target)) {\n    return target\n      .map((item) => definedTraverse(item, transformer))\n      .filter((i) => i !== undefined)\n  } else if (typeof target === \"object\") {\n    const result = {} as any\n\n    for (const key in target) {\n      if (target.hasOwnProperty(key)) {\n        result[key] = definedTraverse(target[key], transformer)\n        if (result[key] === undefined) {\n          delete result[key]\n        }\n      }\n    }\n\n    if (Object.keys(result).length === 0) {\n      return undefined\n    }\n\n    return result\n  } else {\n    return transformer(target)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/base.ts",
    "content": "import { ok } from \"assert\"\nimport { readdir } from \"fs/promises\"\nimport {\n  basename,\n  dirname,\n  extname,\n  isAbsolute,\n  join,\n  parse,\n  relative,\n  resolve\n} from \"path\"\nimport { cwd } from \"process\"\nimport glob from \"fast-glob\"\nimport {\n  copy,\n  ensureDir,\n  existsSync,\n  pathExists,\n  readJson,\n  writeJson\n} from \"fs-extra\"\nimport { hasher as createHasher } from \"node-object-hash\"\n\nimport type {\n  ChromeUrlOverrideType,\n  ExtensionManifest,\n  ExtensionManifestV3,\n  ManifestContentScript,\n  ManifestPermission\n} from \"@plasmo/constants\"\nimport {\n  buildBroadcast,\n  BuildSocketEvent\n} from \"@plasmo/framework-shared/build-socket\"\nimport { assertTruthy } from \"@plasmo/utils/assert\"\nimport { injectEnv } from \"@plasmo/utils/env\"\nimport { isDirectory, isReadable } from \"@plasmo/utils/fs\"\nimport { vLog, wLog } from \"@plasmo/utils/logging\"\nimport { getSubExt, toPosix } from \"@plasmo/utils/path\"\n\nimport { loadEnvConfig, type EnvConfig } from \"~features/env/env-config\"\nimport { outputEnvDeclaration } from \"~features/env/env-declaration\"\nimport {\n  getCommonPath,\n  type CommonPath\n} from \"~features/extension-devtools/common-path\"\nimport { extractContentScriptConfig } from \"~features/extension-devtools/content-script-config\"\nimport { generateIcons } from \"~features/extension-devtools/generate-icons\"\nimport type { PlasmoBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\nimport type { PackageJSON } from \"~features/extension-devtools/package-file\"\nimport {\n  getProjectPath,\n  type ProjectPath\n} from \"~features/extension-devtools/project-path\"\nimport { getTemplatePath } from \"~features/extension-devtools/template-path\"\nimport { outputIndexDeclaration } from \"~features/extension-devtools/tsconfig\"\nimport { cleanUpLargeCache } from \"~features/extra/cache-busting\"\nimport { updateVersionFile } from \"~features/framework-update/version-tracker\"\nimport { definedTraverse } from \"~features/helpers/traverse\"\n\nimport { Scaffolder } from \"./scaffolder\"\nimport {\n  getUiExtMap,\n  getUiLibrary,\n  type UiExtMap,\n  type UiLibrary\n} from \"./ui-library\"\n\nexport const iconMap = {\n  \"16\": \"./gen-assets/icon16.plasmo.png\",\n  \"32\": \"./gen-assets/icon32.plasmo.png\",\n  \"48\": \"./gen-assets/icon48.plasmo.png\",\n  \"64\": \"./gen-assets/icon64.plasmo.png\",\n  \"128\": \"./gen-assets/icon128.plasmo.png\"\n}\n\nexport const autoPermissionList: ManifestPermission[] = [\"storage\"]\n\nconst hasher = createHasher({ trim: true, sort: true })\n\nexport abstract class PlasmoManifest<T extends ExtensionManifest = any> {\n  get browser() {\n    return this.bundleConfig.browser as typeof process.env.PLASMO_BROWSER\n  }\n\n  #commonPath?: CommonPath\n  public get commonPath() {\n    return assertTruthy(this.#commonPath)\n  }\n\n  #projectPath?: ProjectPath\n  public get projectPath() {\n    return assertTruthy(this.#projectPath)\n  }\n\n  readonly templatePath = getTemplatePath()\n\n  #envConfig?: EnvConfig\n\n  public get combinedEnv() {\n    ok(this.#envConfig)\n    return this.#envConfig.combinedEnv\n  }\n\n  public get publicEnv() {\n    ok(this.#envConfig)\n    return this.#envConfig.plasmoPublicEnv\n  }\n\n  #extSet = new Set([\".ts\", \".js\"])\n  #uiExtSet = new Set()\n\n  #uiLibraryData?: {\n    uiLibrary: UiLibrary\n    uiExtMap: UiExtMap\n  }\n\n  get uiLibrary() {\n    ok(this.#uiLibraryData)\n    return this.#uiLibraryData.uiLibrary\n  }\n\n  get mountExt() {\n    ok(this.#uiLibraryData)\n    return this.#uiLibraryData.uiExtMap.mountExt\n  }\n\n  get uiExts() {\n    ok(this.#uiLibraryData)\n    return this.#uiLibraryData.uiExtMap.uiExts\n  }\n\n  #hash = \"\"\n  #prevHash = \"\"\n\n  protected data: Partial<T> = {}\n  protected overideManifest: Partial<T> = {}\n\n  protected packageData?: PackageJSON\n\n  contentScriptMap: Readonly<Map<string, ManifestContentScript>> = new Map()\n\n  get mainWorldScriptList() {\n    const output: ManifestContentScript[] = []\n    for (const script of this.contentScriptMap.values()) {\n      if (script.world === \"MAIN\") {\n        output.push(script)\n      }\n    }\n    return output\n  }\n\n  get hasMainWorldScript() {\n    for (const script of this.contentScriptMap.values()) {\n      if (script.world === \"MAIN\") {\n        return true\n      }\n    }\n    return false\n  }\n\n  readonly copyMap = new Map<string, string>()\n  readonly permissionSet = new Set<ManifestPermission>()\n\n  readonly scaffolder: Scaffolder\n\n  get changed() {\n    return this.#hash !== this.#prevHash\n  }\n\n  get name() {\n    ok(this.packageData)\n    return this.packageData.displayName\n  }\n\n  get dependencies() {\n    ok(this.packageData)\n    // to support npm workspaces (mono repos) we need to fallback to\n    // peerDependencies because dependencies will never exist\n    return this.packageData.dependencies ?? this.packageData.peerDependencies\n  }\n\n  get devDependencies() {\n    ok(this.packageData)\n    return this.packageData.devDependencies\n  }\n\n  get staticScaffoldPath() {\n    ok(this.uiLibrary)\n    return resolve(this.templatePath.staticTemplatePath, this.uiLibrary.path)\n  }\n\n  protected constructor(public bundleConfig: PlasmoBundleConfig) {\n    this.data.icons = iconMap\n    this.scaffolder = new Scaffolder(this)\n  }\n\n  private async initEnv(envRootDirectory = cwd()) {\n    this.#envConfig = await loadEnvConfig(envRootDirectory)\n    this.#commonPath = getCommonPath(envRootDirectory)\n  }\n\n  async startup() {\n    await this.initEnv()\n\n    vLog(`Ensure exists: ${this.commonPath.dotPlasmoDirectory}`)\n    await ensureDir(this.commonPath.dotPlasmoDirectory)\n    await cleanUpLargeCache(this.commonPath)\n    await updateVersionFile(this.commonPath)\n    await generateIcons(this.commonPath)\n\n    await outputIndexDeclaration(this.commonPath)\n    await this.updateEnv()\n\n    await this.updatePackageData()\n  }\n\n  async postBuild() {\n    await Promise.all(\n      Array.from(this.copyMap, async ([dest, src]) => {\n        if (!(await isReadable(dest))) {\n          await copy(src, dest)\n        }\n      })\n    )\n\n    if (!process.env.POST_BUILD_SCRIPT) {\n      return\n    }\n    const postBuildPath = resolve(process.env.POST_BUILD_SCRIPT)\n\n    if (!existsSync(postBuildPath)) {\n      wLog(\"Post-build script is unavailable:\", postBuildPath)\n      return\n    }\n\n    const postBuild = require(postBuildPath)\n    postBuild()\n  }\n\n  async updateEnv() {\n    await this.initEnv(this.commonPath.projectDirectory)\n    await outputEnvDeclaration(this)\n  }\n\n  // https://github.com/PlasmoHQ/plasmo/issues/195\n  prefixDev = (s = \"\") =>\n    process.env.NODE_ENV === \"development\" ? `DEV | ${s}` : s\n\n  async updatePackageData() {\n    this.packageData = await readJson(this.commonPath.packageFilePath)\n    if (!this.packageData) {\n      throw new Error(\"Invalid package.json\")\n    }\n\n    this.data.version = this.packageData.version\n    this.data.author = this.packageData.author\n\n    this.data.name = this.prefixDev(this.packageData.displayName)\n\n    this.data.description = this.packageData.description\n\n    if (this.packageData.homepage) {\n      this.data.homepage_url = this.packageData.homepage\n    }\n\n    for (const perm of autoPermissionList) {\n      if (`@plasmohq/${perm}` in (this.packageData.dependencies || {})) {\n        this.permissionSet.add(perm)\n      }\n    }\n\n    await this.#cacheUiLibrary()\n\n    this.overideManifest = await this.#getOverrideManifest()\n  }\n\n  #cacheUiLibrary = async () => {\n    const uiLibrary = await getUiLibrary(this)\n    const uiExtMap = getUiExtMap(uiLibrary.name)\n\n    this.#uiLibraryData = {\n      uiLibrary,\n      uiExtMap\n    }\n\n    this.#uiExtSet = new Set(uiExtMap.uiExts)\n    this.uiExts.forEach((uiExt) => {\n      this.#extSet.add(uiExt)\n    })\n\n    this.#projectPath = getProjectPath(\n      this.commonPath,\n      this.browser,\n      this.uiExts\n    )\n  }\n\n  abstract togglePopup: (enable?: boolean) => this\n  abstract toggleBackground: (enable?: boolean) => boolean\n  abstract toggleSidePanel: (enable?: boolean) => this\n\n  toggleOptions = (enable = false) => {\n    if (enable) {\n      this.data.options_ui = {\n        page: \"./options.html\",\n        open_in_tab: true\n      }\n    } else {\n      delete this.data.options_ui\n    }\n    return this\n  }\n\n  toggleOverrides = (page: ChromeUrlOverrideType, enable = false) => {\n    if (enable) {\n      this.data.chrome_url_overrides = {\n        ...this.data.chrome_url_overrides,\n        [page]: `./${page}.html`\n      }\n    } else {\n      delete this.data.chrome_url_overrides?.[page]\n    }\n    return this\n  }\n\n  toggleNewtab = (enable = false) => this.toggleOverrides(\"newtab\", enable)\n\n  toggleDevtools = (enable = false) => {\n    if (enable) {\n      this.data.devtools_page = \"./devtools.html\"\n    } else {\n      delete this.data.devtools_page\n    }\n    return this\n  }\n\n  isPathInvalid = (path?: string): path is undefined => {\n    if (path === undefined) {\n      return true\n    }\n\n    const ext = extname(path)\n\n    if (!this.#extSet.has(ext)) {\n      return true\n    }\n\n    const subExt = getSubExt(path)\n    // Ignore if path is browser specific and does not match browser\n    if (subExt.length > 0 && subExt !== `.${this.browser}`) {\n      return true\n    }\n\n    // Ignore if path is browser generic and there is a browser specific path\n    if (\n      subExt.length === 0 &&\n      existsSync(path.replace(ext, `.${this.browser}${ext}`))\n    ) {\n      return true\n    }\n\n    return false\n  }\n\n  toggleContentScript = async (path?: string, enable = false) => {\n    if (this.isPathInvalid(path)) {\n      return false\n    }\n\n    if (enable) {\n      const metadata = await extractContentScriptConfig(path)\n      if (metadata?.isEmpty) {\n        return false\n      }\n\n      vLog(\"Adding content script: \", path)\n\n      let scriptPath = relative(this.commonPath.dotPlasmoDirectory, path)\n      const scriptExt = extname(scriptPath)\n\n      const isCsui = this.#uiExtSet.has(scriptExt)\n\n      if (this.uiLibrary?.name !== \"vanilla\" && isCsui) {\n        // copy the contents and change the manifest path\n        const modulePath = join(\"lab\", scriptPath).replace(/(^src)[\\\\/]/, \"\")\n\n        const parsedModulePath = parse(modulePath)\n        scriptPath = relative(\n          this.commonPath.dotPlasmoDirectory,\n          await this.scaffolder.createContentScriptMount(parsedModulePath)\n        )\n      }\n\n      // Resolve css file paths\n      if (!!metadata?.config?.css && metadata.config.css.length > 0) {\n        metadata.config.css = metadata.config.css.map((cssFile) =>\n          relative(\n            this.commonPath.dotPlasmoDirectory,\n            resolve(dirname(path), cssFile)\n          )\n        )\n      }\n\n      const contentScript = this.injectEnvToObj({\n        matches: [\"<all_urls>\"],\n        js: [\n          metadata?.config?.world === \"MAIN\"\n            ? scriptPath.split(scriptExt)[0]\n            : scriptPath\n        ],\n        ...(metadata?.config || {})\n      })\n\n      this.contentScriptMap.set(path, contentScript)\n    } else {\n      this.contentScriptMap.delete(path)\n    }\n\n    buildBroadcast(BuildSocketEvent.CsChanged)\n\n    return enable\n  }\n\n  addDirectory = async (\n    path: string,\n    toggleDynamicPath: typeof this.toggleContentScript,\n    filterFile?: (fileName: string) => boolean\n  ) => {\n    if (!existsSync(path)) {\n      return false\n    }\n\n    return readdir(path, { withFileTypes: true })\n      .then((files) =>\n        Promise.all(\n          files\n            .filter((f) =>\n              f.isFile() && filterFile ? filterFile(f.name) : true\n            )\n            .map((f) => resolve(path, f.name))\n            .map((filePath) => toggleDynamicPath(filePath, true))\n        )\n      )\n      .then((results) => results.includes(true))\n  }\n\n  addContentScriptsIndexFiles = async () => {\n    const path = this.projectPath.contentsDirectory\n    if (!(await isDirectory(path))) {\n      return false\n    }\n\n    const indexFileList = [...this.#extSet].flatMap((ext) => [\n      `index${ext}`,\n      `index.${this.browser}${ext}`\n    ])\n\n    return readdir(path, { withFileTypes: true })\n      .then((files) =>\n        Promise.all(\n          files\n            .filter((f) => f.isDirectory())\n            .map((dir) => resolve(path, dir.name))\n            .map((dirPath) =>\n              this.addDirectory(dirPath, this.toggleContentScript, (fileName) =>\n                indexFileList.includes(fileName)\n              )\n            )\n        )\n      )\n      .then((results) => results.includes(true))\n  }\n\n  addContentScriptsDirectory = async (\n    contentsDirectory = this.projectPath.contentsDirectory\n  ) =>\n    Promise.all([\n      this.addDirectory(contentsDirectory, this.toggleContentScript),\n      this.addContentScriptsIndexFiles()\n    ]).then((results) => results.includes(true))\n\n  togglePage = async (path?: string, enable = false) => {\n    if (this.isPathInvalid(path)) {\n      return false\n    }\n\n    if (enable) {\n      const scriptPath = relative(this.commonPath.sourceDirectory, path)\n\n      const parsedModulePath = parse(scriptPath)\n\n      const { wereFilesWritten } =\n        await this.scaffolder.createPageMount(parsedModulePath)\n\n      // if enabled, and the template file was written, invalidate hash!\n      if (wereFilesWritten) {\n        this.#hash = \"\"\n      }\n    } else {\n      this.#hash = \"\"\n    }\n\n    return enable\n  }\n\n  addPagesDirectory = async (directory: string) =>\n    this.addDirectory(directory, this.togglePage)\n\n  write = (force = false) => {\n    this.#prevHash = this.#hash\n\n    const newManifest = this.toJSON()\n\n    this.#hash = hasher.hash(newManifest)\n\n    if (!this.changed && !force) {\n      return\n    }\n    vLog(\"Hash changed, updating manifest\")\n    return writeJson(this.commonPath.entryManifestPath, newManifest, {\n      spaces: 2\n    })\n  }\n\n  toJSON = () => {\n    const base = {\n      ...this.data\n    }\n\n    const {\n      options_ui: overrideOptionUi,\n      permissions: overridePermissions,\n      content_scripts: overrideContentScripts,\n      background: overrideBackground,\n      ...overide\n    } = this.overideManifest as T\n\n    if (base.options_ui?.page) {\n      base.options_ui = {\n        ...base.options_ui,\n        ...overrideOptionUi\n      }\n    }\n\n    base.permissions = [\n      ...new Set([...this.permissionSet, ...(overridePermissions || [])])\n    ]\n\n    if (base.permissions?.length === 0) {\n      delete base.permissions\n    }\n\n    if (this.bundleConfig.manifestVersion === \"mv2\") {\n      base.background = {\n        ...base.background,\n        ...overrideBackground\n      }\n      if (Object.keys(base.background).length === 0) {\n        delete base.background\n      }\n      // Host permission is coupled with permission in mv2\n      if (overide[\"host_permissions\"]?.length > 0) {\n        base.permissions = [\n          ...(base.permissions || []),\n          ...overide[\"host_permissions\"]\n        ]\n      }\n    }\n\n    // Populate content_scripts\n    base.content_scripts = [\n      ...Array.from(this.contentScriptMap.values()).filter(\n        (s) => s.world !== \"MAIN\" // TODO: Remove this when Chrome natively supports mainworld for CS\n      ),\n      ...(overrideContentScripts! || [])\n    ]\n\n    if (base.content_scripts.length === 0) {\n      delete base.content_scripts\n    }\n\n    return {\n      ...base,\n      ...overide\n    }\n  }\n\n  protected abstract prepareOverrideManifest: () =>\n    | Promise<Partial<T>>\n    | Partial<T>\n\n  #getOverrideManifest = async (): Promise<Partial<T>> => {\n    if (!this.packageData?.manifest) {\n      return {}\n    }\n\n    let output = await this.prepareOverrideManifest()\n\n    if ((output.web_accessible_resources?.length || 0) > 0) {\n      output.web_accessible_resources = await this.resolveWAR(\n        this.packageData.manifest.web_accessible_resources\n      )\n    }\n\n    // Sanitize the BSS\n    if (!!output.browser_specific_settings) {\n      switch (this.browser) {\n        case \"edge\":\n        case \"safari\":\n          output.browser_specific_settings = {\n            [this.browser]: output.browser_specific_settings[this.browser]\n          }\n          break\n        case \"firefox\":\n        case \"gecko\":\n          output.browser_specific_settings = {\n            gecko: output.browser_specific_settings.gecko\n          }\n          break\n        default:\n          delete output.browser_specific_settings\n      }\n    }\n\n    if (output.overrides && output.overrides[this.browser]) {\n      output = { ...output, ...output.overrides[this.browser] }\n    }\n    delete output.overrides\n    return this.injectEnvToObj(output)\n  }\n\n  protected abstract resolveWAR: (\n    war: ExtensionManifestV3[\"web_accessible_resources\"]\n  ) => Promise<T[\"web_accessible_resources\"]>\n\n  protected copyProjectFile = async (\n    inputFilePath: string\n  ): Promise<string | string[]> => {\n    try {\n      if (inputFilePath.startsWith(\"~\")) {\n        return this.copyProjectFile(inputFilePath.slice(1))\n      }\n\n      if (inputFilePath.includes(\"*\")) {\n        // Handling glob...\n        const files = await glob(inputFilePath, {\n          cwd: this.commonPath.projectDirectory,\n          onlyFiles: true\n        })\n\n        await Promise.all(files.map(this.copyProjectFile))\n        return inputFilePath\n      }\n\n      const resourceFilePath = isAbsolute(inputFilePath)\n        ? inputFilePath\n        : resolve(this.commonPath.projectDirectory, inputFilePath)\n\n      const canCopy =\n        !this.projectPath.isEntryPath(resourceFilePath) &&\n        (await pathExists(resourceFilePath))\n\n      if (!canCopy) {\n        return inputFilePath\n      }\n\n      const destination = resolve(this.commonPath.distDirectory, inputFilePath)\n\n      this.copyMap.set(destination, resourceFilePath)\n\n      return toPosix(inputFilePath)\n    } catch {\n      return inputFilePath\n    }\n  }\n\n  protected copyNodeModuleFile = async (inputFilePath: string) => {\n    try {\n      const resourceFilePath = require.resolve(inputFilePath, {\n        paths: [this.commonPath.projectDirectory]\n      })\n\n      const fileName = basename(resourceFilePath)\n\n      const destination = resolve(this.commonPath.distDirectory, fileName)\n\n      this.copyMap.set(destination, resourceFilePath)\n\n      return fileName\n    } catch {\n      return null\n    }\n  }\n\n  protected injectEnvToObj = <T = any, O = T>(target: T): O =>\n    definedTraverse(target, (value) => {\n      if (typeof value !== \"string\") {\n        return value\n      }\n\n      if (!!value.match(/^\\$(\\w+)$/)) {\n        return this.combinedEnv[value.substring(1)] || undefined\n      } else {\n        return injectEnv(value, this.combinedEnv)\n      }\n    })\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/create-manifest.ts",
    "content": "import { find } from \"@plasmo/utils/array\"\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport { updateBgswEntry } from \"~features/background-service-worker/update-bgsw-entry\"\nimport type { PlasmoBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\n\nimport { PlasmoExtensionManifestMV2 } from \"./mv2\"\nimport { PlasmoExtensionManifestMV3 } from \"./mv3\"\n\nexport async function createManifest(bundleConfig: PlasmoBundleConfig) {\n  vLog(\"Creating Manifest Factory...\")\n  const plasmoManifest =\n    bundleConfig.manifestVersion === \"mv3\"\n      ? new PlasmoExtensionManifestMV3(bundleConfig)\n      : new PlasmoExtensionManifestMV2(bundleConfig)\n\n  await plasmoManifest.startup()\n\n  const {\n    contentIndexList,\n    sandboxIndexList,\n    tabsDirectory,\n    sandboxesDirectory\n  } = plasmoManifest.projectPath\n\n  const [contentIndex, sandboxIndex] = await Promise.all(\n    [contentIndexList, sandboxIndexList].map((l) => find(l, isAccessible))\n  )\n\n  const initResults = await Promise.all([\n    plasmoManifest.scaffolder.init(),\n    plasmoManifest.togglePage(sandboxIndex, true),\n\n    plasmoManifest.toggleContentScript(contentIndex, true),\n    plasmoManifest.addContentScriptsDirectory(),\n\n    plasmoManifest.addPagesDirectory(tabsDirectory),\n    plasmoManifest.addPagesDirectory(sandboxesDirectory)\n  ])\n\n  // BGSW needs to check CS set for main world\n  initResults.push(await updateBgswEntry(plasmoManifest))\n\n  const hasEntrypoints = initResults.flat()\n\n  if (!hasEntrypoints.includes(true)) {\n    wLog(\"Unable to find any entry files. The extension might be empty\")\n  }\n\n  const [hasPopup, hasOptions, hasNewtab, hasDevtools, hasSidePanel] =\n    hasEntrypoints\n\n  plasmoManifest\n    .togglePopup(hasPopup)\n    .toggleOptions(hasOptions)\n    .toggleNewtab(hasNewtab)\n    .toggleDevtools(hasDevtools)\n    .toggleSidePanel(hasSidePanel)\n\n  await plasmoManifest.write(true)\n\n  return plasmoManifest\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/mv2.ts",
    "content": "import type {\n  ExtensionManifestV2,\n  ExtensionManifestV3\n} from \"@plasmo/constants\"\nimport { iLog } from \"@plasmo/utils/logging\"\n\nimport type { PlasmoBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\n\nimport { iconMap, PlasmoManifest } from \"./base\"\n\nexport class PlasmoExtensionManifestMV2 extends PlasmoManifest<ExtensionManifestV2> {\n  constructor(bundleConfig: PlasmoBundleConfig) {\n    super(bundleConfig)\n\n    this.data.manifest_version = 2\n    this.data.browser_action = {\n      default_icon: iconMap\n    }\n  }\n\n  toggleSidePanel = (enable = false) => {\n    switch (this.browser) {\n      case \"firefox\":\n      case \"gecko\": {\n        if (enable) {\n          this.data.sidebar_action = {\n            default_panel: \"./sidepanel.html\"\n          }\n        } else {\n          delete this.data.sidebar_action\n        }\n        break\n      }\n      default: {\n        iLog(\n          \"SidePanel is not available on chromium-based MV2 browsers, skipping.\"\n        )\n      }\n    }\n\n    return this\n  }\n\n  togglePopup = (enable = false) => {\n    if (enable) {\n      this.data.browser_action!.default_popup = \"./popup.html\"\n    } else {\n      delete this.data.browser_action!.default_popup\n    }\n    return this\n  }\n\n  toggleBackground = (enable = false) => {\n    if (enable) {\n      this.data.background = {\n        scripts: [\"./static/background/index.ts\"]\n      }\n    } else {\n      delete this.data.background\n    }\n\n    return enable\n  }\n\n  protected prepareOverrideManifest = () => {\n    const { manifest } = this.packageData\n    const output = {\n      ...manifest\n    } as unknown as ExtensionManifestV2\n\n    // Merge host permissions into permissions\n    output.permissions = [\n      ...(manifest.permissions || []),\n      ...(manifest.host_permissions || [])\n    ] as any\n\n    if (\"host_permissions\" in output) {\n      delete output[\"host_permissions\"]\n    }\n\n    if (\"content_security_policy\" in manifest) {\n      output.content_security_policy =\n        manifest.content_security_policy?.extension_pages\n    }\n\n    return output\n  }\n\n  protected resolveWAR = (\n    war: ExtensionManifestV3[\"web_accessible_resources\"]\n  ) =>\n    Promise.all(\n      war!.map(async ({ resources }) => {\n        const resolvedResources = await Promise.all(\n          resources.map(\n            async (resourcePath) =>\n              (await this.copyNodeModuleFile(resourcePath)) ||\n              (await this.copyProjectFile(resourcePath))\n          )\n        )\n\n        return resolvedResources.flat()\n      })\n    ).then((res) => res.flat())\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/mv3.ts",
    "content": "import type { ExtensionManifestV3 } from \"@plasmo/constants\"\n\nimport type { PlasmoBundleConfig } from \"~features/extension-devtools/get-bundle-config\"\n\nimport { iconMap, PlasmoManifest } from \"./base\"\n\nexport class PlasmoExtensionManifestMV3 extends PlasmoManifest<ExtensionManifestV3> {\n  constructor(bundleConfig: PlasmoBundleConfig) {\n    super(bundleConfig)\n    this.data.manifest_version = 3\n    this.data.action = {\n      default_icon: iconMap\n    }\n  }\n\n  toggleSidePanel = (enable = false) => {\n    switch (this.browser) {\n      case \"firefox\":\n      case \"gecko\": {\n        if (enable) {\n          this.data.sidebar_action = {\n            default_panel: \"./sidepanel.html\"\n          }\n        } else {\n          delete this.data.sidebar_action\n        }\n        break\n      }\n      default: {\n        if (enable) {\n          this.data.side_panel = {\n            default_path: \"./sidepanel.html\"\n          }\n          this.permissionSet.add(\"sidePanel\")\n        } else {\n          delete this.data.side_panel\n          this.permissionSet.delete(\"sidePanel\")\n        }\n      }\n    }\n\n    return this\n  }\n\n  togglePopup = (enable = false) => {\n    if (enable) {\n      this.data.action!.default_popup = \"./popup.html\"\n    } else {\n      delete this.data.action!.default_popup\n    }\n    return this\n  }\n\n  toggleBackground = (enable = false) => {\n    if (enable) {\n      this.data.background = {\n        service_worker: \"./static/background/index.ts\"\n      }\n    } else {\n      delete this.data.background\n    }\n\n    return enable\n  }\n\n  protected prepareOverrideManifest = () => ({\n    ...this.packageData!.manifest\n  })\n\n  protected resolveWAR = (\n    war: ExtensionManifestV3[\"web_accessible_resources\"]\n  ) =>\n    Promise.all(\n      war!.map(async ({ resources, ...warProps }) => {\n        const resolvedResources = await Promise.all(\n          resources.map(\n            async (resourcePath) =>\n              (await this.copyNodeModuleFile(resourcePath)) ||\n              (await this.copyProjectFile(resourcePath))\n          )\n        )\n\n        return {\n          resources: resolvedResources.flat(),\n          ...warProps\n        }\n      })\n    )\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/scaffolder.ts",
    "content": "import { readFile, writeFile } from \"fs/promises\"\nimport { join, relative, resolve, type ParsedPath } from \"path\"\nimport { copy, ensureDir } from \"fs-extra\"\n\nimport { find } from \"@plasmo/utils/array\"\nimport { isAccessible, isFile } from \"@plasmo/utils/fs\"\nimport { vLog } from \"@plasmo/utils/logging\"\nimport { toPosix } from \"@plasmo/utils/path\"\n\nimport { getRevHash } from \"~features/helpers/crypto\"\n\nimport { type PlasmoManifest } from \"./base\"\nimport { isSupportedUiExt } from \"./ui-library\"\n\ntype ExtensionUIPage = \"popup\" | \"options\" | \"devtools\" | \"newtab\" | \"sidepanel\"\n\nexport class Scaffolder {\n  #templateCache = {} as Record<string, string>\n  #outputHashCache = {} as Record<string, string>\n\n  get projectPath() {\n    return this.plasmoManifest.projectPath\n  }\n\n  get commonPath() {\n    return this.plasmoManifest.commonPath\n  }\n\n  get mountExt() {\n    return this.plasmoManifest.mountExt\n  }\n\n  constructor(private plasmoManifest: PlasmoManifest) {}\n\n  async init() {\n    const [_, ...uiPagesResult] = await Promise.all([\n      this.#copyStaticCommon(),\n      this.#initUiPageTemplate(\"popup\"),\n      this.#initUiPageTemplate(\"options\"),\n      this.#initUiPageTemplate(\"newtab\"),\n      this.#initUiPageTemplate(\"devtools\"),\n      this.#initUiPageTemplate(\"sidepanel\")\n    ])\n\n    return uiPagesResult\n  }\n\n  #copyStaticCommon = async () => {\n    const templateCommonDirectory = resolve(\n      this.plasmoManifest.templatePath.staticTemplatePath,\n      \"common\"\n    )\n\n    const staticCommonDirectory = resolve(\n      this.commonPath.staticDirectory,\n      \"common\"\n    )\n\n    return copy(templateCommonDirectory, staticCommonDirectory)\n  }\n\n  #initUiPageTemplate = async (uiPageName: ExtensionUIPage) => {\n    vLog(`Creating static templates for ${uiPageName}`)\n\n    const indexList = this.projectPath[`${uiPageName}IndexList`]\n    const htmlList = this.projectPath[`${uiPageName}HtmlList`]\n\n    const [indexFile, htmlFile] = await Promise.all(\n      [indexList, htmlList].map((l) => find(l, isAccessible))\n    )\n\n    const { staticDirectory } = this.commonPath\n\n    // Generate the static directory\n    await ensureDir(staticDirectory)\n\n    const hasIndex = indexFile !== undefined\n\n    // console.log({ indexFile, hasIndex })\n\n    const indexImport = hasIndex\n      ? toPosix(relative(staticDirectory, indexFile))\n      : `~${uiPageName}`\n\n    const uiPageModulePath = resolve(\n      staticDirectory,\n      `${uiPageName}${this.mountExt}`\n    )\n\n    await Promise.all([\n      this.#cachedGenerate(`index${this.mountExt}`, uiPageModulePath, {\n        __plasmo_import_module__: indexImport\n      }),\n      this.createPageHtml(uiPageName, htmlFile)\n    ])\n\n    return hasIndex\n  }\n\n  generateHtml = async (\n    outputPath = \"\",\n    scriptMountPath = \"\",\n    overridePath = \"\"\n  ) => {\n    const templateReplace = {\n      __plasmo_static_index_title__: this.plasmoManifest.name,\n      __plasmo_static_script__: scriptMountPath\n    }\n\n    const hasOverride = await isFile(overridePath)\n\n    return hasOverride\n      ? this.#copyGenerate(overridePath, outputPath, {\n          ...templateReplace,\n          \"</body>\": `<div id=\"__plasmo\"></div><script src=\"${scriptMountPath}\" type=\"module\"></script></body>`\n        })\n      : this.#cachedGenerate(\"index.html\", outputPath, templateReplace)\n  }\n\n  /**\n   * @return true if file was written, false if cache hit\n   */\n  createPageHtml = async (uiPageName: ExtensionUIPage, htmlFile = \"\") => {\n    const outputHtmlPath = resolve(\n      this.commonPath.dotPlasmoDirectory,\n      `${uiPageName}.html`\n    )\n\n    const scriptMountPath = `./static/${uiPageName}${this.mountExt}`\n\n    return this.generateHtml(outputHtmlPath, scriptMountPath, htmlFile)\n  }\n\n  createPageMount = async (module: ParsedPath) => {\n    vLog(`Creating page mount template for ${module.dir}`)\n\n    const staticModulePath = resolve(\n      this.commonPath.dotPlasmoDirectory,\n      module.dir\n    )\n    const htmlPath = resolve(staticModulePath, `${module.name}.html`)\n\n    await ensureDir(staticModulePath)\n    const isUiExt = isSupportedUiExt(module.ext)\n\n    const scriptPath = resolve(\n      staticModulePath,\n      `${module.name}${this.mountExt}`\n    )\n\n    const overrideHtmlPath = resolve(\n      this.commonPath.sourceDirectory,\n      module.dir,\n      `${module.name}.html`\n    )\n\n    const generateResultList = await Promise.all(\n      isUiExt\n        ? [\n            this.#cachedGenerate(`index${this.mountExt}`, scriptPath, {\n              __plasmo_import_module__: `~${toPosix(\n                join(module.dir, module.name)\n              )}`\n            }),\n            this.generateHtml(\n              htmlPath,\n              `./${module.name}${this.mountExt}`,\n              overrideHtmlPath\n            )\n          ]\n        : [\n            this.generateHtml(\n              htmlPath,\n              `~${toPosix(join(module.dir, module.name))}${module.ext}`,\n              overrideHtmlPath\n            )\n          ]\n    )\n\n    return {\n      htmlPath,\n      wereFilesWritten: generateResultList.some((r) => r)\n    }\n  }\n\n  createContentScriptMount = async (module: ParsedPath) => {\n    vLog(`Creating content script mount for ${module.dir}`)\n    const staticModulePath = resolve(\n      this.commonPath.staticDirectory,\n      module.dir\n    )\n\n    await ensureDir(staticModulePath)\n\n    const staticContentPath = resolve(\n      staticModulePath,\n      `${module.name}${this.mountExt}`\n    )\n\n    // Can pass metadata to check config for type of mount as well?\n    await this.#cachedGenerate(\n      `content-script-ui-mount${this.mountExt}`,\n      staticContentPath,\n      {\n        __plasmo_mount_content_script__: `~${toPosix(\n          join(module.dir, module.name)\n        )}`\n      }\n    )\n\n    return staticContentPath\n  }\n\n  /**\n   * @return true if file was written, false if cache hit\n   */\n  #generate = async (\n    templateContent: string,\n    outputFilePath: string,\n    replaceMap: Record<string, string>\n  ) => {\n    const finalScaffold = Object.entries(replaceMap).reduce(\n      (html, [key, value]) => html.replaceAll(key, value),\n      templateContent\n    )\n\n    const hash = getRevHash(Buffer.from(finalScaffold))\n    if (this.#outputHashCache[outputFilePath] === hash) {\n      return false\n    }\n\n    this.#outputHashCache[outputFilePath] = hash\n    await writeFile(outputFilePath, finalScaffold)\n    return true\n  }\n\n  /**\n   * @return true if file was written, false if cache hit\n   */\n  #copyGenerate = async (\n    filePath: string,\n    outputFilePath: string,\n    replaceMap: Record<string, string>\n  ) => {\n    const templateContent = await readFile(filePath, \"utf8\")\n\n    return this.#generate(templateContent, outputFilePath, replaceMap)\n  }\n\n  /**\n   * @return true if file was written, false if cache hit\n   */\n  #cachedGenerate = async (\n    fileName: string,\n    outputFilePath: string,\n    replaceMap: Record<string, string>\n  ) => {\n    this.#templateCache[fileName] ||= await readFile(\n      resolve(this.plasmoManifest.staticScaffoldPath, fileName),\n      \"utf8\"\n    )\n\n    return this.#generate(\n      this.#templateCache[fileName],\n      outputFilePath,\n      replaceMap\n    )\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/ui-library.ts",
    "content": "import { resolve } from \"path\"\nimport { cwd } from \"process\"\nimport { readJson } from \"fs-extra\"\nimport semver from \"semver\"\n\nimport { assertUnreachable } from \"@plasmo/utils/assert\"\nimport { isAccessible } from \"@plasmo/utils/fs\"\n\nimport { type PlasmoManifest } from \"./base\"\n\nconst supportedUiLibraries = [\"react\", \"svelte\", \"vue\", \"vanilla\"] as const\n\ntype SupportedUiLibraryName = (typeof supportedUiLibraries)[number]\n\nconst supportedUiExt = [\".tsx\", \".svelte\", \".vue\", \".jsx\"] as const\n\nexport type SupportedUiExt = (typeof supportedUiExt)[number]\n\nconst supportedUiExtSet = new Set(supportedUiExt)\n\nexport const isSupportedUiExt = (ext: string): ext is SupportedUiExt =>\n  supportedUiExtSet.has(ext as SupportedUiExt)\n\nexport type UiLibrary = {\n  name: SupportedUiLibraryName\n  path: `${SupportedUiLibraryName}${number | \"\"}`\n  version: number\n}\n\nconst supportedMountExt = [\".ts\", \".tsx\"] as const\n\nexport type ScaffolderMountExt = (typeof supportedMountExt)[number]\n\nexport type UiExtMap = {\n  uiExts: SupportedUiExt[]\n  mountExt: ScaffolderMountExt\n}\n\nconst uiLibraryError = `No supported UI library found.  You can file an RFC for a new UI Library here: https://github.com/PlasmoHQ/plasmo/issues`\n\nconst getMajorVersion = async (version: string) => {\n  if (version.includes(\":\")) {\n    const [protocol, versionData] = version.split(\":\")\n    switch (protocol) {\n      case \"file\":\n      default:\n        const packageJson = await readJson(\n          resolve(cwd(), versionData, \"package.json\")\n        )\n        return semver.coerce(packageJson.version)?.major\n    }\n  } else {\n    return semver.coerce(version)?.major\n  }\n}\n\nexport const getUiLibrary = async (\n  plasmoManifest: PlasmoManifest\n): Promise<UiLibrary> => {\n  const dependencies = plasmoManifest.dependencies ?? {}\n\n  const baseLibrary = supportedUiLibraries.find((l) => l in dependencies)\n\n  if (baseLibrary === undefined) {\n    return {\n      name: \"vanilla\",\n      path: \"vanilla\",\n      version: 8427\n    }\n  }\n\n  const majorVersion = await getMajorVersion(dependencies[baseLibrary])\n\n  if (majorVersion === undefined) {\n    throw new Error(uiLibraryError)\n  }\n\n  // React lower than 18 can uses 17 scaffold\n  if (baseLibrary === \"react\" && majorVersion < 18) {\n    return {\n      name: baseLibrary,\n      path: \"react17\",\n      version: majorVersion\n    }\n  }\n\n  const uiLibraryPath = `${baseLibrary}${majorVersion}` as const\n\n  const staticPath = resolve(\n    plasmoManifest.templatePath.staticTemplatePath,\n    uiLibraryPath\n  )\n\n  if (!(await isAccessible(staticPath))) {\n    throw new Error(uiLibraryError)\n  }\n\n  return {\n    name: baseLibrary,\n    path: uiLibraryPath,\n    version: majorVersion\n  }\n}\n\nexport const getUiExtMap = (\n  uiLibraryName: SupportedUiLibraryName\n): UiExtMap => {\n  switch (uiLibraryName) {\n    case \"svelte\":\n      return {\n        uiExts: [\".svelte\"],\n        mountExt: \".ts\"\n      }\n    case \"vue\":\n      return {\n        uiExts: [\".vue\"],\n        mountExt: \".ts\"\n      }\n    case \"react\":\n      return {\n        uiExts: [\".tsx\", \".jsx\"],\n        mountExt: \".tsx\"\n      }\n    case \"vanilla\":\n      return {\n        uiExts: [\".tsx\", \".jsx\"],\n        mountExt: \".ts\"\n      }\n    default:\n      assertUnreachable(uiLibraryName)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/manifest-factory/zip.ts",
    "content": "import { createReadStream, createWriteStream } from \"fs\"\nimport { resolve } from \"path\"\nimport glob from \"fast-glob\"\nimport { AsyncZipDeflate, Zip } from \"fflate\"\n\nimport { iLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\n\nfunction toMB(bytes: number) {\n  return bytes / 1024 / 1024\n}\n\nexport const zipBundle = async (\n  { distDirectory, buildDirectory, distDirectoryName }: CommonPath,\n  withMaps = false\n) => {\n  const zipFilePath = resolve(buildDirectory, `${distDirectoryName}.zip`)\n\n  const output = createWriteStream(zipFilePath)\n\n  const fileList = await glob(\n    [\n      \"**/*\", // Pick all nested files\n      !withMaps && \"!**/(*.js.map|*.css.map)\" // Exclude source maps\n    ].filter(Boolean),\n    {\n      cwd: distDirectory,\n      onlyFiles: true\n    }\n  )\n\n  return new Promise<void>((pResolve, pReject) => {\n    let size = 0\n    let aborted = false\n    const timer = Date.now()\n    const zip = new Zip((err, data, final) => {\n      if (aborted) {\n        return\n      } else if (err) {\n        pReject(err)\n      } else {\n        size += data.length\n        output.write(data)\n        if (final) {\n          iLog(\n            `Zip Package size: ${toMB(size).toFixed(2)} MB in ${\n              Date.now() - timer\n            }ms`\n          )\n          output.end()\n          pResolve()\n        }\n      }\n    })\n\n    // Start all the file read streams\n    for (const file of fileList) {\n      if (aborted) {\n        return\n      }\n\n      const data = new AsyncZipDeflate(file, {\n        level: 9\n      })\n\n      zip.add(data)\n\n      const absPath = resolve(distDirectory, file)\n\n      createReadStream(absPath)\n        .on(\"data\", (chunk: Buffer) => {\n          data.push(chunk, false)\n        })\n        .on(\"end\", () => {\n          data.push(new Uint8Array(0), true) // Notify completion\n        })\n        .on(\"error\", (error) => {\n          aborted = true\n          zip.terminate()\n          pReject(`Error reading file ${absPath}: ${error.message}`)\n        })\n    }\n\n    zip.end()\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/from-existing-manifest.ts",
    "content": "import { readFile } from \"fs/promises\"\nimport { extname } from \"path\"\nimport { strFromU8, unzipSync, type Unzipped } from \"fflate\"\nimport { readJson } from \"fs-extra\"\n\nimport type {\n  ExtensionManifest,\n  ExtensionManifestV2,\n  ExtensionManifestV3,\n  ManifestPermission\n} from \"@plasmo/constants\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\nimport {\n  generatePackage,\n  type PackageJSON\n} from \"~features/extension-devtools/package-file\"\nimport type { PackageManagerInfo } from \"~features/helpers/package-manager\"\n\nexport const getManifestData = async (absPath: string) => {\n  const data = {\n    unzipped: {} as Unzipped,\n    isZip: false,\n    manifestData: {} as ExtensionManifest\n  }\n\n  const ext = extname(absPath)\n  if (ext === \".zip\") {\n    const fileBuffer = await readFile(absPath)\n    data.unzipped = unzipSync(fileBuffer)\n    data.isZip = true\n    data.manifestData = JSON.parse(strFromU8(data.unzipped[\"manifest.json\"]))\n  } else if (ext === \".json\") {\n    data.manifestData = await readJson(absPath)\n  } else {\n    return null\n  }\n\n  return data\n}\n\nexport const generatePackageFromManifest = async (\n  commonPath: CommonPath,\n  packageManager: PackageManagerInfo,\n  { manifestData }: Awaited<ReturnType<typeof getManifestData>>\n) => {\n  const packageData = await generatePackage({\n    name: commonPath.packageName,\n    packageManager\n  })\n\n  packageData.version = manifestData.version\n  packageData.displayName = manifestData.name\n  packageData.description = manifestData.description\n\n  if (manifestData?.author) {\n    packageData.author = manifestData.author\n  }\n\n  if (manifestData.homepage_url) {\n    packageData.homepage = manifestData.homepage_url\n  }\n\n  if (manifestData.version_name) {\n    packageData.manifest.version_name = manifestData.version_name\n  }\n\n  if (manifestData.browser_specific_settings) {\n    packageData.manifest.browser_specific_settings =\n      manifestData.browser_specific_settings\n  }\n\n  if (manifestData.default_locale) {\n    vLog(\"Convert locale\")\n    // Copy all locale json files to assets\n  }\n\n  if (manifestData.options_ui) {\n    vLog(\"Convert options_ui\")\n    // Create option.tsx if it doesn't exist\n  }\n\n  if (manifestData.chrome_url_overrides) {\n    vLog(\"Convert chrome_url_overrides\")\n    // Create newtab.tsx if it doesn't exist\n  }\n\n  if (manifestData.icons) {\n    vLog(\"Convert icons\")\n    // Copy the largest icon to icon.png\n  }\n\n  if (manifestData.content_scripts) {\n    vLog(\"Convert content_scripts\")\n    // TODO: Create blank content scripts for each js file, with the appropriate config\n  }\n\n  switch (manifestData.manifest_version) {\n    case 2:\n      await fromMv2(manifestData, packageData, commonPath)\n      break\n    case 3:\n      await fromMv3(manifestData, packageData, commonPath)\n      break\n    default:\n      throw new Error(\"Unknown manifest version\")\n  }\n\n  return packageData\n}\n\nasync function fromMv2(\n  manifestData: ExtensionManifestV2,\n  packageData: PackageJSON,\n  commonPath: CommonPath\n) {\n  if (manifestData.content_security_policy) {\n    vLog(\"Convert content_security_policy\")\n    packageData.manifest.content_security_policy = {\n      extension_pages: manifestData.content_security_policy\n    }\n  }\n\n  if (manifestData.web_accessible_resources) {\n    vLog(\"Convert web_accessible_resources\")\n    packageData.manifest.web_accessible_resources = [\n      {\n        matches: [\"https://*/*\"],\n        resources: manifestData.web_accessible_resources\n      }\n    ]\n  }\n\n  if (manifestData.permissions) {\n    vLog(\"Convert permissions\")\n    packageData.manifest.permissions = []\n    packageData.manifest.host_permissions = []\n\n    for (const permission of manifestData.permissions) {\n      if (permission.startsWith(\"http\") || permission === \"<all_urls>\") {\n        packageData.manifest.host_permissions.push(permission)\n      } else {\n        packageData.manifest.permissions.push(permission as ManifestPermission)\n      }\n    }\n  }\n\n  if (manifestData.browser_action) {\n    vLog(\"Convert browser_action\")\n    packageData.manifest.action = {\n      default_title: manifestData.browser_action.default_title\n    }\n    // TODO: create popup.tsx\n    // TODO: copy icons\n  }\n\n  if (manifestData.background) {\n    vLog(\"Convert background\")\n    // TODO: create background.tsx\n  }\n}\n\nasync function fromMv3(\n  manifestData: ExtensionManifestV3,\n  packageData: PackageJSON,\n  commonPath: CommonPath\n) {\n  if (manifestData.content_security_policy) {\n    vLog(\"Convert content_security_policy\")\n    packageData.manifest.content_security_policy = {\n      ...manifestData.content_security_policy\n    }\n  }\n\n  if (manifestData.web_accessible_resources) {\n    vLog(\"Convert web_accessible_resources\")\n    packageData.manifest.web_accessible_resources = [\n      ...manifestData.web_accessible_resources\n    ]\n  }\n\n  if (manifestData.permissions) {\n    vLog(\"Transfer permissions\")\n    packageData.manifest.permissions = [...manifestData.permissions]\n  }\n\n  if (manifestData.host_permissions) {\n    vLog(\"Transfer host_permissions\")\n    packageData.manifest.host_permissions = [...manifestData.host_permissions]\n  }\n\n  if (manifestData.action) {\n    vLog(\"Convert browser_action\")\n    packageData.manifest.action = {\n      default_title: manifestData.action.default_title\n    }\n    // TODO: create popup.tsx\n    // TODO: copy icons\n  }\n\n  if (manifestData.background) {\n    vLog(\"Convert background\")\n    // TODO: create background.tsx\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/get-raw-name.ts",
    "content": "import { createQuestId } from \"mnemonic-id\"\n\nimport { getNonFlagArgvs } from \"@plasmo/utils/argv\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { quickPrompt } from \"~features/helpers/prompt\"\n\nexport const getRawName = async () => {\n  const [rawNameNonInteractive] = getNonFlagArgvs(\"init\")\n\n  if (!!rawNameNonInteractive) {\n    vLog(\"Using user-provided name:\", rawNameNonInteractive)\n    return rawNameNonInteractive\n  }\n\n  vLog(\"Prompting for the extension name\")\n  return await quickPrompt(\"Extension name:\", createQuestId())\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/git-init.ts",
    "content": "import spawnAsync, { type SpawnOptions } from \"@expo/spawn-async\"\n\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { iLog, vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\n\nconst gitInitAddCommit = async (root: string) => {\n  const { default: chalk } = await import(\"chalk\")\n  const commonOpt: SpawnOptions = { cwd: root, ignoreStdio: true }\n\n  try {\n    vLog(\"Checking if the root is a git repository\")\n    await spawnAsync(\"git\", [\"rev-parse\", \"--is-inside-work-tree\"], commonOpt)\n    vLog(`${root} is a git repository, bailing ${chalk.bold(\"git init\")}`)\n    return false\n  } catch (e: any) {\n    if (e.code === \"ENOENT\") {\n      throw new Error(\"Unable to initialize git repo. `git` not in PATH.\")\n    }\n  }\n\n  iLog(\"Initializing git project...\")\n\n  await spawnAsync(\"git\", [\"init\"], commonOpt)\n\n  await spawnAsync(\"git\", [\"add\", \"--all\"], commonOpt)\n\n  await spawnAsync(\n    \"git\",\n    [\"commit\", \"-m\", \"Created a new Plasmo extension\"],\n    commonOpt\n  )\n  vLog(\"Added all files to git and created the initial commit.\")\n\n  return true\n}\n\nexport async function gitInit(\n  commonPath: CommonPath,\n  root: string\n): Promise<boolean> {\n  if (!(await isAccessible(commonPath.gitIgnorePath))) {\n    return false\n  }\n\n  try {\n    return await gitInitAddCommit(root)\n  } catch (error: any) {\n    wLog(error.message)\n    return false\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/index.ts",
    "content": "import { existsSync } from \"fs\"\nimport { lstat, readFile, writeFile } from \"fs/promises\"\nimport { isAbsolute, join, relative, resolve } from \"path\"\nimport spawnAsync from \"@expo/spawn-async\"\nimport { sentenceCase } from \"change-case\"\nimport { copy, outputJson, readJson } from \"fs-extra\"\nimport ignore from \"ignore\"\nimport { temporaryDirectory } from \"tempy\"\n\nimport { getFlag, hasFlag } from \"@plasmo/utils/flags\"\nimport { isAccessible } from \"@plasmo/utils/fs\"\nimport { iLog, vLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\nimport { generateGitIgnore } from \"~features/extension-devtools/git-ignore\"\nimport {\n  resolveWorkspaceToLatestSemver,\n  type PackageJSON\n} from \"~features/extension-devtools/package-file\"\nimport { getTemplatePath } from \"~features/extension-devtools/template-path\"\nimport type { PackageManagerInfo } from \"~features/helpers/package-manager\"\nimport { quickPrompt } from \"~features/helpers/prompt\"\n\nimport {\n  generatePackageFromManifest,\n  getManifestData\n} from \"./from-existing-manifest\"\n\nconst withRegex = /(?:^--with-)(?:\\w+-?)+(?:[^-]$)/\n\nexport class ProjectCreator {\n  templatePath = getTemplatePath()\n\n  constructor(\n    public commonPath: CommonPath,\n    public packageManager: PackageManagerInfo,\n    public isExample = false\n  ) {}\n\n  async create() {\n    return (\n      (await this.createFrom()) ||\n      (await this.createWith()) ||\n      (await this.createBlank())\n    )\n  }\n\n  async createFrom() {\n    const fromPath = getFlag(\"--from\")\n\n    if (!fromPath) {\n      return false\n    }\n\n    const absFromPath = isAbsolute(fromPath) ? fromPath : resolve(fromPath)\n    const fromStats = await lstat(absFromPath)\n\n    if (fromStats.isFile()) {\n      return await this.createFromManifest(absFromPath)\n    } else if (fromStats.isDirectory()) {\n      return await this.createFromLocalTemplate(absFromPath)\n    } else {\n      return false\n    }\n  }\n\n  async createFromLocalTemplate(absFromPath: string) {\n    const ig = ignore().add([\"node_modules\", \".git\", \".env*\"])\n\n    const gitIgnorePath = join(absFromPath, \".gitignore\")\n    const hasGitIgnore = await isAccessible(gitIgnorePath)\n\n    if (hasGitIgnore) {\n      const gitIgnoreData = await readFile(gitIgnorePath, \"utf-8\")\n      ig.add(gitIgnoreData)\n    }\n\n    await copy(absFromPath, this.commonPath.projectDirectory, {\n      filter: (src) =>\n        src === absFromPath || !ig.ignores(relative(absFromPath, src))\n    })\n\n    const packageData = await readJson(this.commonPath.packageFilePath)\n\n    await this.outputPackageData(packageData)\n\n    iLog(`Creating new project from ${absFromPath}`)\n    return true\n  }\n\n  async createFromManifest(absFromPath: string) {\n    const existingData = await getManifestData(absFromPath)\n    if (existingData === null) {\n      return false\n    }\n\n    await this.copyBlankInitFiles()\n\n    const packageData = await generatePackageFromManifest(\n      this.commonPath,\n      this.packageManager,\n      existingData\n    )\n\n    await this.outputPackageData(packageData, { resolveWorkspaceRefs: true })\n\n    return true\n  }\n\n  async createWith() {\n    const withExampleName = process.argv.find((arg) => withRegex.test(arg))\n    if (withExampleName === undefined) {\n      return false\n    }\n\n    return this.createWithExample(withExampleName.substring(2))\n  }\n\n  async createWithExample(exampleName: string) {\n    // locate the tmp directory\n    const tempDirectory = temporaryDirectory()\n    vLog(`Download examples to temporary directory: ${tempDirectory}`)\n\n    try {\n      // download the examples\n      await spawnAsync(\n        \"git\",\n        [\n          \"clone\",\n          \"--depth\",\n          \"1\",\n          \"https://github.com/PlasmoHQ/examples.git\",\n          \".\"\n        ],\n        { cwd: tempDirectory, ignoreStdio: true }\n      )\n    } catch (error: any) {\n      if (error.code === \"ENOENT\") {\n        throw new Error(\"Unable to clone example repo. `git` is not in PATH.\")\n      }\n    }\n\n    const exampleDirectory = resolve(tempDirectory, exampleName)\n\n    if (!existsSync(exampleDirectory)) {\n      throw new Error(\n        `Example ${exampleName} not found. You may file an example request at: https://docs.plasmo.com/exp`\n      )\n    }\n\n    vLog(\"Copy example to project directory\")\n    await Promise.all([\n      copy(exampleDirectory, this.commonPath.projectDirectory),\n      this.copyBppWorkflow()\n    ])\n\n    const packageData = await readJson(this.commonPath.packageFilePath)\n    await this.outputPackageData(packageData, { resolveWorkspaceRefs: true })\n\n    iLog(`Creating new project ${exampleName.split(\"-\").join(\" \")}`)\n    return true\n  }\n\n  async createBlank() {\n    await this.createWithExample(\"with-popup\")\n    return true\n  }\n\n  private async outputPackageData(\n    packageData: PackageJSON,\n    { resolveWorkspaceRefs = false } = {}\n  ) {\n    const { packageName, packageFilePath } = this.commonPath\n\n    packageData.name = packageName\n    packageData.displayName = sentenceCase(packageName)\n\n    packageData.description = await quickPrompt(\n      \"Extension description:\",\n      packageData.description\n    )\n\n    if (this.isExample) {\n      delete packageData.packageManager\n      packageData.contributors = [\n        await quickPrompt(\"Contributor name:\", packageData.author)\n      ]\n      packageData.author = \"Plasmo Corp. <foss@plasmo.com>\"\n    } else {\n      delete packageData.contributors\n      packageData.author = await quickPrompt(\"Author name:\", packageData.author)\n\n      if (resolveWorkspaceRefs) {\n        vLog(\n          \"Replace workspace refs with the latest package version from npm registry\"\n        )\n        const resolvedDeps = await Promise.all([\n          resolveWorkspaceToLatestSemver(packageData.dependencies),\n          resolveWorkspaceToLatestSemver(packageData.devDependencies)\n        ])\n\n        packageData.dependencies = resolvedDeps[0]\n        packageData.devDependencies = resolvedDeps[1]\n      }\n    }\n\n    await outputJson(packageFilePath, packageData, {\n      spaces: 2\n    })\n  }\n\n  async copyBlankInitFiles() {\n    const entry = getFlag(\"--entry\") || \"popup\"\n\n    const entryFiles = entry\n      .split(new RegExp(\",|\\\\s\"))\n      .flatMap((e) => [`${e}.ts`, `${e}.tsx`])\n      .map((e) => [\n        resolve(this.templatePath.initEntryPath, e),\n        resolve(this.commonPath.projectDirectory, e)\n      ])\n      .filter(([entryPath]) => existsSync(entryPath))\n\n    vLog(\"Using the following entry files: \", entryFiles)\n\n    await Promise.all([\n      copy(\n        this.templatePath.initTemplatePath,\n        this.commonPath.projectDirectory\n      ),\n      this.copyBppWorkflow(),\n      writeFile(this.commonPath.gitIgnorePath, generateGitIgnore())\n    ])\n\n    await Promise.all(entryFiles.map(([src, dest]) => copy(src, dest)))\n  }\n\n  async copyBppWorkflow() {\n    if (this.isExample || hasFlag(\"--no-bpp\")) {\n      return\n    }\n\n    vLog(`Copying BPP workflow...`)\n    const bppSubmitWorkflowYamlPath = resolve(\n      this.commonPath.projectDirectory,\n      \".github\",\n      \"workflows\",\n      \"submit.yml\"\n    )\n\n    return copy(this.templatePath.bppYaml, bppSubmitWorkflowYamlPath)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/install-dependencies.ts",
    "content": "import spawnAsync from \"@expo/spawn-async\"\n\nimport { iLog, wLog } from \"@plasmo/utils/logging\"\n\nimport type { PackageManagerInfo } from \"~features/helpers/package-manager\"\n\nexport const installDependencies = async (\n  projectDirectory: string,\n  packageManager: PackageManagerInfo\n) => {\n  try {\n    iLog(\"Installing dependencies...\")\n    await spawnAsync(packageManager.name, [\"install\"], {\n      cwd: projectDirectory,\n      stdio: \"inherit\"\n    })\n  } catch (error: any) {\n    wLog(error.message)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/src/features/project-creator/print-ready.ts",
    "content": "import { sLog } from \"@plasmo/utils/logging\"\n\nimport type { CommonPath } from \"~features/extension-devtools/common-path\"\nimport type { PackageManagerInfo } from \"~features/helpers/package-manager\"\n\nexport const printReady = async (\n  projectDirectory: string,\n  currentDirectory: string,\n  commonPath: CommonPath,\n  packageManager: PackageManagerInfo\n) => {\n  const { default: chalk } = await import(\"chalk\")\n\n  sLog(\n    \"Your extension is ready in: \",\n    chalk.yellowBright(projectDirectory),\n    `\\n\\n    To start hacking, run:\\n\\n`,\n    projectDirectory === currentDirectory\n      ? \"\"\n      : `      cd ${commonPath.packageName}\\n`,\n    `      ${packageManager.name} ${\n      packageManager.name === \"npm\" ? \"run dev\" : \"dev\"\n    }\\n`,\n    \"\\n    Visit https://docs.plasmo.com for documentation and more examples.\"\n  )\n}\n"
  },
  {
    "path": "cli/plasmo/src/index.ts",
    "content": "#!/usr/bin/env node\nimport { argv, exit, versions } from \"process\"\nimport semver from \"semver\"\n\nimport { ErrorMessage } from \"@plasmo/constants/error\"\nimport { verbose } from \"@plasmo/utils/flags\"\nimport { eLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { runMap, validCommandSet, type ValidCommand } from \"~commands\"\nimport { printHeader, printHelp } from \"~features/helpers/print\"\n\nasync function defaultMode() {\n  printHeader()\n\n  printHelp()\n}\n\nasync function main() {\n  try {\n    // In case someone pasted an essay into the cli\n    if (argv.length > 10) {\n      throw new Error(ErrorMessage.TooManyArg)\n    }\n\n    if (semver.major(versions.node) < 16) {\n      throw new Error(\"Node version must be >= 16\")\n    }\n\n    process.env.VERBOSE = verbose ? \"true\" : \"false\"\n\n    // Setting startup policy/daemon\n    const mode = argv.find((arg) =>\n      validCommandSet.has(arg as ValidCommand)\n    ) as ValidCommand\n\n    if (mode in runMap) {\n      vLog(\"Running command:\", mode)\n\n      const { default: runner } = await runMap[mode]()\n\n      await runner()\n    } else {\n      vLog(\"Running default mode\")\n      await defaultMode()\n    }\n  } catch (e) {\n    eLog((e as Error)?.message || ErrorMessage.Unknown)\n    vLog(e?.stack)\n    exit(1)\n  }\n}\n\nmain()\n\nprocess.on(\"SIGINT\", () => exit(0))\nprocess.on(\"SIGTERM\", () => exit(0))\n"
  },
  {
    "path": "cli/plasmo/src/type.ts",
    "content": "import * as React from 'react';\nimport type { Root } from \"react-dom/client\"\n\nimport type { ManifestContentScript } from \"@plasmo/constants/manifest/content-script\"\n\n// See https://www.plasmo.com/engineering/log/2022.04#update-2022.04.23\nexport type PlasmoCSConfig = Omit<Partial<ManifestContentScript>, \"js\">\n\n/**\n * @deprecated use **PlasmoCSConfig** instead\n */\nexport type PlasmoContentScript = PlasmoCSConfig\n\ntype Async<T> = Promise<T> | T\n\ntype Getter<T, P = any> = (props?: P) => Async<T>\n\ntype InsertPosition = \"beforebegin\" | \"afterbegin\" | \"beforeend\" | \"afterend\"\n\ntype ElementInsertOptions = {\n  element: Element\n  insertPosition?: InsertPosition\n}\n\ntype ElementInsertOptionsList = ElementInsertOptions[]\n\ntype GetElement = Getter<Element>\ntype GetElementInsertOptions = Getter<ElementInsertOptions>\n\ntype PlasmoCSUIOverlayAnchor = {\n  element: Element\n  root?: Root\n  type: \"overlay\"\n}\n\ntype PlasmoCSUIInlineAnchor = {\n  element: Element\n  type: \"inline\"\n  insertPosition?: InsertPosition\n  root?: Root\n}\n\nexport type PlasmoCSUIAnchor = PlasmoCSUIOverlayAnchor | PlasmoCSUIInlineAnchor\n\nexport type PlasmoCSUIProps = {\n  anchor?: PlasmoCSUIAnchor\n}\n\nexport type PlasmoCSUIMountState = {\n  document: Document\n  observer: MutationObserver | null\n\n  mountInterval: NodeJS.Timer | null\n\n  isMounting: boolean\n  isMutated: boolean\n  /**\n   * Used to quickly check if element is already mounted\n   */\n  hostSet: Set<Element>\n\n  /**\n   * Used to add more metadata to the host Set\n   */\n  hostMap: WeakMap<Element, PlasmoCSUIAnchor>\n\n  /**\n   * Used to align overlay anchor with elements on the page\n   */\n  overlayTargetList: Element[]\n}\n\nexport type PlasmoGetRootContainer = (\n  props: {\n    mountState?: PlasmoCSUIMountState\n  } & PlasmoCSUIProps\n) => Async<Element>\n\nexport type PlasmoGetOverlayAnchor = GetElement\nexport type PlasmoGetOverlayAnchorList = Getter<NodeList>\n\nexport type PlasmoGetInlineAnchor = GetElement | GetElementInsertOptions\nexport type PlasmoGetInlineAnchorList = Getter<\n  NodeList | ElementInsertOptionsList\n>\n\nexport type PlasmoMountShadowHost = (\n  props: {\n    mountState?: PlasmoCSUIMountState\n    shadowHost: Element\n  } & PlasmoCSUIProps\n) => Async<void>\n\nexport type PlasmoGetShadowHostId = Getter<string, PlasmoCSUIAnchor>\n\nexport type PlasmoGetStyle = Getter<\n  HTMLStyleElement,\n  PlasmoCSUIAnchor & { sfcStyleContent?: string }\n>\n\nexport type PlasmoGetSfcStyleContent = Getter<string>\n\n/**\n * @return a cleanup unwatch function that will be run when unmounted\n */\nexport type PlasmoWatchOverlayAnchor = (\n  updatePosition: () => Promise<void>\n) => (() => void) | void\n\nexport type PlasmoCSUIContainerProps = {\n  id?: string\n  children?: React.ReactNode\n  watchOverlayAnchor?: PlasmoWatchOverlayAnchor\n} & PlasmoCSUIProps\n\nexport type PlasmoCSUIJSXContainer = (\n  p?: PlasmoCSUIContainerProps\n) => React.JSX.Element\nexport type PlasmoCSUIHTMLContainer = (\n  p?: PlasmoCSUIContainerProps\n) => HTMLElement\n\nexport type PlasmoCreateShadowRoot = (\n  shadowHost: HTMLElement\n) => Async<ShadowRoot>\n\nexport type PlasmoRender<T> = (\n  props: {\n    createRootContainer?: (p?: PlasmoCSUIAnchor) => Async<Element>\n  } & PlasmoCSUIProps,\n  InlineCSUIContainer?: T,\n  OverlayCSUIContainer?: T\n) => Async<void>\n\nexport type PlasmoCSUIWatch = (props: {\n  render: (anchor: PlasmoCSUIAnchor) => Async<void>\n  observer: {\n    start: (render: (anchor?: PlasmoCSUIAnchor) => void) => void\n    mountState: PlasmoCSUIMountState\n  }\n}) => void\n\nexport type PlasmoCSUI<T> = {\n  default: any\n  getStyle: PlasmoGetStyle\n  getSfcStyleContent: PlasmoGetSfcStyleContent\n  getShadowHostId: PlasmoGetShadowHostId\n\n  getOverlayAnchor: PlasmoGetOverlayAnchor\n  getOverlayAnchorList: PlasmoGetOverlayAnchorList\n\n  getInlineAnchor: PlasmoGetInlineAnchor\n  getInlineAnchorList: PlasmoGetInlineAnchorList\n\n  getRootContainer: PlasmoGetRootContainer\n\n  createShadowRoot: PlasmoCreateShadowRoot\n  watchOverlayAnchor: PlasmoWatchOverlayAnchor\n  mountShadowHost: PlasmoMountShadowHost\n\n  render: PlasmoRender<T>\n\n  watch: PlasmoCSUIWatch\n}\n"
  },
  {
    "path": "cli/plasmo/templates/plasmo.d.ts",
    "content": "declare namespace NodeJS {\n  interface ProcessEnv {\n    NODE_ENV: \"development\" | \"production\"\n\n    PLASMO_BROWSER?:\n      | \"arc\"\n      | \"brave\"\n      | \"chrome\"\n      | \"chromium\"\n      | \"edge\"\n      | \"firefox\"\n      | \"gecko\"\n      | \"island\"\n      | \"opera\"\n      | \"plasmo\"\n      | \"safari\"\n      | \"sigmaos\"\n      | \"tor\"\n      | \"vivaldi\"\n      | \"waterfox\"\n      | \"yandex\"\n\n    PLASMO_MANIFEST_VERSION?: \"mv2\" | \"mv3\"\n\n    PLASMO_TARGET?: `${ProcessEnv[\"PLASMO_BROWSER\"]}-${ProcessEnv[\"PLASMO_MANIFEST_VERSION\"]}`\n\n    PLASMO_TAG?: string\n  }\n}\n\ndeclare module \"*.module.css\"\ndeclare module \"*.module.less\"\ndeclare module \"*.module.scss\"\ndeclare module \"*.module.sass\"\ndeclare module \"*.module.styl\"\ndeclare module \"*.module.pcss\"\n\ndeclare module \"react:*.svg\" {\n  import type { FunctionComponent, SVGProps } from \"react\"\n\n  const value: FunctionComponent<SVGProps<SVGSVGElement>>\n  export default value\n}\n\ndeclare module \"*.gql\"\ndeclare module \"*.graphql\"\n\ndeclare module \"react:*\"\n\ndeclare module \"https:*\"\n\ndeclare module \"url:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"data-text:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"data-base64:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"data-env:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"data-text-env:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"raw:*\" {\n  const value: string\n  export default value\n}\n\ndeclare module \"raw-env:*\" {\n  const value: string\n  export default value\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/background/index.ts",
    "content": "import \"./messaging\"\nimport \"~background\"\n"
  },
  {
    "path": "cli/plasmo/templates/static/common/csui-container-react.tsx",
    "content": "import React from \"react\"\n\nimport type { PlasmoCSUIContainerProps } from \"~type\"\n\nexport const OverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => {\n  const [top, setTop] = React.useState(0)\n  const [left, setLeft] = React.useState(0)\n\n  React.useEffect(() => {\n    // Handle overlay repositioning\n    if (props.anchor.type !== \"overlay\") {\n      return\n    }\n\n    const updatePosition = async () => {\n      const rect = props.anchor.element?.getBoundingClientRect()\n      if (!rect) {\n        return\n      }\n\n      const pos = {\n        left: rect.left + window.scrollX,\n        top: rect.top + window.scrollY\n      }\n\n      setLeft(pos.left)\n      setTop(pos.top)\n    }\n\n    updatePosition()\n\n    const unwatch = props.watchOverlayAnchor?.(updatePosition)\n    window.addEventListener(\"scroll\", updatePosition)\n    window.addEventListener(\"resize\", updatePosition)\n\n    return () => {\n      if (typeof unwatch === \"function\") {\n        unwatch()\n      }\n      window.removeEventListener(\"scroll\", updatePosition)\n      window.removeEventListener(\"resize\", updatePosition)\n    }\n  }, [props.anchor.element])\n\n  return (\n    <div\n      id={props.id}\n      className=\"plasmo-csui-container\"\n      style={{\n        display: \"flex\",\n        position: \"absolute\",\n        top,\n        left\n      }}>\n      {props.children}\n    </div>\n  )\n}\n\nexport const InlineCSUIContainer = (props: PlasmoCSUIContainerProps) => (\n  <div\n    id=\"plasmo-inline\"\n    className=\"plasmo-csui-container\"\n    style={{\n      display: \"flex\",\n      position: \"relative\",\n      top: 0,\n      left: 0\n    }}>\n    {props.children}\n  </div>\n)\n"
  },
  {
    "path": "cli/plasmo/templates/static/common/csui-container-vanilla.tsx",
    "content": "import type { PlasmoCSUIContainerProps } from \"~type\"\n\nexport const createOverlayCSUIContainer = (props: PlasmoCSUIContainerProps) => {\n  const container = document.createElement(\"div\")\n  container.className = \"plasmo-csui-container\"\n  container.id = props.id\n\n  container.style.cssText = `\n    display: flex;\n    position: relative;\n    top: 0px;\n    left: 0px;\n  `\n\n  if (props.anchor.type === \"overlay\") {\n    const updatePosition = async () => {\n      const rect = props.anchor.element.getBoundingClientRect()\n\n      if (!rect) {\n        return\n      }\n\n      const pos = {\n        left: rect.left + window.scrollX,\n        top: rect.top + window.scrollY\n      }\n\n      container.style.top = `${pos.top}px`\n      container.style.left = `${pos.left}px`\n    }\n\n    updatePosition()\n\n    props.watchOverlayAnchor?.(updatePosition)\n    window.addEventListener(\"scroll\", updatePosition)\n    window.addEventListener(\"resize\", updatePosition)\n  }\n\n  return container\n}\n\nexport const createInlineCSUIContainer = (props: PlasmoCSUIContainerProps) => {\n  const container = document.createElement(\"div\")\n  container.className = \"plasmo-csui-container\"\n  container.id = \"plasmo-inline\"\n\n  container.style.cssText = `\n    display: flex;\n    position: relative;\n    top: 0px;\n    left: 0px;\n  `\n\n  return container\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/common/csui.ts",
    "content": "import type { PlasmoCSUI, PlasmoCSUIAnchor, PlasmoCSUIMountState } from \"~type\"\n\nasync function createShadowDOM<T>(Mount: PlasmoCSUI<T>) {\n  const shadowHost = document.createElement(\"plasmo-csui\")\n\n  const shadowRoot =\n    typeof Mount.createShadowRoot === \"function\"\n      ? await Mount.createShadowRoot(shadowHost)\n      : shadowHost.attachShadow({ mode: \"open\" })\n\n  const shadowContainer = document.createElement(\"div\")\n\n  shadowContainer.id = \"plasmo-shadow-container\"\n  shadowContainer.style.zIndex = \"2147483647\"\n  shadowContainer.style.position = \"relative\"\n\n  shadowRoot.appendChild(shadowContainer)\n\n  return {\n    shadowHost,\n    shadowRoot,\n    shadowContainer\n  }\n}\n\nexport type PlasmoCSUIShadowDOM = Awaited<ReturnType<typeof createShadowDOM>>\n\nasync function injectAnchor<T>(\n  Mount: PlasmoCSUI<T>,\n  anchor: PlasmoCSUIAnchor,\n  { shadowHost, shadowRoot }: PlasmoCSUIShadowDOM,\n  mountState?: PlasmoCSUIMountState\n) {\n  if (typeof Mount.getStyle === \"function\") {\n    const sfcStyleContent =\n      typeof Mount.getSfcStyleContent === \"function\"\n        ? await Mount.getSfcStyleContent()\n        : \"\"\n    shadowRoot.prepend(await Mount.getStyle({ ...anchor, sfcStyleContent }))\n  }\n\n  if (typeof Mount.getShadowHostId === \"function\") {\n    shadowHost.id = await Mount.getShadowHostId(anchor)\n  }\n\n  if (typeof Mount.mountShadowHost === \"function\") {\n    await Mount.mountShadowHost({\n      shadowHost,\n      anchor,\n      mountState\n    })\n  } else if (anchor.type === \"inline\") {\n    anchor.element.insertAdjacentElement(\n      anchor.insertPosition || \"afterend\",\n      shadowHost\n    )\n  } else {\n    document.documentElement.prepend(shadowHost)\n  }\n}\n\nexport async function createShadowContainer<T>(\n  Mount: PlasmoCSUI<T>,\n  anchor: PlasmoCSUIAnchor,\n  mountState?: PlasmoCSUIMountState\n) {\n  const shadowDom = await createShadowDOM(Mount)\n\n  mountState?.hostSet.add(shadowDom.shadowHost)\n  mountState?.hostMap.set(shadowDom.shadowHost, anchor)\n\n  await injectAnchor(Mount, anchor, shadowDom, mountState)\n\n  return shadowDom.shadowContainer\n}\n\nconst isVisible = (el: Element) => {\n  if (!el) {\n    return false\n  }\n  const elementRect = el.getBoundingClientRect()\n  const elementStyle = globalThis.getComputedStyle(el)\n\n  // console.log(elementRect, elementStyle)\n\n  if (elementStyle.display === \"none\") {\n    return false\n  }\n\n  if (elementStyle.visibility === \"hidden\") {\n    return false\n  }\n\n  if (elementStyle.opacity === \"0\") {\n    return false\n  }\n\n  if (\n    elementRect.width === 0 &&\n    elementRect.height === 0 &&\n    elementStyle.overflow !== \"hidden\"\n  ) {\n    return false\n  }\n\n  // Check if the element is irrevocably off-screen:\n  if (\n    elementRect.x + elementRect.width < 0 ||\n    elementRect.y + elementRect.height < 0\n  ) {\n    return false\n  }\n\n  return true\n}\n\nexport function createAnchorObserver<T>(Mount: PlasmoCSUI<T>) {\n  const mountState: PlasmoCSUIMountState = {\n    document: document || window.document,\n    observer: null,\n\n    mountInterval: null,\n\n    isMounting: false,\n    isMutated: false,\n\n    hostSet: new Set(),\n    hostMap: new WeakMap(),\n\n    overlayTargetList: []\n  }\n\n  const isMounted = (el: Element | null) =>\n    el?.id\n      ? !!document.getElementById(el.id)\n      : el?.getRootNode({ composed: true }) === mountState.document\n\n  const hasInlineAnchor = typeof Mount.getInlineAnchor === \"function\"\n  const hasOverlayAnchor = typeof Mount.getOverlayAnchor === \"function\"\n\n  const hasInlineAnchorList = typeof Mount.getInlineAnchorList === \"function\"\n  const hasOverlayAnchorList = typeof Mount.getOverlayAnchorList === \"function\"\n\n  const shouldObserve =\n    hasInlineAnchor ||\n    hasOverlayAnchor ||\n    hasInlineAnchorList ||\n    hasOverlayAnchorList\n\n  if (!shouldObserve) {\n    return null\n  }\n\n  async function mountAnchors(render: (anchor?: PlasmoCSUIAnchor) => void) {\n    mountState.isMounting = true\n\n    const mountedInlineAnchorSet = new WeakSet()\n\n    // There should only be 1 overlay mount\n    let overlayHost: Element = null\n\n    // Go through mounted sets and check if they are still mounted\n    for (const el of mountState.hostSet) {\n      const anchor = mountState.hostMap.get(el)\n      const anchorExists = document.contains(anchor?.element)\n      if (isMounted(el) && anchorExists) {\n        if (anchor.type === \"inline\") {\n          mountedInlineAnchorSet.add(anchor.element)\n        } else if (anchor.type === \"overlay\") {\n          overlayHost = el\n        }\n      } else {\n        anchor.root?.unmount()\n        // Clean up the plasmo-csui element\n        el.remove()\n        mountState.hostSet.delete(el)\n      }\n    }\n\n    const [inlineAnchor, inlineAnchorList, overlayAnchor, overlayAnchorList] =\n      await Promise.all([\n        hasInlineAnchor ? Mount.getInlineAnchor() : null,\n        hasInlineAnchorList ? Mount.getInlineAnchorList() : null,\n        hasOverlayAnchor ? Mount.getOverlayAnchor() : null,\n        hasOverlayAnchorList ? Mount.getOverlayAnchorList() : null\n      ])\n\n    const renderList: PlasmoCSUIAnchor[] = []\n\n    if (!!inlineAnchor) {\n      if (inlineAnchor instanceof Element) {\n        if (!mountedInlineAnchorSet.has(inlineAnchor)) {\n          renderList.push({\n            element: inlineAnchor,\n            type: \"inline\"\n          })\n        }\n      } else if (\n        inlineAnchor.element instanceof Element &&\n        !mountedInlineAnchorSet.has(inlineAnchor.element)\n      ) {\n        renderList.push({\n          element: inlineAnchor.element,\n          type: \"inline\",\n          insertPosition: inlineAnchor.insertPosition\n        })\n      }\n    }\n\n    if ((inlineAnchorList?.length || 0) > 0) {\n      inlineAnchorList.forEach((inlineAnchor) => {\n        if (\n          inlineAnchor instanceof Element &&\n          !mountedInlineAnchorSet.has(inlineAnchor)\n        ) {\n          renderList.push({\n            element: inlineAnchor,\n            type: \"inline\"\n          })\n        } else if (\n          inlineAnchor.element instanceof Element &&\n          !mountedInlineAnchorSet.has(inlineAnchor.element)\n        ) {\n          renderList.push({\n            element: inlineAnchor.element,\n            type: \"inline\",\n            insertPosition: inlineAnchor.insertPosition\n          })\n        }\n      })\n    }\n\n    const overlayTargetList = []\n\n    if (!!overlayAnchor && isVisible(overlayAnchor)) {\n      overlayTargetList.push(overlayAnchor)\n    }\n\n    if ((overlayAnchorList?.length || 0) > 0) {\n      overlayAnchorList.forEach((el) => {\n        if (el instanceof Element && isVisible(el)) {\n          overlayTargetList.push(el)\n        }\n      })\n    }\n\n    if (overlayTargetList.length > 0) {\n      mountState.overlayTargetList = overlayTargetList\n      if (!overlayHost) {\n        renderList.push({\n          element: document.documentElement,\n          type: \"overlay\"\n        })\n      } else {\n        // force re-render\n      }\n    } else {\n      overlayHost?.remove()\n      mountState.hostSet.delete(overlayHost)\n    }\n\n    await Promise.all(renderList.map(render))\n\n    if (mountState.isMutated) {\n      mountState.isMutated = false\n      await mountAnchors(render)\n    }\n\n    mountState.isMounting = false\n  }\n\n  const start = (render: (anchor?: PlasmoCSUIAnchor) => void) => {\n    mountState.observer = new MutationObserver(() => {\n      if (mountState.isMounting) {\n        mountState.isMutated = true\n        return\n      }\n      mountAnchors(render)\n    })\n\n    // Need to watch the subtree for shadowDOM\n    mountState.observer.observe(document.documentElement, {\n      childList: true,\n      subtree: true\n    })\n\n    mountState.mountInterval = setInterval(() => {\n      if (mountState.isMounting) {\n        mountState.isMutated = true\n        return\n      }\n      mountAnchors(render)\n    }, 142)\n  }\n\n  return {\n    start,\n    mountState\n  }\n}\n\nexport const createRender = <T>(\n  Mount: PlasmoCSUI<T>,\n  containers: [T, T],\n  mountState?: PlasmoCSUIMountState,\n  renderFx?: (anchor: PlasmoCSUIAnchor, rootContainer: Element) => Promise<void>\n) => {\n  const createRootContainer = (anchor: PlasmoCSUIAnchor) =>\n    typeof Mount.getRootContainer === \"function\"\n      ? Mount.getRootContainer({\n          anchor,\n          mountState\n        })\n      : createShadowContainer(Mount, anchor, mountState)\n\n  if (typeof Mount.render === \"function\") {\n    return (anchor: PlasmoCSUIAnchor) =>\n      Mount.render(\n        {\n          anchor,\n          createRootContainer\n        },\n        ...containers\n      )\n  }\n\n  return async (anchor: PlasmoCSUIAnchor) => {\n    const rootContainer = await createRootContainer(anchor)\n    return renderFx(anchor, rootContainer)\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/common/react.ts",
    "content": "import { Fragment, type FC, type ReactNode } from \"react\"\n\nexport const getLayout = (RawImport: any): FC<{ children: ReactNode }> =>\n  typeof RawImport.Layout === \"function\"\n    ? RawImport.Layout\n    : typeof RawImport.getGlobalProvider === \"function\"\n    ? RawImport.getGlobalProvider()\n    : Fragment\n"
  },
  {
    "path": "cli/plasmo/templates/static/common/vue.ts",
    "content": "globalThis.__VUE_OPTIONS_API__ = true\nglobalThis.__VUE_PROD_DEVTOOLS__ = process.env.NODE_ENV !== \"production\"\nglobalThis.__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false\n"
  },
  {
    "path": "cli/plasmo/templates/static/react17/content-script-ui-mount.tsx",
    "content": "import React from \"react\"\nimport * as ReactDOM from \"react-dom\"\n\nimport { createAnchorObserver, createRender } from \"@plasmo-static-common/csui\"\nimport {\n  InlineCSUIContainer,\n  OverlayCSUIContainer\n} from \"@plasmo-static-common/csui-container-react\"\nimport { getLayout } from \"@plasmo-static-common/react\"\n\nimport type {\n  PlasmoCSUI,\n  PlasmoCSUIAnchor,\n  PlasmoCSUIJSXContainer\n} from \"~type\"\n\n// @ts-ignore\nimport * as RawMount from \"__plasmo_mount_content_script__\"\n\n// Escape parcel's static analyzer\nconst Mount = RawMount as PlasmoCSUI<PlasmoCSUIJSXContainer>\n\nconst observer = createAnchorObserver(Mount)\n\nconst render = createRender(\n  Mount,\n  [InlineCSUIContainer, OverlayCSUIContainer],\n  observer?.mountState,\n  async (anchor, rootContainer) => {\n    const Layout = getLayout(RawMount)\n\n    switch (anchor.type) {\n      case \"inline\": {\n        ReactDOM.render(\n          <Layout>\n            <InlineCSUIContainer anchor={anchor}>\n              <RawMount.default anchor={anchor} />\n            </InlineCSUIContainer>\n          </Layout>,\n          rootContainer\n        )\n        break\n      }\n      case \"overlay\": {\n        const targetList = observer?.mountState.overlayTargetList || [\n          anchor.element\n        ]\n\n        ReactDOM.render(\n          <Layout>\n            {targetList.map((target, i) => {\n              const id = `plasmo-overlay-${i}`\n              const innerAnchor: PlasmoCSUIAnchor = {\n                element: target,\n                type: \"overlay\"\n              }\n              return (\n                <OverlayCSUIContainer\n                  key={id}\n                  id={id}\n                  anchor={innerAnchor}\n                  watchOverlayAnchor={Mount.watchOverlayAnchor}>\n                  <RawMount.default anchor={innerAnchor} />\n                </OverlayCSUIContainer>\n              )\n            })}\n          </Layout>,\n          rootContainer\n        )\n        break\n      }\n    }\n  }\n)\n\nif (!!observer) {\n  observer.start(render)\n} else {\n  render({\n    element: document.documentElement,\n    type: \"overlay\"\n  })\n}\n\nif (typeof Mount.watch === \"function\") {\n  Mount.watch({\n    observer,\n    render\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/react17/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/react17/index.tsx",
    "content": "import React from \"react\"\nimport * as ReactDOM from \"react-dom\"\n\nimport { getLayout } from \"@plasmo-static-common/react\"\n\n// @ts-ignore\nimport * as Component from \"__plasmo_import_module__\"\n\nlet __plasmoRoot: HTMLElement = null\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  if (!!__plasmoRoot) {\n    return\n  }\n\n  const Layout = getLayout(Component)\n\n  __plasmoRoot = document.getElementById(\"__plasmo\")\n\n  ReactDOM.render(\n    <Layout>\n      <Component.default />\n    </Layout>,\n    __plasmoRoot\n  )\n})\n"
  },
  {
    "path": "cli/plasmo/templates/static/react18/content-script-ui-mount.tsx",
    "content": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport { createAnchorObserver, createRender } from \"@plasmo-static-common/csui\"\nimport {\n  InlineCSUIContainer,\n  OverlayCSUIContainer\n} from \"@plasmo-static-common/csui-container-react\"\nimport { getLayout } from \"@plasmo-static-common/react\"\n\nimport type {\n  PlasmoCSUI,\n  PlasmoCSUIAnchor,\n  PlasmoCSUIJSXContainer\n} from \"~type\"\n\n// @ts-ignore\nimport * as RawMount from \"__plasmo_mount_content_script__\"\n\n// Escape parcel's static analyzer\nconst Mount = RawMount as PlasmoCSUI<PlasmoCSUIJSXContainer>\n\nconst observer = createAnchorObserver(Mount)\n\nconst render = createRender(\n  Mount,\n  [InlineCSUIContainer, OverlayCSUIContainer],\n  observer?.mountState,\n  async (anchor, rootContainer) => {\n    const root = createRoot(rootContainer)\n    anchor.root = root\n\n    const Layout = getLayout(RawMount)\n\n    switch (anchor.type) {\n      case \"inline\": {\n        root.render(\n          <Layout>\n            <InlineCSUIContainer anchor={anchor}>\n              <RawMount.default anchor={anchor} />\n            </InlineCSUIContainer>\n          </Layout>\n        )\n        break\n      }\n      case \"overlay\": {\n        const targetList = observer?.mountState.overlayTargetList || [\n          anchor.element\n        ]\n\n        root.render(\n          <Layout>\n            {targetList.map((target, i) => {\n              const id = `plasmo-overlay-${i}`\n              const innerAnchor: PlasmoCSUIAnchor = {\n                element: target,\n                type: \"overlay\"\n              }\n              return (\n                <OverlayCSUIContainer\n                  key={id}\n                  id={id}\n                  anchor={innerAnchor}\n                  watchOverlayAnchor={Mount.watchOverlayAnchor}>\n                  <RawMount.default anchor={innerAnchor} />\n                </OverlayCSUIContainer>\n              )\n            })}\n          </Layout>\n        )\n        break\n      }\n    }\n  }\n)\n\nif (!!observer) {\n  observer.start(render)\n} else {\n  render({\n    element: document.documentElement,\n    type: \"overlay\"\n  })\n}\n\nif (typeof Mount.watch === \"function\") {\n  Mount.watch({\n    observer,\n    render\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/react18/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/react18/index.tsx",
    "content": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport { getLayout } from \"@plasmo-static-common/react\"\n\n// @ts-ignore\nimport * as Component from \"__plasmo_import_module__\"\n\nlet __plasmoRoot: HTMLElement = null\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  if (!!__plasmoRoot) {\n    return\n  }\n\n  __plasmoRoot = document.getElementById(\"__plasmo\")\n\n  const root = createRoot(__plasmoRoot)\n\n  const Layout = getLayout(Component)\n\n  root.render(\n    <Layout>\n      <Component.default />\n    </Layout>\n  )\n})\n"
  },
  {
    "path": "cli/plasmo/templates/static/react19/content-script-ui-mount.tsx",
    "content": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport { createAnchorObserver, createRender } from \"@plasmo-static-common/csui\"\nimport {\n  InlineCSUIContainer,\n  OverlayCSUIContainer\n} from \"@plasmo-static-common/csui-container-react\"\nimport { getLayout } from \"@plasmo-static-common/react\"\n\nimport type {\n  PlasmoCSUI,\n  PlasmoCSUIAnchor,\n  PlasmoCSUIJSXContainer\n} from \"~type\"\n\n// @ts-ignore\nimport * as RawMount from \"__plasmo_mount_content_script__\"\n\n// Escape parcel's static analyzer\nconst Mount = RawMount as PlasmoCSUI<PlasmoCSUIJSXContainer>\n\nconst observer = createAnchorObserver(Mount)\n\nconst render = createRender(\n  Mount,\n  [InlineCSUIContainer, OverlayCSUIContainer],\n  observer?.mountState,\n  async (anchor, rootContainer) => {\n    const root = createRoot(rootContainer)\n    anchor.root = root\n\n    const Layout = getLayout(RawMount)\n\n    switch (anchor.type) {\n      case \"inline\": {\n        root.render(\n          <Layout>\n            <InlineCSUIContainer anchor={anchor}>\n              <RawMount.default anchor={anchor} />\n            </InlineCSUIContainer>\n          </Layout>\n        )\n        break\n      }\n      case \"overlay\": {\n        const targetList = observer?.mountState.overlayTargetList || [\n          anchor.element\n        ]\n\n        root.render(\n          <Layout>\n            {targetList.map((target, i) => {\n              const id = `plasmo-overlay-${i}`\n              const innerAnchor: PlasmoCSUIAnchor = {\n                element: target,\n                type: \"overlay\"\n              }\n              return (\n                <OverlayCSUIContainer\n                  key={id}\n                  id={id}\n                  anchor={innerAnchor}\n                  watchOverlayAnchor={Mount.watchOverlayAnchor}>\n                  <RawMount.default anchor={innerAnchor} />\n                </OverlayCSUIContainer>\n              )\n            })}\n          </Layout>\n        )\n        break\n      }\n    }\n  }\n)\n\nif (!!observer) {\n  observer.start(render)\n} else {\n  render({\n    element: document.documentElement,\n    type: \"overlay\"\n  })\n}\n\nif (typeof Mount.watch === \"function\") {\n  Mount.watch({\n    observer,\n    render\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/react19/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/react19/index.tsx",
    "content": "import React from \"react\"\nimport { createRoot } from \"react-dom/client\"\n\nimport { getLayout } from \"@plasmo-static-common/react\"\n\n// @ts-ignore\nimport * as Component from \"__plasmo_import_module__\"\n\nlet __plasmoRoot: HTMLElement = null\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  if (!!__plasmoRoot) {\n    return\n  }\n\n  __plasmoRoot = document.getElementById(\"__plasmo\")\n\n  const root = createRoot(__plasmoRoot)\n\n  const Layout = getLayout(Component)\n\n  root.render(\n    <Layout>\n      <Component.default />\n    </Layout>\n  )\n})\n"
  },
  {
    "path": "cli/plasmo/templates/static/svelte4/content-script-ui-mount.ts",
    "content": "import { createAnchorObserver, createRender } from \"@plasmo-static-common/csui\"\nimport {\n  createInlineCSUIContainer,\n  createOverlayCSUIContainer\n} from \"@plasmo-static-common/csui-container-vanilla\"\n\nimport type {\n  PlasmoCSUI,\n  PlasmoCSUIAnchor,\n  PlasmoCSUIHTMLContainer\n} from \"~type\"\n\n// @ts-ignore\nimport * as RawMount from \"__plasmo_mount_content_script__\"\n\n// Escape parcel's static analyzer\nconst Mount = RawMount as PlasmoCSUI<PlasmoCSUIHTMLContainer>\n\nconst observer = createAnchorObserver(Mount)\n\nconst render = createRender(\n  Mount,\n  [createInlineCSUIContainer, createOverlayCSUIContainer],\n  observer?.mountState,\n  async (anchor, rootContainer) => {\n    switch (anchor.type) {\n      case \"inline\": {\n        const mountPoint = createInlineCSUIContainer({ anchor })\n        rootContainer.appendChild(mountPoint)\n        new Mount.default({\n          target: mountPoint,\n          props: {\n            anchor\n          }\n        })\n        break\n      }\n      case \"overlay\": {\n        const targetList = observer?.mountState.overlayTargetList || [\n          anchor.element\n        ]\n\n        targetList.forEach((target, i) => {\n          const id = `plasmo-overlay-${i}`\n          const innerAnchor: PlasmoCSUIAnchor = {\n            element: target,\n            type: \"overlay\"\n          }\n\n          const mountPoint = createOverlayCSUIContainer({\n            id,\n            anchor: innerAnchor,\n            watchOverlayAnchor: Mount.watchOverlayAnchor\n          })\n\n          rootContainer.appendChild(mountPoint)\n          new Mount.default({\n            target: mountPoint,\n            props: {\n              anchor: innerAnchor\n            }\n          })\n        })\n        break\n      }\n    }\n  }\n)\n\nif (!!observer) {\n  observer.start(render)\n} else {\n  render({\n    element: document.documentElement,\n    type: \"overlay\"\n  })\n}\n\nif (typeof Mount.watch === \"function\") {\n  Mount.watch({\n    observer,\n    render\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/svelte4/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/svelte4/index.ts",
    "content": "// @ts-nocheck\nimport * as Component from \"__plasmo_import_module__\"\n\nlet __plasmoRoot: HTMLElement = null\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  if (!!__plasmoRoot) {\n    return\n  }\n\n  __plasmoRoot = document.getElementById(\"__plasmo\")\n  new Component.default({\n    target: __plasmoRoot\n  })\n})\n"
  },
  {
    "path": "cli/plasmo/templates/static/vanilla/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/vanilla/index.ts",
    "content": "// @ts-nocheck\nimport \"__plasmo_import_module__\"\n"
  },
  {
    "path": "cli/plasmo/templates/static/vue3/content-script-ui-mount.ts",
    "content": "import { createApp } from \"vue\"\n\nimport { createAnchorObserver, createRender } from \"@plasmo-static-common/csui\"\nimport {\n  createInlineCSUIContainer,\n  createOverlayCSUIContainer\n} from \"@plasmo-static-common/csui-container-vanilla\"\n\nimport type {\n  PlasmoCSUI,\n  PlasmoCSUIAnchor,\n  PlasmoCSUIHTMLContainer\n} from \"~type\"\n\nimport \"@plasmo-static-common/vue\"\n\n// @ts-ignore\nimport RawMount from \"__plasmo_mount_content_script__\"\n// @ts-ignore\nimport SfcStyleContent from \"style-raw:__plasmo_mount_content_script__\"\n\n// Escape parcel's static analyzer\nconst Mount = (RawMount.plasmo || {}) as PlasmoCSUI<PlasmoCSUIHTMLContainer>\n\nif (typeof SfcStyleContent === \"string\") {\n  Mount.getSfcStyleContent = () => SfcStyleContent\n\n  if (typeof Mount.getStyle !== \"function\") {\n    Mount.getStyle = ({ sfcStyleContent }) => {\n      const element = document.createElement(\"style\")\n      element.textContent = sfcStyleContent\n      return element\n    }\n  }\n}\n\nconst observer = createAnchorObserver(Mount)\n\nconst render = createRender(\n  Mount,\n  [createInlineCSUIContainer, createOverlayCSUIContainer],\n  observer?.mountState,\n  async (anchor, rootContainer) => {\n    switch (anchor.type) {\n      case \"inline\": {\n        const mountPoint = createInlineCSUIContainer({ anchor })\n        rootContainer.appendChild(mountPoint)\n\n        const app = createApp(RawMount)\n        app.config.globalProperties.$anchor = anchor\n        app.mount(mountPoint)\n        break\n      }\n      case \"overlay\": {\n        const targetList = observer?.mountState.overlayTargetList || [\n          anchor.element\n        ]\n\n        targetList.forEach((target, i) => {\n          const id = `plasmo-overlay-${i}`\n          const innerAnchor: PlasmoCSUIAnchor = {\n            element: target,\n            type: \"overlay\"\n          }\n\n          const mountPoint = createOverlayCSUIContainer({\n            id,\n            anchor: innerAnchor,\n            watchOverlayAnchor: Mount.watchOverlayAnchor\n          })\n\n          rootContainer.appendChild(mountPoint)\n\n          const app = createApp(RawMount)\n          app.config.globalProperties.$anchor = innerAnchor\n          app.mount(mountPoint)\n        })\n        break\n      }\n    }\n  }\n)\n\nif (!!observer) {\n  observer.start(render)\n} else {\n  render({\n    element: document.documentElement,\n    type: \"overlay\"\n  })\n}\n\nif (typeof Mount.watch === \"function\") {\n  Mount.watch({\n    observer,\n    render\n  })\n}\n"
  },
  {
    "path": "cli/plasmo/templates/static/vue3/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>__plasmo_static_index_title__</title>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"__plasmo\"></div>\n    <script src=\"__plasmo_static_script__\" type=\"module\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "cli/plasmo/templates/static/vue3/index.ts",
    "content": "import { createApp } from \"vue\"\n\n// @ts-ignore\nimport * as Component from \"__plasmo_import_module__\"\n\nimport \"@plasmo-static-common/vue\"\n\ndocument.addEventListener(\"DOMContentLoaded\", () => {\n  const app = createApp(Component.default)\n  Component.default.prepare?.(app)\n  app.mount(\"#__plasmo\")\n})\n"
  },
  {
    "path": "cli/plasmo/templates/tsconfig.base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Plasmo Extension\",\n  \"files\": [\"./plasmo.d.ts\"],\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"skipLibCheck\": true,\n\n    \"target\": \"esnext\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"noEmit\": true,\n    \"incremental\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"resolveJsonModule\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"inlineSources\": false,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n\n    \"verbatimModuleSyntax\": true,\n    \"jsx\": \"preserve\"\n  }\n}\n"
  },
  {
    "path": "cli/plasmo/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework.json\",\n  \"include\": [\n    \"src/**/*.ts\",\n    \"templates/plasmo.d.ts\",\n    \"templates/static/**/*.ts\",\n    \"templates/static/**/*.tsx\"\n  ],\n  \"exclude\": [\"dist\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"outDir\": \"dist\",\n    \"baseUrl\": \".\",\n    \"lib\": [\"es2022\", \"dom\"],\n    \"jsx\": \"preserve\",\n    \"paths\": {\n      \"~*\": [\"./src/*\"],\n      \"@plasmo-static-common/*\": [\"./templates/static/common/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-bundler/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-bundler\",\n  \"version\": \"0.5.6\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"main\": \"dist/index.js\",\n  \"source\": \"src/index.ts\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"engines\": {\n    \"node\": \">= 16.0.0\",\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/graph\": \"2.9.3\",\n    \"@parcel/hash\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"nullthrows\": \"1.1.1\"\n  },\n  \"devDependencies\": {\n    \"@parcel/types\": \"2.9.3\",\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/bit-set.ts",
    "content": "import nullthrows from \"nullthrows\"\n\nconst BIGINT_ZERO = 0n\nconst BIGINT_ONE = 1n\nlet numberToBigInt = (v: number): bigint => globalThis.BigInt(v)\n\nlet bitUnion = (a: bigint, b: bigint): bigint => a | b\n\nexport class BitSet<T> {\n  _value: bigint\n  _lookup: Map<T, bigint>\n  _items: Array<T>\n\n  constructor({\n    initial,\n    items,\n    lookup\n  }: {\n    items: Array<T>\n    lookup: Map<T, bigint>\n    initial?: BitSet<T> | bigint\n  }) {\n    if (initial instanceof BitSet) {\n      this._value = initial._value\n    } else if (initial) {\n      this._value = initial\n    } else {\n      this._value = BIGINT_ZERO\n    }\n\n    this._items = items\n    this._lookup = lookup\n  }\n\n  static from<TT>(items: Array<TT>): BitSet<TT> {\n    let lookup: Map<TT, bigint> = new Map()\n    for (let i = 0; i < items.length; i++) {\n      lookup.set(items[i], numberToBigInt(i))\n    }\n\n    return new BitSet({ items, lookup })\n  }\n\n  static union<TT>(a: BitSet<TT>, b: BitSet<TT>): BitSet<TT> {\n    return new BitSet({\n      initial: bitUnion(a._value, b._value),\n      lookup: a._lookup,\n      items: a._items\n    })\n  }\n\n  private getIndex(item: T) {\n    return nullthrows(this._lookup.get(item), \"Item is missing from BitSet\")\n  }\n\n  add(item: T) {\n    this._value |= BIGINT_ONE << this.getIndex(item)\n  }\n\n  delete(item: T) {\n    this._value &= ~(BIGINT_ONE << this.getIndex(item))\n  }\n\n  has(item: T): boolean {\n    return Boolean(this._value & (BIGINT_ONE << this.getIndex(item)))\n  }\n\n  intersect(v: BitSet<T>) {\n    this._value = this._value & v._value\n  }\n\n  union(v: BitSet<T>) {\n    this._value = bitUnion(this._value, v._value)\n  }\n\n  clear() {\n    this._value = BIGINT_ZERO\n  }\n\n  cloneEmpty(): BitSet<T> {\n    return new BitSet({\n      lookup: this._lookup,\n      items: this._items\n    })\n  }\n\n  clone(): BitSet<T> {\n    return new BitSet({\n      lookup: this._lookup,\n      items: this._items,\n      initial: this._value\n    })\n  }\n\n  values(): Array<T> {\n    let values = []\n    let tmpValue = this._value\n    let i: number\n\n    while (tmpValue > BIGINT_ZERO) {\n      i = tmpValue.toString(2).length - 1\n\n      values.push(this._items[i])\n\n      tmpValue &= ~(BIGINT_ONE << numberToBigInt(i))\n    }\n\n    return values\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/can-merge.ts",
    "content": "export function canMerge(a, b) {\n  // Bundles can be merged if they have the same type and environment,\n  // unless they are explicitly marked as isolated or inline.\n  return (\n    a.type === b.type &&\n    a.env.context === b.env.context &&\n    a.bundleBehavior == null &&\n    b.bundleBehavior == null\n  )\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/create-bundle.ts",
    "content": "import type { Asset, BundleBehavior, Environment, Target } from \"@parcel/types\"\nimport nullthrows from \"nullthrows\"\n\nimport type { Bundle } from \"./types\"\n\nexport function createBundle(opts: {\n  uniqueKey?: string\n  target: Target\n  asset?: Asset\n  env?: Environment\n  type?: string\n  needsStableName?: boolean\n  bundleBehavior?: BundleBehavior | null | undefined\n}): Bundle {\n  if (opts.asset == null) {\n    return {\n      uniqueKey: opts.uniqueKey,\n      assets: new Set(),\n      internalizedAssetIds: [],\n      mainEntryAsset: null,\n      size: 0,\n      sourceBundles: new Set(),\n      target: opts.target,\n      type: nullthrows(opts.type),\n      env: nullthrows(opts.env),\n      needsStableName: Boolean(opts.needsStableName),\n      bundleBehavior: opts.bundleBehavior\n    }\n  }\n\n  let asset = nullthrows(opts.asset)\n  return {\n    uniqueKey: opts.uniqueKey,\n    assets: new Set([asset]),\n    internalizedAssetIds: [],\n    mainEntryAsset: asset,\n    size: asset.stats.size,\n    sourceBundles: new Set(),\n    target: opts.target,\n    type: opts.type ?? asset.type,\n    env: opts.env ?? asset.env,\n    needsStableName: Boolean(opts.needsStableName),\n    bundleBehavior: opts.bundleBehavior ?? asset.bundleBehavior\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/create-ideal-graph.ts",
    "content": "import invariant from \"assert\"\nimport { ALL_EDGE_TYPES, ContentGraph, Graph, NodeId } from \"@parcel/graph\"\nimport type { Asset, Dependency, MutableBundleGraph } from \"@parcel/types\"\nimport { DefaultMap, setEqual, setIntersect, setUnion } from \"@parcel/utils\"\nimport nullthrows from \"nullthrows\"\n\nimport { canMerge } from \"./can-merge\"\nimport { createBundle } from \"./create-bundle\"\nimport { getReachableBundleRoots } from \"./get-reachable-bundle-root\"\nimport { removeBundle } from \"./remove-bundle\"\nimport {\n  dependencyPriorityEdges,\n  type Bundle,\n  type BundleRoot,\n  type DependencyBundleGraph,\n  type IdealGraph,\n  type ResolvedBundlerConfig\n} from \"./types\"\n\nexport function createIdealGraph(\n  assetGraph: MutableBundleGraph,\n  config: ResolvedBundlerConfig,\n  entries: Map<Asset, Dependency>\n): IdealGraph {\n  // Asset to the bundle and group it's an entry of\n  let bundleRoots: Map<BundleRoot, [NodeId, NodeId]> = new Map()\n  let bundles: Map<string, NodeId> = new Map()\n  let dependencyBundleGraph: DependencyBundleGraph = new ContentGraph()\n  let assetReference: DefaultMap<\n    Asset,\n    Array<[Dependency, Bundle]>\n  > = new DefaultMap(() => [])\n  // A Graph of Bundles and a root node (dummy string), which models only Bundles, and connections to their\n  // referencing Bundle. There are no actual BundleGroup nodes, just bundles that take on that role.\n  let bundleGraph: Graph<Bundle | \"root\"> = new Graph()\n  let stack: Array<[BundleRoot, NodeId]> = []\n  let bundleRootEdgeTypes = {\n    parallel: 1,\n    lazy: 2\n  }\n  // ContentGraph that models bundleRoots, with parallel & async deps only to inform reachability\n  let bundleRootGraph: ContentGraph<\n    BundleRoot | \"root\",\n    typeof bundleRootEdgeTypes\n  > = new ContentGraph()\n  let bundleGroupBundleIds: Set<NodeId> = new Set()\n  // Models bundleRoots and the assets that require it synchronously\n  let reachableRoots: ContentGraph<Asset> = new ContentGraph()\n  let rootNodeId = nullthrows(bundleRootGraph.addNode(\"root\"))\n  let bundleGraphRootNodeId = nullthrows(bundleGraph.addNode(\"root\"))\n  bundleRootGraph.setRootNodeId(rootNodeId)\n  bundleGraph.setRootNodeId(bundleGraphRootNodeId)\n\n  // Step Create Entry Bundles\n  for (let [asset, dependency] of entries) {\n    let bundle = createBundle({\n      asset,\n      target: nullthrows(dependency.target),\n      needsStableName: dependency.isEntry\n    })\n    let nodeId = bundleGraph.addNode(bundle)\n    bundles.set(asset.id, nodeId)\n    bundleRoots.set(asset, [nodeId, nodeId])\n    bundleRootGraph.addEdge(\n      rootNodeId,\n      bundleRootGraph.addNodeByContentKey(asset.id, asset)\n    )\n    bundleGraph.addEdge(bundleGraphRootNodeId, nodeId)\n    dependencyBundleGraph.addEdge(\n      dependencyBundleGraph.addNodeByContentKeyIfNeeded(dependency.id, {\n        value: dependency,\n        type: \"dependency\"\n      }),\n      dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(nodeId), {\n        value: bundle,\n        type: \"bundle\"\n      }),\n      dependencyPriorityEdges[dependency.priority]\n    )\n    bundleGroupBundleIds.add(nodeId)\n  }\n\n  let assets = []\n  let typeChangeIds = new Set()\n\n  /**\n   * Step Create Bundles: Traverse the assetGraph (aka MutableBundleGraph) and create bundles\n   * for asset type changes, parallel, inline, and async or lazy dependencies,\n   * adding only that asset to each bundle, not its entire subgraph.\n   */\n  assetGraph.traverse<any>(\n    {\n      enter(node, context, actions) {\n        if (node.type === \"asset\") {\n          if (\n            context?.type === \"dependency\" &&\n            context?.value.isEntry &&\n            !entries.has(node.value)\n          ) {\n            // Skip whole subtrees of other targets by skipping those entries\n            actions.skipChildren()\n            return node\n          }\n\n          assets.push(node.value)\n          let bundleIdTuple = bundleRoots.get(node.value)\n\n          if (bundleIdTuple && bundleIdTuple[0] === bundleIdTuple[1]) {\n            // Push to the stack (only) when a new bundle is created\n            stack.push([node.value, bundleIdTuple[0]])\n          } else if (bundleIdTuple) {\n            // Otherwise, push on the last bundle that marks the start of a BundleGroup\n            stack.push([node.value, stack[stack.length - 1][1]])\n          }\n        } else if (node.type === \"dependency\") {\n          if (context == null) {\n            return node\n          }\n\n          let dependency = node.value\n\n          if (assetGraph.isDependencySkipped(dependency)) {\n            actions.skipChildren()\n            return node\n          }\n\n          invariant(context?.type === \"asset\")\n          let parentAsset = context.value\n          let assets = assetGraph.getDependencyAssets(dependency)\n\n          if (assets.length === 0) {\n            return node\n          }\n\n          for (let childAsset of assets) {\n            if (\n              dependency.priority === \"lazy\" ||\n              childAsset.bundleBehavior === \"isolated\" // An isolated Dependency, or Bundle must contain all assets it needs to load.\n            ) {\n              let bundleId = bundles.get(childAsset.id)\n              let bundle\n\n              if (bundleId == null) {\n                let firstBundleGroup = nullthrows(\n                  bundleGraph.getNode(stack[0][1])\n                )\n                invariant(firstBundleGroup !== \"root\")\n                bundle = createBundle({\n                  asset: childAsset,\n                  target: firstBundleGroup.target,\n                  needsStableName:\n                    dependency.bundleBehavior === \"inline\" ||\n                    childAsset.bundleBehavior === \"inline\"\n                      ? false\n                      : dependency.isEntry || dependency.needsStableName,\n                  bundleBehavior:\n                    dependency.bundleBehavior ?? childAsset.bundleBehavior\n                })\n                bundleId = bundleGraph.addNode(bundle)\n                bundles.set(childAsset.id, bundleId)\n                bundleRoots.set(childAsset, [bundleId, bundleId])\n                bundleGroupBundleIds.add(bundleId)\n                bundleGraph.addEdge(bundleGraphRootNodeId, bundleId)\n              } else {\n                bundle = nullthrows(bundleGraph.getNode(bundleId))\n                invariant(bundle !== \"root\")\n\n                if (\n                  // If this dependency requests isolated, but the bundle is not,\n                  // make the bundle isolated for all uses.\n                  dependency.bundleBehavior === \"isolated\" &&\n                  bundle.bundleBehavior == null\n                ) {\n                  bundle.bundleBehavior = dependency.bundleBehavior\n                }\n              }\n\n              dependencyBundleGraph.addEdge(\n                dependencyBundleGraph.addNodeByContentKeyIfNeeded(\n                  dependency.id,\n                  {\n                    value: dependency,\n                    type: \"dependency\"\n                  }\n                ),\n                dependencyBundleGraph.addNodeByContentKeyIfNeeded(\n                  String(bundleId),\n                  {\n                    value: bundle,\n                    type: \"bundle\"\n                  }\n                ),\n                dependencyPriorityEdges[dependency.priority]\n              )\n              continue\n            }\n\n            if (\n              parentAsset.type !== childAsset.type ||\n              dependency.priority === \"parallel\" ||\n              childAsset.bundleBehavior === \"inline\"\n            ) {\n              // The referencing bundleRoot is the root of a Bundle that first brings in another bundle (essentially the FIRST parent of a bundle, this may or may not be a bundleGroup)\n              let [referencingBundleRoot, bundleGroupNodeId] = nullthrows(\n                stack[stack.length - 1]\n              )\n              let bundleGroup = nullthrows(\n                bundleGraph.getNode(bundleGroupNodeId)\n              )\n              invariant(bundleGroup !== \"root\")\n              let bundleId\n              let referencingBundleId = nullthrows(\n                bundleRoots.get(referencingBundleRoot)\n              )[0]\n              let referencingBundle = nullthrows(\n                bundleGraph.getNode(referencingBundleId)\n              )\n              invariant(referencingBundle !== \"root\")\n              let bundle\n              bundleId = bundles.get(childAsset.id)\n\n              /**\n               * If this is an entry bundlegroup, we only allow one bundle per type in those groups\n               * So attempt to add the asset to the entry bundle if it's of the same type.\n               * This asset will be created by other dependency if it's in another bundlegroup\n               * and bundles of other types should be merged in the next step\n               */\n              let bundleGroupRootAsset = nullthrows(bundleGroup.mainEntryAsset)\n\n              if (\n                entries.has(bundleGroupRootAsset) &&\n                canMerge(bundleGroupRootAsset, childAsset) &&\n                dependency.bundleBehavior == null\n              ) {\n                bundleId = bundleGroupNodeId\n              }\n\n              if (bundleId == null) {\n                bundle = createBundle({\n                  // Bundles created from type changes shouldn't have an entry asset.\n                  asset: childAsset,\n                  type: childAsset.type,\n                  env: childAsset.env,\n                  bundleBehavior:\n                    dependency.bundleBehavior ?? childAsset.bundleBehavior,\n                  target: referencingBundle.target,\n                  needsStableName:\n                    childAsset.bundleBehavior === \"inline\" ||\n                    dependency.bundleBehavior === \"inline\" ||\n                    (dependency.priority === \"parallel\" &&\n                      !dependency.needsStableName)\n                      ? false\n                      : referencingBundle.needsStableName\n                })\n                bundleId = bundleGraph.addNode(bundle)\n\n                // Store Type-Change bundles for later since we need to know ALL bundlegroups they are part of to reduce/combine them\n                if (parentAsset.type !== childAsset.type) {\n                  typeChangeIds.add(bundleId)\n                }\n              } else {\n                bundle = bundleGraph.getNode(bundleId)\n                invariant(bundle != null && bundle !== \"root\")\n\n                if (\n                  // If this dependency requests isolated, but the bundle is not,\n                  // make the bundle isolated for all uses.\n                  dependency.bundleBehavior === \"isolated\" &&\n                  bundle.bundleBehavior == null\n                ) {\n                  bundle.bundleBehavior = dependency.bundleBehavior\n                }\n              }\n\n              bundles.set(childAsset.id, bundleId)\n              // A bundle can belong to multiple bundlegroups, all the bundle groups of it's\n              // ancestors, and all async and entry bundles before it are \"bundle groups\"\n              // TODO: We may need to track bundles to all bundleGroups it belongs to in the future.\n              bundleRoots.set(childAsset, [bundleId, bundleGroupNodeId])\n              bundleGraph.addEdge(referencingBundleId, bundleId)\n\n              if (bundleId != bundleGroupNodeId) {\n                dependencyBundleGraph.addEdge(\n                  dependencyBundleGraph.addNodeByContentKeyIfNeeded(\n                    dependency.id,\n                    {\n                      value: dependency,\n                      type: \"dependency\"\n                    }\n                  ),\n                  dependencyBundleGraph.addNodeByContentKeyIfNeeded(\n                    String(bundleId),\n                    {\n                      value: bundle,\n                      type: \"bundle\"\n                    }\n                  ),\n                  dependencyPriorityEdges.parallel\n                )\n              }\n\n              assetReference.get(childAsset).push([dependency, bundle])\n              continue\n            }\n          }\n        }\n\n        return node\n      },\n\n      exit(node) {\n        if (stack[stack.length - 1]?.[0] === node.value) {\n          stack.pop()\n        }\n      }\n    },\n    undefined\n  )\n\n  // Step Merge Type Change Bundles: Clean up type change bundles within the exact same bundlegroups\n  for (let [nodeIdA, a] of bundleGraph.nodes) {\n    //if bundle b bundlegroups ==== bundle a bundlegroups then combine type changes\n    if (!typeChangeIds.has(nodeIdA) || a === \"root\") continue\n    let bundleABundleGroups = getBundleGroupsForBundle(nodeIdA)\n\n    for (let [nodeIdB, b] of bundleGraph.nodes) {\n      if (\n        a !== \"root\" &&\n        b !== \"root\" &&\n        a !== b &&\n        typeChangeIds.has(nodeIdB) &&\n        canMerge(a, b)\n      ) {\n        let bundleBbundleGroups = getBundleGroupsForBundle(nodeIdB)\n\n        if (setEqual(bundleBbundleGroups, bundleABundleGroups)) {\n          let shouldMerge = true\n\n          for (let depId of dependencyBundleGraph.getNodeIdsConnectedTo(\n            dependencyBundleGraph.getNodeIdByContentKey(String(nodeIdB)),\n            ALL_EDGE_TYPES\n          )) {\n            let depNode = dependencyBundleGraph.getNode(depId)\n\n            // Cannot merge Dependency URL specifier type\n            if (\n              depNode &&\n              depNode.type === \"dependency\" &&\n              depNode.value.specifierType === \"url\"\n            ) {\n              shouldMerge = false\n              continue\n            }\n          }\n\n          if (!shouldMerge) continue\n          mergeBundle(nodeIdA, nodeIdB)\n        }\n      }\n    }\n  }\n\n  /**\n   *  Step Determine Reachability: Determine reachability for every asset from each bundleRoot.\n   * This is later used to determine which bundles to place each asset in. We build up two\n   * structures, one traversal each. ReachableRoots to store sync relationships,\n   * and bundleRootGraph to store the minimal availability through `parallel` and `async` relationships.\n   * The two graphs, are used to build up ancestorAssets, a structure which holds all availability by\n   * all means for each asset.\n   */\n  for (let [root] of bundleRoots) {\n    if (!entries.has(root)) {\n      bundleRootGraph.addNodeByContentKey(root.id, root) // Add in all bundleRoots to BundleRootGraph\n    }\n  }\n\n  // ReachableRoots is a Graph of Asset Nodes which represents a BundleRoot, to all assets (non-bundleroot assets\n  // available to it synchronously (directly) built by traversing the assetgraph once.\n  for (let [root] of bundleRoots) {\n    // Add sync relationships to ReachableRoots\n    let rootNodeId = reachableRoots.addNodeByContentKeyIfNeeded(root.id, root)\n    assetGraph.traverse((node, _, actions) => {\n      if (node.value === root) {\n        return\n      }\n\n      if (node.type === \"dependency\") {\n        let dependency = node.value\n\n        if (dependencyBundleGraph.hasContentKey(dependency.id)) {\n          if (dependency.priority !== \"sync\") {\n            let assets = assetGraph.getDependencyAssets(dependency)\n\n            if (assets.length === 0) {\n              return\n            }\n\n            invariant(assets.length === 1)\n            let bundleRoot = assets[0]\n            let bundle = nullthrows(\n              bundleGraph.getNode(nullthrows(bundles.get(bundleRoot.id)))\n            )\n\n            if (\n              bundle !== \"root\" &&\n              bundle.bundleBehavior == null &&\n              !bundle.env.isIsolated() &&\n              bundle.env.context === root.env.context\n            ) {\n              bundleRootGraph.addEdge(\n                bundleRootGraph.getNodeIdByContentKey(root.id),\n                bundleRootGraph.getNodeIdByContentKey(bundleRoot.id),\n                dependency.priority === \"parallel\"\n                  ? bundleRootEdgeTypes.parallel\n                  : bundleRootEdgeTypes.lazy\n              )\n            }\n          }\n        }\n\n        if (dependency.priority !== \"sync\") {\n          actions.skipChildren()\n        }\n\n        return\n      }\n\n      //asset node type\n      let asset = node.value\n\n      if (asset.bundleBehavior != null || root.type !== asset.type) {\n        actions.skipChildren()\n        return\n      }\n\n      let nodeId = reachableRoots.addNodeByContentKeyIfNeeded(\n        node.value.id,\n        node.value\n      )\n      reachableRoots.addEdge(rootNodeId, nodeId)\n    }, root)\n  }\n\n  // Maps a given bundleRoot to the assets reachable from it,\n  // and the bundleRoots reachable from each of these assets\n  let ancestorAssets: Map<BundleRoot, Set<Asset>> = new Map()\n\n  for (let entry of entries.keys()) {\n    // Initialize an empty set of ancestors available to entries\n    ancestorAssets.set(entry, new Set())\n  }\n\n  // Step Determine Availability\n  // Visit nodes in a topological order, visiting parent nodes before child nodes.\n  // This allows us to construct an understanding of which assets will already be\n  // loaded and available when a bundle runs, by pushing available assets downwards and\n  // computing the intersection of assets available through all possible paths to a bundle.\n  // We call this structure ancestorAssets, a Map that tracks a bundleRoot,\n  // to all assets available to it (meaning they will exist guaranteed when the bundleRoot is loaded)\n  //  The topological sort ensures all parents are visited before the node we want to process.\n  for (let nodeId of bundleRootGraph.topoSort(ALL_EDGE_TYPES)) {\n    const bundleRoot = bundleRootGraph.getNode(nodeId)\n    if (bundleRoot === \"root\") continue\n    invariant(bundleRoot != null)\n    let bundleGroupId = nullthrows(bundleRoots.get(bundleRoot))[1]\n    // At a BundleRoot, we access it's available assets (via ancestorAssets),\n    // and add to that all assets within the bundles in that BundleGroup.\n    // This set is available to all bundles in a particular bundleGroup because\n    // bundleGroups are just bundles loaded at the same time. However it is\n    // not true that a bundle's available assets = all assets of all the bundleGroups\n    // it belongs to. It's the intersection of those sets.\n    let available\n\n    if (bundleRoot.bundleBehavior === \"isolated\") {\n      available = new Set()\n    } else {\n      available = new Set(ancestorAssets.get(bundleRoot))\n\n      for (let bundleIdInGroup of [\n        bundleGroupId,\n        ...bundleGraph.getNodeIdsConnectedFrom(bundleGroupId)\n      ]) {\n        let bundleInGroup = nullthrows(bundleGraph.getNode(bundleIdInGroup))\n        invariant(bundleInGroup !== \"root\")\n\n        if (bundleInGroup.bundleBehavior != null) {\n          continue\n        }\n\n        for (let bundleRoot of bundleInGroup.assets) {\n          // Assets directly connected to current bundleRoot\n          let assetsFromBundleRoot = reachableRoots\n            .getNodeIdsConnectedFrom(\n              reachableRoots.getNodeIdByContentKey(bundleRoot.id)\n            )\n            .map((id) => nullthrows(reachableRoots.getNode(id)))\n\n          for (let asset of [bundleRoot, ...assetsFromBundleRoot]) {\n            available.add(asset)\n          }\n        }\n      }\n    }\n\n    //  Now that we have bundleGroup availability, we will propagate that down to all the children\n    //  of this bundleGroup. For a child, we also must maintain parallel availability. If it has\n    //  parallel siblings that come before it, those, too, are available to it. Add those parallel\n    //  available assets to the set of available assets for this child as well.\n    let children = bundleRootGraph.getNodeIdsConnectedFrom(\n      nodeId,\n      ALL_EDGE_TYPES\n    )\n    let parallelAvailability: Set<BundleRoot> = new Set()\n\n    for (let childId of children) {\n      let child = bundleRootGraph.getNode(childId)\n      invariant(child !== \"root\" && child != null)\n      let bundleBehavior = getBundleFromBundleRoot(child).bundleBehavior\n\n      if (bundleBehavior != null) {\n        continue\n      }\n\n      let isParallel = bundleRootGraph.hasEdge(\n        nodeId,\n        childId,\n        bundleRootEdgeTypes.parallel\n      )\n      // Most of the time, a child will have many parent bundleGroups,\n      // so the next time we peek at a child from another parent, we will\n      // intersect the availability built there with the previously computed\n      // availability. this ensures no matter which bundleGroup loads a particular bundle,\n      // it will only assume availability of assets it has under any circumstance\n      const childAvailableAssets = ancestorAssets.get(child)\n      let currentChildAvailable = isParallel\n        ? setUnion(parallelAvailability, available)\n        : available\n\n      if (childAvailableAssets != null) {\n        setIntersect(childAvailableAssets, currentChildAvailable)\n      } else {\n        ancestorAssets.set(child, new Set(currentChildAvailable))\n      }\n\n      if (isParallel) {\n        let assetsFromBundleRoot = reachableRoots\n          .getNodeIdsConnectedFrom(\n            reachableRoots.getNodeIdByContentKey(child.id)\n          )\n          .map((id) => nullthrows(reachableRoots.getNode(id)))\n        parallelAvailability = setUnion(\n          parallelAvailability,\n          assetsFromBundleRoot\n        )\n        parallelAvailability.add(child) //The next sibling should have older sibling available via parallel\n      }\n    }\n  }\n\n  // Step Internalize async bundles - internalize Async bundles if and only if,\n  // the bundle is synchronously available elsewhere.\n  // We can query sync assets available via reachableRoots. If the parent has\n  // the bundleRoot by reachableRoots AND ancestorAssets, internalize it.\n  for (let [id, bundleRoot] of bundleRootGraph.nodes) {\n    if (bundleRoot === \"root\") continue\n    let parentRoots = bundleRootGraph\n      .getNodeIdsConnectedTo(id, ALL_EDGE_TYPES)\n      .map((id) => nullthrows(bundleRootGraph.getNode(id)))\n    let canDelete =\n      getBundleFromBundleRoot(bundleRoot).bundleBehavior !== \"isolated\"\n    if (parentRoots.length === 0) continue\n\n    for (let parent of parentRoots) {\n      if (parent === \"root\") {\n        canDelete = false\n        continue\n      }\n\n      if (\n        reachableRoots.hasEdge(\n          reachableRoots.getNodeIdByContentKey(parent.id),\n          reachableRoots.getNodeIdByContentKey(bundleRoot.id)\n        ) ||\n        ancestorAssets.get(parent)?.has(bundleRoot)\n      ) {\n        let parentBundle = bundleGraph.getNode(\n          nullthrows(bundles.get(parent.id))\n        )\n        invariant(parentBundle != null && parentBundle !== \"root\")\n        parentBundle.internalizedAssetIds.push(bundleRoot.id)\n      } else {\n        canDelete = false\n      }\n    }\n\n    if (canDelete) {\n      deleteBundle(bundleRoot)\n    }\n  }\n\n  // Step Insert Or Share: Place all assets into bundles or create shared bundles. Each asset\n  // is placed into a single bundle based on the bundle entries it is reachable from.\n  // This creates a maximally code split bundle graph with no duplication.\n  for (let asset of assets) {\n    // Unreliable bundleRoot assets which need to pulled in by shared bundles or other means\n    let reachable: Array<BundleRoot> = getReachableBundleRoots(\n      asset,\n      reachableRoots\n    ).reverse()\n    let reachableEntries = []\n    let reachableNonEntries = []\n\n    // Filter out entries, since they can't have shared bundles.\n    // Neither can non-splittable, isolated, or needing of stable name bundles.\n    // Reserve those filtered out bundles since we add the asset back into them.\n    for (let a of reachable) {\n      if (\n        entries.has(a) ||\n        !a.isBundleSplittable ||\n        getBundleFromBundleRoot(a).needsStableName ||\n        getBundleFromBundleRoot(a).bundleBehavior === \"isolated\"\n      ) {\n        reachableEntries.push(a)\n      } else {\n        reachableNonEntries.push(a)\n      }\n    }\n\n    reachable = reachableNonEntries\n    // Filter out bundles from this asset's reachable array if\n    // bundle does not contain the asset in its ancestry\n    reachable = reachable.filter((b) => !ancestorAssets.get(b)?.has(asset))\n    // Finally, filter out bundleRoots (bundles) from this assets\n    // reachable if they are subgraphs, and reuse that subgraph bundle\n    // by drawing an edge. Essentially, if two bundles within an asset's\n    // reachable array, have an ancestor-subgraph relationship, draw that edge.\n    // This allows for us to reuse a bundle instead of making a shared bundle if\n    // a bundle represents the exact set of assets a set of bundles would share\n    // if a bundle b is a subgraph of another bundle f, reuse it, drawing an edge between the two\n    let canReuse: Set<BundleRoot> = new Set()\n\n    // console.log({\n    //   bundles\n    // })\n\n    for (let candidateSourceBundleRoot of reachable) {\n      let candidateSourceBundleId = nullthrows(\n        bundles.get(candidateSourceBundleRoot.id)\n      )\n\n      // console.log({\n      //   candidateSourceBundleRoot,\n      //   id: candidateSourceBundleRoot.id\n      // })\n\n      if (candidateSourceBundleRoot.env.isIsolated()) {\n        continue\n      }\n\n      let reuseableBundleId = bundles.get(asset.id)\n\n      // console.log({\n      //   reuseableBundleId\n      // })\n\n      if (reuseableBundleId != null) {\n        canReuse.add(candidateSourceBundleRoot)\n        bundleGraph.addEdge(candidateSourceBundleId, reuseableBundleId)\n        let reusableBundle = bundleGraph.getNode(reuseableBundleId)\n        invariant(reusableBundle !== \"root\" && reusableBundle != null)\n        reusableBundle.sourceBundles.add(candidateSourceBundleId)\n      } else {\n        // Asset is not a bundleRoot, but if its ancestor bundle (in the asset's reachable) can be\n        // reused as a subgraph of another bundleRoot in its reachable, reuse it\n\n        for (let otherReuseCandidate of reachable) {\n          if (candidateSourceBundleRoot === otherReuseCandidate) continue\n\n          let reusableCandidateReachable = getReachableBundleRoots(\n            otherReuseCandidate,\n            reachableRoots\n          ).filter((b) => {\n            return !ancestorAssets.get(b)?.has(otherReuseCandidate)\n          })\n\n          if (reusableCandidateReachable.includes(candidateSourceBundleRoot)) {\n            let reusableBundleId = nullthrows(\n              bundles.get(otherReuseCandidate.id)\n            )\n            canReuse.add(candidateSourceBundleRoot)\n            bundleGraph.addEdge(\n              nullthrows(bundles.get(candidateSourceBundleRoot.id)),\n              reusableBundleId\n            )\n            let reusableBundle = bundleGraph.getNode(reusableBundleId)\n            invariant(reusableBundle !== \"root\" && reusableBundle != null)\n            reusableBundle.sourceBundles.add(candidateSourceBundleId)\n          }\n        }\n      }\n    }\n\n    //Bundles that are reused should not be considered for shared bundles, so filter them out\n    reachable = reachable.filter((b) => !canReuse.has(b))\n\n    // Add assets to non-splittable bundles.\n    for (let entry of reachableEntries) {\n      let entryBundleId = nullthrows(bundles.get(entry.id))\n      let entryBundle = nullthrows(bundleGraph.getNode(entryBundleId))\n      invariant(entryBundle !== \"root\")\n      entryBundle.assets.add(asset)\n      entryBundle.size += asset.stats.size\n    }\n\n    // Create shared bundles for splittable bundles.\n    if (reachable.length > config.minBundles) {\n      let sourceBundles = reachable.map((a) => nullthrows(bundles.get(a.id)))\n      let key = reachable.map((a) => a.id).join(\",\")\n      let bundleId = bundles.get(key)\n      let bundle\n\n      if (bundleId == null) {\n        let firstSourceBundle = nullthrows(\n          bundleGraph.getNode(sourceBundles[0])\n        )\n        invariant(firstSourceBundle !== \"root\")\n        bundle = createBundle({\n          target: firstSourceBundle.target,\n          type: firstSourceBundle.type,\n          env: firstSourceBundle.env\n        })\n        bundle.sourceBundles = new Set(sourceBundles)\n        let sharedInternalizedAssets = new Set(\n          firstSourceBundle.internalizedAssetIds\n        )\n\n        for (let p of sourceBundles) {\n          let parentBundle = nullthrows(bundleGraph.getNode(p))\n          invariant(parentBundle !== \"root\")\n          if (parentBundle === firstSourceBundle) continue\n          setIntersect(\n            sharedInternalizedAssets,\n            new Set(parentBundle.internalizedAssetIds)\n          )\n        }\n\n        bundle.internalizedAssetIds = [...sharedInternalizedAssets]\n        bundleId = bundleGraph.addNode(bundle)\n        bundles.set(key, bundleId)\n      } else {\n        bundle = nullthrows(bundleGraph.getNode(bundleId))\n        invariant(bundle !== \"root\")\n      }\n\n      bundle.assets.add(asset)\n      bundle.size += asset.stats.size\n\n      for (let sourceBundleId of sourceBundles) {\n        if (bundleId !== sourceBundleId) {\n          bundleGraph.addEdge(sourceBundleId, bundleId)\n        }\n      }\n\n      dependencyBundleGraph.addNodeByContentKeyIfNeeded(String(bundleId), {\n        value: bundle,\n        type: \"bundle\"\n      })\n    } else if (reachable.length <= config.minBundles) {\n      for (let root of reachable) {\n        let bundle = nullthrows(\n          bundleGraph.getNode(nullthrows(bundles.get(root.id)))\n        )\n        invariant(bundle !== \"root\")\n        bundle.assets.add(asset)\n        bundle.size += asset.stats.size\n      }\n    }\n  }\n\n  // Step Merge Share Bundles: Merge any shared bundles under the minimum bundle size back into\n  // their source bundles, and remove the bundle.\n  // We should include \"bundle reuse\" as shared bundles that may be removed but the bundle itself would have to be retained\n  for (let [bundleNodeId, bundle] of bundleGraph.nodes) {\n    if (bundle === \"root\") continue\n\n    if (\n      bundle.sourceBundles.size > 0 &&\n      bundle.mainEntryAsset == null &&\n      bundle.size < config.minBundleSize\n    ) {\n      removeBundle(bundleGraph, bundleNodeId, assetReference)\n    }\n  }\n\n  // Step Remove Shared Bundles: Remove shared bundles from bundle groups that hit the parallel request limit.\n  for (let bundleGroupId of bundleGraph.getNodeIdsConnectedFrom(rootNodeId)) {\n    // Find shared bundles in this bundle group.\n    let bundleId = bundleGroupId\n    // We should include \"bundle reuse\" as shared bundles that may be removed but the bundle itself would have to be retained\n    let bundleIdsInGroup = getBundlesForBundleGroup(bundleId) //get all bundlegrups this bundle is an ancestor of\n\n    if (bundleIdsInGroup.length > config.maxParallelRequests) {\n      let sharedBundleIdsInBundleGroup = bundleIdsInGroup.filter((b) => {\n        let bundle = nullthrows(bundleGraph.getNode(b))\n        // shared bundles must have source bundles, we could have a bundle\n        // connected to another bundle that isn't a shared bundle, so check\n        return (\n          bundle !== \"root\" && bundle.sourceBundles.size > 0 && bundleId != b\n        )\n      })\n      let numBundlesInGroup = bundleIdsInGroup.length\n      // Sort the bundles so the smallest ones are removed first.\n      let sharedBundlesInGroup = sharedBundleIdsInBundleGroup\n        .map((id) => ({\n          id,\n          bundle: nullthrows(bundleGraph.getNode(id))\n        }))\n        .map(({ id, bundle }) => {\n          // For Flow\n          invariant(bundle !== \"root\")\n          return {\n            id,\n            bundle\n          }\n        })\n        .sort((a, b) => b.bundle.size - a.bundle.size)\n\n      // Remove bundles until the bundle group is within the parallel request limit.\n      while (\n        sharedBundlesInGroup.length > 0 &&\n        numBundlesInGroup > config.maxParallelRequests\n      ) {\n        let bundleTuple = sharedBundlesInGroup.pop()\n        let bundleToRemove = bundleTuple.bundle\n        let bundleIdToRemove = bundleTuple.id\n        //TODO add integration test where bundles in bunlde group > max parallel request limit & only remove a couple shared bundles\n        // but total # bundles still exceeds limit due to non shared bundles\n        // Add all assets in the shared bundle into the source bundles that are within this bundle group.\n        let sourceBundles = [...bundleToRemove.sourceBundles].filter((b) =>\n          bundleIdsInGroup.includes(b)\n        )\n\n        for (let sourceBundleId of sourceBundles) {\n          let sourceBundle = nullthrows(bundleGraph.getNode(sourceBundleId))\n          invariant(sourceBundle !== \"root\")\n          bundleToRemove.sourceBundles.delete(sourceBundleId)\n\n          for (let asset of bundleToRemove.assets) {\n            sourceBundle.assets.add(asset)\n            sourceBundle.size += asset.stats.size\n          }\n\n          //This case is specific to reused bundles, which can have shared bundles attached to it\n          for (let childId of bundleGraph.getNodeIdsConnectedFrom(\n            bundleIdToRemove\n          )) {\n            let child = bundleGraph.getNode(childId)\n            invariant(child !== \"root\" && child != null)\n            child.sourceBundles.add(sourceBundleId)\n            bundleGraph.addEdge(sourceBundleId, childId)\n          }\n\n          // needs to add test case where shared bundle is removed from ONE bundlegroup but not from the whole graph!\n          // Remove the edge from this bundle group to the shared bundle.\n          // If there is now only a single bundle group that contains this bundle,\n          // merge it into the remaining source bundles. If it is orphaned entirely, remove it.\n          let incomingNodeCount =\n            bundleGraph.getNodeIdsConnectedTo(bundleIdToRemove).length\n\n          if (\n            incomingNodeCount <= 2 && //Never fully remove reused bundles\n            bundleToRemove.mainEntryAsset == null\n          ) {\n            // If one bundle group removes a shared bundle, but the other *can* keep it, still remove because that shared bundle is pointless (only one source bundle)\n            removeBundle(bundleGraph, bundleIdToRemove, assetReference)\n            // Stop iterating through bundleToRemove's sourceBundles as the bundle has been removed.\n            break\n          } else {\n            bundleGraph.removeEdge(sourceBundleId, bundleIdToRemove)\n          }\n        }\n\n        numBundlesInGroup--\n      }\n    }\n  }\n\n  function deleteBundle(bundleRoot: BundleRoot) {\n    bundleGraph.removeNode(nullthrows(bundles.get(bundleRoot.id)))\n    bundleRoots.delete(bundleRoot)\n    bundles.delete(bundleRoot.id)\n\n    if (reachableRoots.hasContentKey(bundleRoot.id)) {\n      reachableRoots.replaceNodeIdsConnectedTo(\n        reachableRoots.getNodeIdByContentKey(bundleRoot.id),\n        []\n      )\n    }\n\n    if (bundleRootGraph.hasContentKey(bundleRoot.id)) {\n      bundleRootGraph.removeNode(\n        bundleRootGraph.getNodeIdByContentKey(bundleRoot.id)\n      )\n    }\n  }\n\n  function getBundleGroupsForBundle(nodeId: NodeId) {\n    let bundleGroupBundleIds = new Set()\n    bundleGraph.traverseAncestors(nodeId, (ancestorId) => {\n      if (\n        bundleGraph\n          .getNodeIdsConnectedTo(ancestorId) //if node is root, then dont add, otherwise do add.\n          .includes(bundleGraph.rootNodeId)\n      ) {\n        bundleGroupBundleIds.add(ancestorId)\n      }\n    })\n    return bundleGroupBundleIds\n  }\n\n  function getBundlesForBundleGroup(bundleGroupId) {\n    let bundlesInABundleGroup = []\n    bundleGraph.traverse((nodeId) => {\n      bundlesInABundleGroup.push(nodeId)\n    }, bundleGroupId)\n    return bundlesInABundleGroup\n  }\n\n  function mergeBundle(mainNodeId: NodeId, otherNodeId: NodeId) {\n    //merges assets of \"otherRoot\" into \"mainBundleRoot\"\n    let a = nullthrows(bundleGraph.getNode(mainNodeId))\n    let b = nullthrows(bundleGraph.getNode(otherNodeId))\n    invariant(a !== \"root\" && b !== \"root\")\n    let bundleRootB = nullthrows(b.mainEntryAsset)\n    let mainBundleRoot = nullthrows(a.mainEntryAsset)\n\n    for (let asset of a.assets) {\n      b.assets.add(asset)\n    }\n\n    a.assets = b.assets\n\n    for (let depId of dependencyBundleGraph.getNodeIdsConnectedTo(\n      dependencyBundleGraph.getNodeIdByContentKey(String(otherNodeId)),\n      ALL_EDGE_TYPES\n    )) {\n      dependencyBundleGraph.replaceNodeIdsConnectedTo(depId, [\n        dependencyBundleGraph.getNodeIdByContentKey(String(mainNodeId))\n      ])\n    }\n\n    //clean up asset reference\n    for (let dependencyTuple of assetReference.get(bundleRootB)) {\n      dependencyTuple[1] = a\n    }\n\n    //add in any lost edges\n    for (let nodeId of bundleGraph.getNodeIdsConnectedTo(otherNodeId)) {\n      bundleGraph.addEdge(nodeId, mainNodeId)\n    }\n\n    deleteBundle(bundleRootB)\n    let bundleGroupOfMain = nullthrows(bundleRoots.get(mainBundleRoot))[1]\n    bundleRoots.set(bundleRootB, [mainNodeId, bundleGroupOfMain])\n    bundles.set(bundleRootB.id, mainNodeId)\n  }\n\n  function getBundleFromBundleRoot(bundleRoot: BundleRoot): Bundle {\n    let bundle = bundleGraph.getNode(nullthrows(bundleRoots.get(bundleRoot))[0])\n    invariant(bundle !== \"root\" && bundle != null)\n    return bundle\n  }\n\n  return {\n    bundleGraph,\n    dependencyBundleGraph,\n    bundleGroupBundleIds,\n    assetReference\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/decorate-legacy-graph.ts",
    "content": "import invariant from \"assert\"\nimport { ALL_EDGE_TYPES, NodeId } from \"@parcel/graph\"\nimport type {\n  BundleGroup,\n  Bundle as LegacyBundle,\n  MutableBundleGraph\n} from \"@parcel/types\"\nimport nullthrows from \"nullthrows\"\n\nimport type { Bundle, IdealGraph } from \"./types\"\n\nexport function decorateLegacyGraph(\n  idealGraph: IdealGraph,\n  bundleGraph: MutableBundleGraph\n): void {\n  let idealBundleToLegacyBundle: Map<Bundle, LegacyBundle> = new Map()\n  let {\n    bundleGraph: idealBundleGraph,\n    dependencyBundleGraph,\n    bundleGroupBundleIds\n  } = idealGraph\n  let entryBundleToBundleGroup: Map<NodeId, BundleGroup> = new Map()\n\n  // Step Create Bundles: Create bundle groups, bundles, and shared bundles and add assets to them\n  for (let [bundleNodeId, idealBundle] of idealBundleGraph.nodes) {\n    if (idealBundle === \"root\") continue\n    let entryAsset = idealBundle.mainEntryAsset\n    let bundleGroup\n    let bundle\n\n    if (bundleGroupBundleIds.has(bundleNodeId)) {\n      let dependencies = dependencyBundleGraph\n        .getNodeIdsConnectedTo(\n          dependencyBundleGraph.getNodeIdByContentKey(String(bundleNodeId)),\n          ALL_EDGE_TYPES\n        )\n        .map((nodeId) => {\n          let dependency = nullthrows(dependencyBundleGraph.getNode(nodeId))\n          invariant(dependency.type === \"dependency\")\n          return dependency.value\n        })\n\n      for (let dependency of dependencies) {\n        bundleGroup = bundleGraph.createBundleGroup(\n          dependency,\n          idealBundle.target\n        )\n      }\n\n      invariant(bundleGroup)\n      entryBundleToBundleGroup.set(bundleNodeId, bundleGroup)\n      bundle = nullthrows(\n        bundleGraph.createBundle({\n          entryAsset: nullthrows(entryAsset),\n          needsStableName: idealBundle.needsStableName,\n          bundleBehavior: idealBundle.bundleBehavior,\n          target: idealBundle.target\n        })\n      )\n      bundleGraph.addBundleToBundleGroup(bundle, bundleGroup)\n    } else if (\n      idealBundle.sourceBundles.size > 0 &&\n      !idealBundle.mainEntryAsset\n    ) {\n      bundle = nullthrows(\n        bundleGraph.createBundle({\n          uniqueKey:\n            [...idealBundle.assets].map((asset) => asset.id).join(\",\") +\n            [...idealBundle.sourceBundles].join(\",\"),\n          needsStableName: idealBundle.needsStableName,\n          bundleBehavior: idealBundle.bundleBehavior,\n          type: idealBundle.type,\n          target: idealBundle.target,\n          env: idealBundle.env\n        })\n      )\n    } else if (idealBundle.uniqueKey != null) {\n      bundle = nullthrows(\n        bundleGraph.createBundle({\n          uniqueKey: idealBundle.uniqueKey,\n          needsStableName: idealBundle.needsStableName,\n          bundleBehavior: idealBundle.bundleBehavior,\n          type: idealBundle.type,\n          target: idealBundle.target,\n          env: idealBundle.env\n        })\n      )\n    } else {\n      invariant(entryAsset != null)\n      bundle = nullthrows(\n        bundleGraph.createBundle({\n          entryAsset,\n          needsStableName: idealBundle.needsStableName,\n          bundleBehavior: idealBundle.bundleBehavior,\n          target: idealBundle.target\n        })\n      )\n    }\n\n    idealBundleToLegacyBundle.set(idealBundle, bundle)\n\n    for (let asset of idealBundle.assets) {\n      bundleGraph.addAssetToBundle(asset, bundle)\n    }\n  }\n\n  // Step Internalization: Internalize dependencies for bundles\n  for (let [, idealBundle] of idealBundleGraph.nodes) {\n    if (idealBundle === \"root\") continue\n    let bundle = nullthrows(idealBundleToLegacyBundle.get(idealBundle))\n    if (idealBundle.internalizedAssets) {\n      for (let internalized of idealBundle.internalizedAssets.values()) {\n        let incomingDeps = bundleGraph.getIncomingDependencies(internalized)\n        for (let incomingDep of incomingDeps) {\n          if (\n            incomingDep.priority === \"lazy\" &&\n            incomingDep.specifierType !== \"url\" &&\n            bundle.hasDependency(incomingDep)\n          ) {\n            bundleGraph.internalizeAsyncDependency(bundle, incomingDep)\n          }\n        }\n      }\n    }\n  }\n\n  // Step Add to BundleGroups: Add bundles to their bundle groups\n  idealBundleGraph.traverse((nodeId, _, actions) => {\n    let node = idealBundleGraph.getNode(nodeId)\n\n    if (node === \"root\") {\n      return\n    }\n\n    actions.skipChildren()\n    let outboundNodeIds = idealBundleGraph.getNodeIdsConnectedFrom(nodeId)\n    let entryBundle = nullthrows(idealBundleGraph.getNode(nodeId))\n    invariant(entryBundle !== \"root\")\n    let legacyEntryBundle = nullthrows(\n      idealBundleToLegacyBundle.get(entryBundle)\n    )\n\n    for (let id of outboundNodeIds) {\n      let siblingBundle = nullthrows(idealBundleGraph.getNode(id))\n      invariant(siblingBundle !== \"root\")\n      let legacySiblingBundle = nullthrows(\n        idealBundleToLegacyBundle.get(siblingBundle)\n      )\n      bundleGraph.createBundleReference(legacyEntryBundle, legacySiblingBundle)\n    }\n  })\n\n  // Step References: Add references to all bundles\n  for (let [asset, references] of idealGraph.assetReference) {\n    for (let [dependency, bundle] of references) {\n      let legacyBundle = nullthrows(idealBundleToLegacyBundle.get(bundle))\n      bundleGraph.createAssetReference(dependency, asset, legacyBundle)\n    }\n  }\n\n  for (let { from, to } of idealBundleGraph.getAllEdges()) {\n    let sourceBundle = nullthrows(idealBundleGraph.getNode(from))\n\n    if (sourceBundle === \"root\") {\n      continue\n    }\n\n    invariant(sourceBundle !== \"root\")\n    let legacySourceBundle = nullthrows(\n      idealBundleToLegacyBundle.get(sourceBundle)\n    )\n    let targetBundle = nullthrows(idealBundleGraph.getNode(to))\n\n    if (targetBundle === \"root\") {\n      continue\n    }\n\n    invariant(targetBundle !== \"root\")\n    let legacyTargetBundle = nullthrows(\n      idealBundleToLegacyBundle.get(targetBundle)\n    )\n    bundleGraph.createBundleReference(legacySourceBundle, legacyTargetBundle)\n  }\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/get-entry-by-target.ts",
    "content": "// @ts-nocheck\nimport invariant from \"assert\"\nimport type { Asset, Dependency, MutableBundleGraph } from \"@parcel/types\"\nimport { DefaultMap } from \"@parcel/utils\"\n\nexport function getEntryByTarget(\n  bundleGraph: MutableBundleGraph\n): DefaultMap<string, Map<Asset, Dependency>> {\n  // Find entries from assetGraph per target\n  let targets: DefaultMap<string, Map<Asset, Dependency>> = new DefaultMap(\n    () => new Map()\n  )\n  bundleGraph.traverse(\n    {\n      enter(node, context, actions) {\n        if (node.type !== \"asset\") {\n          return node\n        }\n\n        invariant(\n          context != null &&\n            context.type === \"dependency\" &&\n            context.value.isEntry &&\n            context.value.target != null\n        )\n        targets.get(context.value.target.distDir).set(node.value, context.value)\n        actions.skipChildren()\n        return node\n      }\n    },\n    undefined\n  )\n\n  return targets\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/get-reachable-bundle-root.ts",
    "content": "import nullthrows from \"nullthrows\"\n\nimport type { BundleRoot } from \"./types\"\n\nexport function getReachableBundleRoots(asset, graph): Array<BundleRoot> {\n  return graph\n    .getNodeIdsConnectedTo(graph.getNodeIdByContentKey(asset.id))\n    .map((nodeId) => nullthrows(graph.getNode(nodeId)))\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/blob/v2/packages/bundlers/default/package.json\n * MIT License\n */\n\nimport { Bundler } from \"@parcel/plugin\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { createIdealGraph } from \"./create-ideal-graph\"\nimport { decorateLegacyGraph } from \"./decorate-legacy-graph\"\nimport { getEntryByTarget } from \"./get-entry-by-target\"\n\nconst EXTENSION_OPTIONS = {\n  minBundles: 1_000_000_000,\n  minBundleSize: 2_400,\n  maxParallelRequests: 20\n}\n\n/**\n *\n * The Bundler works by creating an IdealGraph, which contains a BundleGraph that models bundles\n * connected to othervbundles by what references them, and thus models BundleGroups.\n *\n * First, we enter `bundle({bundleGraph, config})`. Here, \"bundleGraph\" is actually just the\n * assetGraph turned into a type `MutableBundleGraph`, which will then be mutated in decorate,\n * and turned into what we expect the bundleGraph to be as per the old (default) bundler structure\n *  & what the rest of Parcel expects a BundleGraph to be.\n *\n * `bundle({bundleGraph, config})` First gets a Mapping of target to entries, In most cases there is\n *  only one target, and one or more entries. (Targets are pertinent in monorepos or projects where you\n *  will have two or more distDirs, or output folders.) Then calls create IdealGraph and Decorate per target.\n *\n */\nexport default new Bundler({\n  loadConfig({ options }) {\n    // TODO: Maybe depend on whether it's BGSW or not, we enable bundle splitting\n    // console.log(options)\n\n    return EXTENSION_OPTIONS\n  },\n\n  bundle({ bundleGraph, config }) {\n    vLog(\"@plasmohq/parcel-bundler\")\n    let targetMap = getEntryByTarget(bundleGraph) // Organize entries by target output folder/ distDir\n\n    let graphs = []\n\n    for (let entries of targetMap.values()) {\n      // Create separate bundleGraphs per distDir\n      graphs.push(createIdealGraph(bundleGraph, config, entries))\n    }\n\n    for (let g of graphs) {\n      decorateLegacyGraph(g, bundleGraph) //mutate original graph\n    }\n  },\n\n  optimize() {}\n})\n"
  },
  {
    "path": "core/parcel-bundler/src/remove-bundle.ts",
    "content": "import invariant from \"assert\"\nimport type { Graph, NodeId } from \"@parcel/graph\"\nimport type { Asset, Dependency } from \"@parcel/types\"\nimport type { DefaultMap } from \"@parcel/utils\"\nimport nullthrows from \"nullthrows\"\n\nimport type { Bundle } from \"./types\"\n\nexport function removeBundle(\n  bundleGraph: Graph<Bundle | \"root\">,\n  bundleId: NodeId,\n  assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>\n) {\n  let bundle = nullthrows(bundleGraph.getNode(bundleId))\n  invariant(bundle !== \"root\")\n\n  for (let asset of bundle.assets) {\n    assetReference.set(\n      asset,\n      assetReference.get(asset).filter((t) => !t.includes(bundle))\n    )\n\n    for (let sourceBundleId of bundle.sourceBundles) {\n      let sourceBundle = nullthrows(bundleGraph.getNode(sourceBundleId))\n      invariant(sourceBundle !== \"root\")\n      sourceBundle.assets.add(asset)\n      sourceBundle.size += asset.stats.size\n    }\n  }\n\n  bundleGraph.removeNode(bundleId)\n}\n"
  },
  {
    "path": "core/parcel-bundler/src/types.ts",
    "content": "import type { ContentGraph, Graph, NodeId } from \"@parcel/graph\"\nimport type {\n  Asset,\n  BundleBehavior,\n  Dependency,\n  Environment,\n  Target\n} from \"@parcel/types\"\nimport type { DefaultMap } from \"@parcel/utils\"\n\nimport type { BitSet } from \"./bit-set\"\n\ntype AssetId = string\n\nexport type DependencyBundleGraph = ContentGraph<\n  | {\n      value: Bundle\n      type: \"bundle\"\n    }\n  | {\n      value: Dependency\n      type: \"dependency\"\n    },\n  number\n>\n// IdealGraph is the structure we will pass to decorate,\n// which mutates the assetGraph into the bundleGraph we would\n// expect from default bundler\nexport type IdealGraph = {\n  dependencyBundleGraph: DependencyBundleGraph\n  bundleGraph: Graph<Bundle | \"root\">\n  bundleGroupBundleIds: Set<NodeId>\n  assetReference: DefaultMap<Asset, Array<[Dependency, Bundle]>>\n}\n\nexport type Bundle = {\n  uniqueKey: string | null | undefined\n  assets: Set<Asset>\n  internalizedAsset?: BitSet<Asset>\n  internalizedAssetIds: Array<AssetId>\n  bundleBehavior?: BundleBehavior | null | undefined\n  needsStableName: boolean\n  mainEntryAsset: Asset | null | undefined\n  size: number\n  sourceBundles: Set<NodeId>\n  target: Target\n  env: Environment\n  type: string\n}\n\n/* BundleRoot - An asset that is the main entry of a Bundle. */\nexport type BundleRoot = Asset\n\nexport const dependencyPriorityEdges = {\n  sync: 1,\n  parallel: 2,\n  lazy: 3\n}\n\nexport type ResolvedBundlerConfig = {\n  minBundles: number\n  minBundleSize: number\n  maxParallelRequests: number\n}\n"
  },
  {
    "path": "core/parcel-bundler/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-compressor-utf8/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-compressor-utf8/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-compressor-utf8\",\n  \"version\": \"0.1.1\",\n  \"description\": \"Plasmo UTF8 Compressor for Extension\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup --minify --clean\",\n    \"dev\": \"tsup --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.8.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/plugin\": \"2.9.3\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-compressor-utf8/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n */\nimport { Compressor } from \"@parcel/plugin\"\n\nimport { Utf8Transform } from \"./utf8-transform\"\n\nexport default new Compressor({\n  async compress({ stream }) {\n    return {\n      stream: stream.pipe(new Utf8Transform())\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-compressor-utf8/src/utf8-transform.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n */\nimport { Transform, type TransformCallback } from \"stream\"\nimport { StringDecoder } from \"string_decoder\"\n\nclass Utf8Transform extends Transform {\n  private decoder = new StringDecoder(\"utf8\")\n\n  _transform(chunk: Buffer, _: BufferEncoding, callback: TransformCallback) {\n    callback(null, this.transformChunk(this.decoder.write(chunk)))\n  }\n\n  _flush(callback: TransformCallback) {\n    const remainder = this.decoder.end()\n    remainder ? callback(null, this.transformChunk(remainder)) : callback()\n  }\n\n  private transformChunk(chunk: string, segmentSize: number = 1024): string {\n    let result = \"\"\n    for (let i = 0; i < chunk.length; i += segmentSize) {\n      const endIndex = Math.min(i + segmentSize, chunk.length)\n      const segment = chunk.substring(i, endIndex)\n      result += this.transformSegment(segment)\n    }\n    return result\n  }\n\n  private transformSegment(segment: string): string {\n    return Array.from(segment)\n      .map((ch) =>\n        ch.charCodeAt(0) <= 0x7f\n          ? ch\n          : \"\\\\u\" + (\"0000\" + ch.charCodeAt(0).toString(16)).slice(-4)\n      )\n      .join(\"\")\n  }\n}\n\nexport { Utf8Transform }\n"
  },
  {
    "path": "core/parcel-compressor-utf8/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-compressor-utf8/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig({\n  entry: [\"src/index.ts\"]\n})\n"
  },
  {
    "path": "core/parcel-config/.gitignore",
    "content": "run.json"
  },
  {
    "path": "core/parcel-config/index.json",
    "content": "{\n  \"extends\": \"@parcel/config-default\",\n  \"bundler\": \"@plasmohq/parcel-bundler\",\n  \"resolvers\": [\n    \"@plasmohq/parcel-resolver\",\n    \"@parcel/resolver-default\",\n    \"@plasmohq/parcel-resolver-post\"\n  ],\n  \"transformers\": {\n    \"*.plasmo.manifest.json\": [\"@plasmohq/parcel-transformer-manifest\"],\n\n    \"react:*.svg\": [\"@parcel/transformer-svg-react\"],\n    \"react:*.{js,mjs,jsm,jsx,es6,cjs,ts,tsx}\": [\n      \"@parcel/transformer-babel\",\n      \"@parcel/transformer-js\",\n      \"@parcel/transformer-react-refresh-wrap\"\n    ],\n\n    \"data-text:*.module.{css,pcss}\": [\n      \"@parcel/transformer-postcss\",\n      \"@plasmohq/parcel-transformer-inline-css\",\n      \"@parcel/transformer-inline-string\"\n    ],\n\n    \"data-text:*\": [\"...\", \"@parcel/transformer-inline-string\"],\n    \"data-base64:*\": [\"...\", \"@parcel/transformer-inline-string\"],\n\n    \"data-env:*\": [\"@plasmohq/parcel-transformer-inject-env\", \"...\"],\n    \"data-text-env:*\": [\n      \"@plasmohq/parcel-transformer-inject-env\",\n      \"...\",\n      \"@parcel/transformer-inline-string\"\n    ],\n\n    \"raw:*\": [\"@parcel/transformer-raw\"],\n    \"raw-env:*\": [\n      \"@plasmohq/parcel-transformer-inject-env\",\n      \"@parcel/transformer-raw\"\n    ],\n\n    \"*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n    \"template:*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n    \"script:*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n    \"style:*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n    \"style-raw:*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n    \"custom:*.vue\": [\"@plasmohq/parcel-transformer-vue\"],\n\n    \"*.svelte\": [\"@plasmohq/parcel-transformer-svelte\"],\n\n    \"*.{gql,graphql}\": [\"@parcel/transformer-graphql\"],\n    \"*.{sass,scss}\": [\"@parcel/transformer-sass\"],\n    \"*.less\": [\"@parcel/transformer-less\"]\n  },\n  \"namers\": [\"@plasmohq/parcel-namer-manifest\", \"...\"],\n  \"packagers\": {\n    \"manifest.json\": \"@plasmohq/parcel-packager\"\n  },\n  \"optimizers\": {\n    \"data-base64:*\": [\"...\", \"@parcel/optimizer-data-url\"],\n    \"*.{js,mjs,cjs}\": [\n      \"@plasmohq/parcel-optimizer-encapsulate\",\n      \"@plasmohq/parcel-optimizer-es\"\n    ]\n  },\n  \"compressors\": {\n    \"*.js\": [\"@plasmohq/parcel-compressor-utf8\"],\n    \"*\": [\"@parcel/compressor-raw\"]\n  },\n  \"runtimes\": [\n    \"@parcel/runtime-js\",\n    \"@plasmohq/parcel-runtime\",\n    \"@parcel/runtime-service-worker\"\n  ]\n}\n"
  },
  {
    "path": "core/parcel-config/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-config\",\n  \"version\": \"0.42.0\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"main\": \"index.json\",\n  \"dependencies\": {\n    \"@parcel/compressor-raw\": \"2.9.3\",\n    \"@parcel/config-default\": \"2.9.3\",\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/optimizer-data-url\": \"2.9.3\",\n    \"@parcel/reporter-bundle-buddy\": \"2.9.3\",\n    \"@parcel/resolver-default\": \"2.9.3\",\n    \"@parcel/runtime-js\": \"2.8.3\",\n    \"@parcel/runtime-service-worker\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/transformer-babel\": \"2.9.3\",\n    \"@parcel/transformer-css\": \"2.9.3\",\n    \"@parcel/transformer-graphql\": \"2.9.3\",\n    \"@parcel/transformer-inline-string\": \"2.9.3\",\n    \"@parcel/transformer-js\": \"2.9.3\",\n    \"@parcel/transformer-less\": \"2.9.3\",\n    \"@parcel/transformer-postcss\": \"2.9.3\",\n    \"@parcel/transformer-raw\": \"2.9.3\",\n    \"@parcel/transformer-react-refresh-wrap\": \"2.9.3\",\n    \"@parcel/transformer-sass\": \"2.9.3\",\n    \"@parcel/transformer-svg-react\": \"2.9.3\",\n    \"@parcel/transformer-worklet\": \"2.9.3\",\n    \"@plasmohq/parcel-bundler\": \"workspace:*\",\n    \"@plasmohq/parcel-compressor-utf8\": \"workspace:*\",\n    \"@plasmohq/parcel-namer-manifest\": \"workspace:*\",\n    \"@plasmohq/parcel-optimizer-encapsulate\": \"workspace:*\",\n    \"@plasmohq/parcel-optimizer-es\": \"workspace:*\",\n    \"@plasmohq/parcel-packager\": \"workspace:*\",\n    \"@plasmohq/parcel-resolver\": \"workspace:*\",\n    \"@plasmohq/parcel-resolver-post\": \"workspace:*\",\n    \"@plasmohq/parcel-runtime\": \"workspace:*\",\n    \"@plasmohq/parcel-transformer-inject-env\": \"workspace:*\",\n    \"@plasmohq/parcel-transformer-inline-css\": \"workspace:*\",\n    \"@plasmohq/parcel-transformer-manifest\": \"workspace:*\",\n    \"@plasmohq/parcel-transformer-svelte\": \"workspace:*\",\n    \"@plasmohq/parcel-transformer-vue\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-core/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-core/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-core\",\n  \"version\": \"0.1.11\",\n  \"description\": \"Plasmo Parcel Core Fork\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"types\": \"src/types.ts\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup --minify --clean\",\n    \"dev\": \"tsup --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@parcel/types\": \"2.8.3\",\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/cache\": \"2.9.3\",\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/events\": \"2.9.3\",\n    \"@parcel/fs\": \"2.9.3\",\n    \"@parcel/graph\": \"2.9.3\",\n    \"@parcel/hash\": \"2.9.3\",\n    \"@parcel/logger\": \"2.9.3\",\n    \"@parcel/package-manager\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"@parcel/watcher\": \"2.5.1\",\n    \"@parcel/workers\": \"2.9.3\",\n    \"abortcontroller-polyfill\": \"1.7.8\",\n    \"nullthrows\": \"1.1.1\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-core/src/index.ts",
    "content": "/**\n * Forked from https://github.com/parcel-bundler/parcel/blob/19fe7ff00f28f44300fe803c4e594b9fc02b25ad/packages/core/core/src/Parcel.js\n * MIT License\n */\n\nimport invariant from \"assert\"\nimport { createWorkerFarm } from \"@parcel/core\"\nimport dumpGraphToGraphViz from \"@parcel/core/lib/dumpGraphToGraphViz\"\nimport ParcelConfig from \"@parcel/core/lib/ParcelConfig\"\nimport { toProjectPath } from \"@parcel/core/lib/projectPath\"\nimport { assetFromValue } from \"@parcel/core/lib/public/Asset\"\nimport { PackagedBundle } from \"@parcel/core/lib/public/Bundle\"\nimport BundleGraph from \"@parcel/core/lib/public/BundleGraph\"\nimport ReporterRunner from \"@parcel/core/lib/ReporterRunner\"\nimport createParcelBuildRequest from \"@parcel/core/lib/requests/ParcelBuildRequest\"\nimport { loadParcelConfig } from \"@parcel/core/lib/requests/ParcelConfigRequest\"\nimport createValidationRequest from \"@parcel/core/lib/requests/ValidationRequest\"\nimport RequestTracker, {\n  getWatcherOptions,\n  requestGraphEdgeTypes\n} from \"@parcel/core/lib/RequestTracker\"\nimport {\n  BuildAbortError,\n  registerCoreWithSerializer\n} from \"@parcel/core/lib/utils\"\nimport ThrowableDiagnostic, {\n  anyToDiagnostic,\n  type Diagnostic\n} from \"@parcel/diagnostic\"\nimport { Disposable, ValueEmitter } from \"@parcel/events\"\nimport { init as initHash } from \"@parcel/hash\"\nimport logger from \"@parcel/logger\"\nimport { init as initSourcemaps } from \"@parcel/source-map\"\nimport type {\n  AsyncSubscription,\n  BuildEvent,\n  BuildSuccessEvent,\n  InitialParcelOptions,\n  PackagedBundle as IPackagedBundle\n} from \"@parcel/types\"\nimport { PromiseQueue } from \"@parcel/utils\"\nimport { type Options as ParcelWatcherOptions } from \"@parcel/watcher\"\n// eslint-disable-next-line no-unused-vars\nimport { AbortController } from \"abortcontroller-polyfill/dist/cjs-ponyfill\"\nimport nullthrows from \"nullthrows\"\n\nimport { resolveOptions, type ResolvedOptions } from \"./resolve-options\"\n\nregisterCoreWithSerializer()\n\nexport const INTERNAL_TRANSFORM: symbol = Symbol(\"internal_transform\")\nexport const INTERNAL_RESOLVE: symbol = Symbol(\"internal_resolve\")\n\ntype SharedReference = number\n\nexport class Parcel {\n  #requestTracker: RequestTracker\n  #config: ParcelConfig\n  #farm\n  #initialized = false\n  #disposable: Disposable\n  #initialOptions: InitialParcelOptions\n  #reporterRunner: ReporterRunner\n  #resolvedOptions: ResolvedOptions = null\n  #optionsRef: SharedReference\n  #watchAbortController /*: AbortController*/\n  #watchQueue = new PromiseQueue<BuildEvent>({\n    maxConcurrent: 1\n  })\n\n  #watchEvents: ValueEmitter<\n    | {\n        error: Error\n        buildEvent?: void\n      }\n    | {\n        buildEvent: BuildEvent\n        error?: void\n      }\n  >\n\n  #watcherSubscription: AsyncSubscription\n  #watcherCount = 0\n  #requestedAssetIds: Set<string> = new Set()\n\n  isProfiling /*: boolean */\n\n  constructor(options: InitialParcelOptions) {\n    this.#initialOptions = options\n  }\n\n  async _init(): Promise<void> {\n    if (this.#initialized) {\n      return\n    }\n\n    await initSourcemaps\n    await initHash\n\n    let resolvedOptions = await resolveOptions(this.#initialOptions)\n    this.#resolvedOptions = resolvedOptions\n    let { config } = await loadParcelConfig(resolvedOptions)\n    this.#config = new ParcelConfig(config, resolvedOptions)\n\n    if (this.#initialOptions.workerFarm) {\n      if (this.#initialOptions.workerFarm[\"ending\"]) {\n        throw new Error(\"Supplied WorkerFarm is ending\")\n      }\n      this.#farm = this.#initialOptions.workerFarm\n    } else {\n      this.#farm = createWorkerFarm({\n        shouldPatchConsole: resolvedOptions.shouldPatchConsole\n      })\n    }\n\n    // @ts-ignore QUIRK: upstream def is outdated:\n    await resolvedOptions.cache.ensure()\n\n    let { dispose: disposeOptions, ref: optionsRef } =\n      await this.#farm.createSharedReference(resolvedOptions, false)\n    this.#optionsRef = optionsRef\n\n    this.#disposable = new Disposable()\n    if (this.#initialOptions.workerFarm) {\n      // If we don't own the farm, dispose of only these references when\n      // Parcel ends.\n      this.#disposable.add(disposeOptions)\n    } else {\n      // Otherwise, when shutting down, end the entire farm we created.\n      this.#disposable.add(() => this.#farm.end())\n    }\n\n    this.#watchEvents = new ValueEmitter()\n    this.#disposable.add(() => this.#watchEvents.dispose())\n\n    this.#requestTracker = await RequestTracker.init({\n      farm: this.#farm,\n      options: resolvedOptions\n    })\n\n    this.#reporterRunner = new ReporterRunner({\n      config: this.#config,\n      options: resolvedOptions,\n      workerFarm: this.#farm\n    })\n    this.#disposable.add(this.#reporterRunner)\n\n    this.#initialized = true\n  }\n\n  async run(): Promise<BuildSuccessEvent> {\n    let startTime = Date.now()\n    if (!this.#initialized) {\n      await this._init()\n    }\n\n    let result = await this._build({ startTime })\n    await this._end()\n\n    if (result.type === \"buildFailure\") {\n      throw new BuildError(result.diagnostics)\n    }\n\n    return result as BuildSuccessEvent\n  }\n\n  async _end(): Promise<void> {\n    this.#initialized = false\n\n    await Promise.all([\n      this.#disposable.dispose(),\n      await this.#requestTracker.writeToCache()\n    ])\n  }\n\n  async _startNextBuild() {\n    this.#watchAbortController = new AbortController()\n    await this.#farm.callAllWorkers(\"clearConfigCache\", [])\n\n    try {\n      let buildEvent = await this._build({\n        signal: this.#watchAbortController.signal\n      })\n\n      this.#watchEvents.emit({\n        buildEvent\n      })\n\n      return buildEvent\n    } catch (err) {\n      // Ignore BuildAbortErrors and only emit critical errors.\n      if (!(err instanceof BuildAbortError)) {\n        throw err\n      }\n    }\n  }\n\n  async watch(\n    cb?: (err: Error, buildEvent?: BuildEvent) => any\n  ): Promise<AsyncSubscription> {\n    if (!this.#initialized) {\n      await this._init()\n    }\n\n    let watchEventsDisposable\n    if (cb) {\n      watchEventsDisposable = this.#watchEvents.addListener(\n        ({ error, buildEvent }) => cb(error, buildEvent)\n      )\n    }\n\n    if (this.#watcherCount === 0) {\n      this.#watcherSubscription = await this._getWatcherSubscription()\n      await this.#reporterRunner.report({ type: \"watchStart\" })\n\n      // Kick off a first build, but don't await its results. Its results will\n      // be provided to the callback.\n      this.#watchQueue.add(() => this._startNextBuild())\n      this.#watchQueue.run()\n    }\n\n    this.#watcherCount++\n\n    let unsubscribePromise\n    const unsubscribe = async () => {\n      if (watchEventsDisposable) {\n        watchEventsDisposable.dispose()\n      }\n\n      this.#watcherCount--\n      if (this.#watcherCount === 0) {\n        await nullthrows(this.#watcherSubscription).unsubscribe()\n        this.#watcherSubscription = null\n        await this.#reporterRunner.report({ type: \"watchEnd\" })\n        this.#watchAbortController.abort()\n        await this.#watchQueue.run()\n        await this._end()\n      }\n    }\n\n    return {\n      unsubscribe() {\n        if (unsubscribePromise == null) {\n          unsubscribePromise = unsubscribe()\n        }\n\n        return unsubscribePromise\n      }\n    }\n  }\n\n  async _build({\n    signal = null,\n    startTime = Date.now()\n  } = {}): Promise<BuildEvent> {\n    this.#requestTracker.setSignal(signal)\n    let options = nullthrows(this.#resolvedOptions)\n    try {\n      if (options.shouldProfile) {\n        await this.startProfiling()\n      }\n\n      this.#watchEvents.emit({\n        buildEvent: { type: \"buildStart\" }\n      })\n\n      this.#reporterRunner.report({\n        type: \"buildStart\"\n      })\n\n      this.#requestTracker.graph.invalidateOnBuildNodes()\n\n      let request = createParcelBuildRequest({\n        optionsRef: this.#optionsRef,\n        requestedAssetIds: this.#requestedAssetIds,\n        signal\n      })\n\n      let { bundleGraph, bundleInfo, changedAssets, assetRequests } =\n        await this.#requestTracker.runRequest(request, { force: true })\n\n      this.#requestedAssetIds.clear()\n\n      await dumpGraphToGraphViz(\n        // $FlowFixMe\n        this.#requestTracker.graph,\n        \"RequestGraph\",\n        requestGraphEdgeTypes\n      )\n\n      let event = {\n        type: \"buildSuccess\" as const,\n        changedAssets: new Map(\n          Array.from(changedAssets).map(([id, asset]) => [\n            id,\n            assetFromValue(asset, options)\n          ])\n        ),\n        bundleGraph: new BundleGraph<IPackagedBundle>(\n          bundleGraph,\n          (bundle, bundleGraph, options) =>\n            PackagedBundle.getWithInfo(\n              bundle,\n              bundleGraph,\n              options,\n              bundleInfo.get(bundle.id)\n            ),\n          options\n        ),\n        buildTime: Date.now() - startTime,\n        requestBundle: async (bundle) => {\n          let bundleNode = bundleGraph._graph.getNodeByContentKey(bundle.id)\n          invariant(bundleNode?.type === \"bundle\", \"Bundle does not exist\")\n\n          if (!bundleNode.value.isPlaceholder) {\n            // Nothing to do.\n            return {\n              type: \"buildSuccess\",\n              changedAssets: new Map(),\n              bundleGraph: event.bundleGraph,\n              buildTime: 0,\n              requestBundle: event.requestBundle\n            }\n          }\n\n          for (let assetId of bundleNode.value.entryAssetIds) {\n            this.#requestedAssetIds.add(assetId)\n          }\n\n          if (this.#watchQueue.getNumWaiting() === 0) {\n            if (this.#watchAbortController) {\n              this.#watchAbortController.abort()\n            }\n\n            this.#watchQueue.add(() => this._startNextBuild())\n          }\n\n          let results = await this.#watchQueue.run()\n          let result = results.filter(Boolean).pop()\n          if (result.type === \"buildFailure\") {\n            throw new BuildError(result.diagnostics)\n          }\n\n          return result\n        }\n      }\n\n      await this.#reporterRunner.report(event)\n      await this.#requestTracker.runRequest(\n        createValidationRequest({\n          optionsRef: this.#optionsRef,\n          assetRequests\n        }),\n        { force: assetRequests.length > 0 }\n      )\n      return event\n    } catch (e) {\n      if (e instanceof BuildAbortError) {\n        throw e\n      }\n\n      let diagnostic = anyToDiagnostic(e)\n      let event = {\n        type: \"buildFailure\" as const,\n        diagnostics: Array.isArray(diagnostic) ? diagnostic : [diagnostic]\n      }\n\n      await this.#reporterRunner.report(event)\n\n      return event\n    } finally {\n      if (this.isProfiling) {\n        await this.stopProfiling()\n      }\n\n      await this.#farm.callAllWorkers(\"clearConfigCache\", [])\n    }\n  }\n\n  async _getWatcherSubscription(): Promise<AsyncSubscription> {\n    invariant(this.#watcherSubscription == null)\n\n    // TODO: This is where the resolvedOptions - the watch project root, need to be fixed\n\n    let resolvedOptions = nullthrows(this.#resolvedOptions)\n    let opts: ParcelWatcherOptions = getWatcherOptions(resolvedOptions)\n\n    opts.ignore.push(process.env.PLASMO_BUILD_DIR)\n\n    let sub = await resolvedOptions.inputFS.watch(\n      resolvedOptions.projectRoot,\n      (err, events) => {\n        if (err) {\n          this.#watchEvents.emit({ error: err })\n          return\n        }\n\n        let isInvalid = this.#requestTracker.respondToFSEvents(\n          events.map((e) => ({\n            type: e.type,\n            path: toProjectPath(resolvedOptions.projectRoot, e.path)\n          }))\n        )\n        if (isInvalid && this.#watchQueue.getNumWaiting() === 0) {\n          if (this.#watchAbortController) {\n            this.#watchAbortController.abort()\n          }\n\n          this.#watchQueue.add(() => this._startNextBuild())\n          this.#watchQueue.run()\n        }\n      },\n      opts\n    )\n    return { unsubscribe: () => sub.unsubscribe() }\n  }\n\n  async startProfiling(): Promise<void> {\n    if (this.isProfiling) {\n      throw new Error(\"Parcel is already profiling\")\n    }\n\n    logger.info({ origin: \"@parcel/core\", message: \"Starting profiling...\" })\n    this.isProfiling = true\n    await this.#farm.startProfile()\n  }\n\n  stopProfiling(): Promise<void> {\n    if (!this.isProfiling) {\n      throw new Error(\"Parcel is not profiling\")\n    }\n\n    logger.info({ origin: \"@parcel/core\", message: \"Stopping profiling...\" })\n    this.isProfiling = false\n    return this.#farm.endProfile()\n  }\n\n  takeHeapSnapshot(): Promise<void> {\n    logger.info({ origin: \"@parcel/core\", message: \"Taking heap snapshot...\" })\n    return this.#farm.takeHeapSnapshot()\n  }\n}\n\nclass BuildError extends ThrowableDiagnostic {\n  constructor(diagnostic: Array<Diagnostic> | Diagnostic) {\n    super({ diagnostic })\n    this.name = \"BuildError\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-core/src/resolve-options.ts",
    "content": "/**\n * Based on https://github.com/parcel-bundler/parcel/blob/v2/packages/core/core/src/resolveOptions.js\n * MIT License\n */\n\nimport path from \"path\"\nimport { FSCache, LMDBCache } from \"@parcel/cache\"\nimport { toProjectPath } from \"@parcel/core/lib/projectPath\"\nimport { getResolveFrom } from \"@parcel/core/lib/requests/ParcelConfigRequest\"\nimport { NodeFS, type FileSystem } from \"@parcel/fs\"\nimport { hashString } from \"@parcel/hash\"\nimport { NodePackageManager } from \"@parcel/package-manager\"\nimport type {\n  DependencySpecifier,\n  FilePath,\n  InitialParcelOptions,\n  InitialServerOptions\n} from \"@parcel/types\"\nimport { getRootDir, isGlob, relativePath, resolveConfig } from \"@parcel/utils\"\n\n// Default cache directory name\nconst LOCK_FILE_NAMES = [\"yarn.lock\", \"package-lock.json\", \"pnpm-lock.yaml\"]\n\n// Generate a unique instanceId, will change on every run of parcel\nfunction generateInstanceId(entries: Array<FilePath>): string {\n  return hashString(\n    `${entries.join(\",\")}-${Date.now()}-${Math.round(Math.random() * 100)}`\n  )\n}\n\nexport async function resolveOptions(initialOptions: InitialParcelOptions) {\n  let inputFS = initialOptions.inputFS || new NodeFS()\n  let outputFS = initialOptions.outputFS || new NodeFS()\n  let inputCwd = inputFS.cwd()\n  let outputCwd = outputFS.cwd()\n  let entries: Array<FilePath>\n\n  if (initialOptions.entries == null || initialOptions.entries === \"\") {\n    entries = []\n  } else if (Array.isArray(initialOptions.entries)) {\n    entries = initialOptions.entries.map((entry) =>\n      path.resolve(inputCwd, entry)\n    )\n  } else {\n    entries = [path.resolve(inputCwd, initialOptions.entries)]\n  }\n\n  let shouldMakeEntryReferFolder = false\n\n  if (entries.length === 1 && !isGlob(entries[0])) {\n    let [entry] = entries\n\n    try {\n      shouldMakeEntryReferFolder = (await inputFS.stat(entry)).isDirectory()\n    } catch {\n      // ignore failing stat call\n    }\n  }\n\n  // getRootDir treats the input as files, so getRootDir([\"/home/user/myproject\"]) returns \"/home/user\".\n  // Instead we need to make the entry refer to some file inside the specified folders if entries refers to the directory.\n  let entryRoot = getRootDir(\n    shouldMakeEntryReferFolder ? [path.join(entries[0], \"index\")] : entries\n  )\n  let projectRootFile =\n    (await resolveConfig(\n      inputFS,\n      path.join(entryRoot, \"index\"),\n      [...LOCK_FILE_NAMES],\n      path.parse(entryRoot).root\n    )) || path.join(inputCwd, \"index\")\n\n  // ? Should this just be rootDir\n  let projectRoot = path.dirname(projectRootFile)\n  let packageManager =\n    initialOptions.packageManager ||\n    new NodePackageManager(inputFS, projectRoot)\n\n  let cacheDir = path.resolve(outputCwd, initialOptions.cacheDir)\n\n  let cache =\n    initialOptions.cache ??\n    (outputFS instanceof NodeFS\n      ? new LMDBCache(cacheDir)\n      : // @ts-ignore QUIRK: upstream def is outdated\n        new FSCache(outputFS, cacheDir))\n\n  let mode = initialOptions.mode ?? \"development\"\n  let shouldOptimize =\n    initialOptions?.defaultTargetOptions?.shouldOptimize ??\n    mode === \"production\"\n  let publicUrl = initialOptions?.defaultTargetOptions?.publicUrl ?? \"/\"\n  let distDir =\n    initialOptions?.defaultTargetOptions?.distDir != null\n      ? path.resolve(inputCwd, initialOptions?.defaultTargetOptions?.distDir)\n      : undefined\n  let shouldBuildLazily = initialOptions.shouldBuildLazily ?? false\n  let shouldContentHash =\n    initialOptions.shouldContentHash ?? initialOptions.mode === \"production\"\n\n  if (shouldBuildLazily && shouldContentHash) {\n    throw new Error(\"Lazy bundling does not work with content hashing\")\n  }\n\n  let env = initialOptions.env\n  let port = determinePort(initialOptions.serveOptions, process.env.PORT)\n\n  return {\n    config: getRelativeConfigSpecifier(\n      inputFS,\n      projectRoot,\n      initialOptions.config\n    ),\n    defaultConfig: getRelativeConfigSpecifier(\n      inputFS,\n      projectRoot,\n      initialOptions.defaultConfig\n    ),\n    shouldPatchConsole: initialOptions.shouldPatchConsole ?? false,\n    env,\n    mode,\n    shouldAutoInstall: initialOptions.shouldAutoInstall ?? false,\n    hmrOptions: initialOptions.hmrOptions ?? null,\n    shouldBuildLazily,\n    shouldBundleIncrementally: initialOptions.shouldBundleIncrementally ?? true,\n    shouldContentHash,\n    serveOptions: initialOptions.serveOptions\n      ? {\n          ...initialOptions.serveOptions,\n          distDir: distDir ?? path.join(outputCwd, \"dist\"),\n          port\n        }\n      : false,\n    shouldDisableCache: initialOptions.shouldDisableCache ?? false,\n    shouldProfile: initialOptions.shouldProfile ?? false,\n    cacheDir,\n    entries: entries.map((e) => toProjectPath(projectRoot, e)),\n    targets: initialOptions.targets,\n    logLevel: initialOptions.logLevel ?? \"info\",\n    projectRoot,\n    inputFS,\n    outputFS,\n    cache,\n    packageManager,\n    additionalReporters:\n      initialOptions.additionalReporters?.map(\n        ({ packageName, resolveFrom }) => ({\n          packageName,\n          resolveFrom: toProjectPath(projectRoot, resolveFrom)\n        })\n      ) ?? [],\n    instanceId: generateInstanceId(entries),\n    detailedReport: initialOptions.detailedReport,\n    defaultTargetOptions: {\n      shouldOptimize,\n      shouldScopeHoist: initialOptions?.defaultTargetOptions?.shouldScopeHoist,\n      sourceMaps: initialOptions?.defaultTargetOptions?.sourceMaps ?? true,\n      publicUrl,\n      ...(distDir != null\n        ? {\n            distDir: toProjectPath(projectRoot, distDir)\n          }\n        : {\n            /*::...null*/\n          }),\n      engines: initialOptions?.defaultTargetOptions?.engines,\n      outputFormat: initialOptions?.defaultTargetOptions?.outputFormat,\n      isLibrary: initialOptions?.defaultTargetOptions?.isLibrary\n    }\n  }\n}\n\nexport type ResolvedOptions = Awaited<ReturnType<typeof resolveOptions>>\n\nfunction getRelativeConfigSpecifier(\n  fs: FileSystem,\n  projectRoot: FilePath,\n  specifier: DependencySpecifier | null | undefined\n) {\n  if (specifier == null) {\n    return undefined\n  } else if (path.isAbsolute(specifier)) {\n    let resolveFrom = getResolveFrom(fs, projectRoot)\n    let relative = relativePath(path.dirname(resolveFrom), specifier)\n    // If the config is outside the project root, use an absolute path so that if the project root\n    // moves the path still works. Otherwise, use a relative path so that the cache is portable.\n    return relative.startsWith(\"..\") ? specifier : relative\n  } else {\n    return specifier\n  }\n}\n\nfunction determinePort(\n  initialServerOptions: InitialServerOptions | false | void,\n  portInEnv: string | void,\n  defaultPort: number = 1234\n): number {\n  function parsePort(port: string) {\n    let parsedPort = Number(port)\n\n    // return undefined if port number defined in .env is not valid integer\n    if (!Number.isInteger(parsedPort)) {\n      return undefined\n    }\n\n    return parsedPort\n  }\n\n  if (!initialServerOptions) {\n    return typeof portInEnv !== \"undefined\"\n      ? parsePort(portInEnv) ?? defaultPort\n      : defaultPort\n  }\n\n  // if initialServerOptions.port is equal to defaultPort, then this means that port number is provided via PORT=~~~~ on cli. In this case, we should ignore port number defined in .env.\n  if (initialServerOptions.port !== defaultPort) {\n    return initialServerOptions.port\n  }\n\n  return typeof portInEnv !== \"undefined\"\n    ? parsePort(portInEnv) ?? defaultPort\n    : defaultPort\n}\n"
  },
  {
    "path": "core/parcel-core/src/types.ts",
    "content": "export type { InitialParcelOptions as ParcelOptions } from \"@parcel/types\"\n\nexport { Parcel } from \"./index\"\n"
  },
  {
    "path": "core/parcel-core/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-core/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig({\n  entry: [\"src/index.ts\"]\n})\n"
  },
  {
    "path": "core/parcel-namer-manifest/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-namer-manifest/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-namer-manifest\",\n  \"version\": \"0.3.12\",\n  \"description\": \"Plasmo Parcel Namer for Extension Manifest\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-namer-manifest/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n */\nimport { Namer } from \"@parcel/plugin\"\n\nexport default new Namer({\n  name({ bundle }) {\n    const mainEntry = bundle.getMainEntry()\n\n    if (!mainEntry) {\n      return null\n    }\n\n    if (\n      bundle.type === \"json\" &&\n      mainEntry.filePath.endsWith(\".plasmo.manifest.json\") &&\n      mainEntry.meta?.webextEntry\n    ) {\n      return \"manifest.json\"\n    }\n\n    if (typeof mainEntry.meta?.bundlePath === \"string\") {\n      return mainEntry.meta.bundlePath\n    }\n\n    return null\n  }\n})\n"
  },
  {
    "path": "core/parcel-namer-manifest/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-optimizer-encapsulate/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-optimizer-encapsulate/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-optimizer-encapsulate\",\n  \"version\": \"0.0.8\",\n  \"description\": \"Plasmo ECMAScript Encapsulation for Extension\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup --minify --clean\",\n    \"dev\": \"tsup --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.8.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/types\": \"2.9.3\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-optimizer-encapsulate/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n */\n\nimport { Optimizer } from \"@parcel/plugin\"\nimport SourceMap from \"@parcel/source-map\"\nimport type { PluginOptions } from \"@parcel/types\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nconst encapsulateGlobal = (name: string) =>\n  `var __${name}; typeof ${name} === \"function\" && (__${name}=${name},${name}=null);`\n\nconst problematicGlobals = [\"define\"]\n\nconst beforeContent = `(function(${problematicGlobals.join(\n  \",\"\n)}){${problematicGlobals.map(encapsulateGlobal).join(\"\")}`\n\nconst afterContent = ` ${problematicGlobals\n  .map((n) => `globalThis.${n}=__${n};`)\n  .join(\"\")}  })(${problematicGlobals\n  .map((n) => `globalThis.${n}`)\n  .join(\",\")});`\n\nfunction getSourceMap(options: PluginOptions, map: SourceMap) {\n  if (process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS !== \"none\") {\n    const newMap = new SourceMap(options.projectRoot)\n    const mapBuffer = map.toBuffer()\n    const lineOffset = 1\n    newMap.addBuffer(mapBuffer, lineOffset)\n    return newMap\n  } else {\n    return null\n  }\n}\n\nexport default new Optimizer({\n  async optimize({ bundle, contents, map, options }) {\n    vLog(\n      \"@plasmohq/parcel-optimizer-encapsulate\",\n      bundle.name,\n      bundle.displayName\n    )\n\n    return {\n      contents: `${beforeContent}\\n${contents}${afterContent}`,\n      map: getSourceMap(options, map)\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-optimizer-encapsulate/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-optimizer-encapsulate/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig({\n  entry: [\"src/index.ts\"]\n})\n"
  },
  {
    "path": "core/parcel-optimizer-es/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-optimizer-es/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-optimizer-es\",\n  \"version\": \"0.4.2\",\n  \"description\": \"Plasmo ECMAScript Optimizer for Extension\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup --minify --clean\",\n    \"dev\": \"tsup --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.8.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"@swc/core\": \"1.11.13\",\n    \"nullthrows\": \"1.1.1\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-optimizer-es/src/blob-to-string.ts",
    "content": "import { Buffer } from \"buffer\"\nimport { Readable } from \"stream\"\n\ntype Blob = Buffer | Readable | string\n\nexport function bufferStream(stream: Readable): Promise<Buffer> {\n  return new Promise((resolve, reject) => {\n    let buf = Buffer.from([])\n    stream.on(\"data\", (data) => {\n      buf = Buffer.concat([buf, data])\n    })\n    stream.on(\"end\", () => {\n      resolve(buf)\n    })\n    stream.on(\"error\", reject)\n  })\n}\n\nexport async function blobToString(blob: Blob): Promise<string> {\n  if (typeof blob === \"string\") {\n    return blob\n  } else if (blob instanceof Readable) {\n    return (await bufferStream(blob)).toString()\n  } else if (blob instanceof Buffer) {\n    return blob.toString()\n  } else {\n    return blob\n  }\n}\n"
  },
  {
    "path": "core/parcel-optimizer-es/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/blob/723e8443757cfb12a1a3af8180f0d923e666e1aa/packages/optimizers/esbuild/src/ESBuildOptimizer.js\n * MIT License\n */\n\nimport { Optimizer } from \"@parcel/plugin\"\nimport SourceMap from \"@parcel/source-map\"\nimport { transform as swcTransform } from \"@swc/core\"\nimport nullthrows from \"nullthrows\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { blobToString } from \"./blob-to-string\"\n\nexport default new Optimizer({\n  async optimize({\n    contents,\n    map: originalMap,\n    bundle,\n    options,\n    getSourceMapReference\n  }) {\n    vLog(\n      \"@plasmohq/optimizer-es: \",\n      bundle.name,\n      bundle.displayName,\n      options.projectRoot\n    )\n\n    const code = await blobToString(contents)\n\n    if (!bundle.env.shouldOptimize) {\n      vLog(`optimizer-es: skipped`)\n      return {\n        contents: code,\n        map: originalMap\n      }\n    }\n\n    const sourceMapType = process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS\n    const shouldMinify =\n      process.env.__PLASMO_FRAMEWORK_INTERNAL_NO_MINIFY === \"false\"\n\n    vLog(`optimizer-es: use SWC for ${bundle.displayName}`)\n\n    const swcOutput = await swcTransform(code, {\n      jsc: {\n        target: process.env.__PLASMO_FRAMEWORK_INTERNAL_ES_TARGET,\n        minify: {\n          format: {\n            comments: shouldMinify ? \"some\" : \"all\"\n          },\n          mangle: shouldMinify,\n          compress: shouldMinify,\n          sourceMap: sourceMapType !== \"none\",\n          toplevel:\n            bundle.env.outputFormat === \"esmodule\" ||\n            bundle.env.outputFormat === \"commonjs\",\n          module: bundle.env.outputFormat === \"esmodule\"\n        }\n      },\n      minify: shouldMinify,\n      sourceMaps:\n        sourceMapType === \"inline\" ? \"inline\" : sourceMapType === \"external\",\n      configFile: false,\n      swcrc: false\n    })\n\n    let sourceMap = null\n    let minifiedContents = nullthrows(swcOutput.code)\n    if (swcOutput.map) {\n      sourceMap = new SourceMap(options.projectRoot)\n      sourceMap.addVLQMap(JSON.parse(swcOutput.map))\n      if (originalMap) {\n        sourceMap.extends(originalMap)\n      }\n      let sourcemapReference = await getSourceMapReference(sourceMap)\n      if (sourcemapReference) {\n        minifiedContents += `\\n//# sourceMappingURL=${sourcemapReference}\\n`\n      }\n    }\n\n    return {\n      contents: minifiedContents,\n      map: sourceMap\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-optimizer-es/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-optimizer-es/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig({\n  entry: [\"src/index.ts\"]\n})\n"
  },
  {
    "path": "core/parcel-packager/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-packager/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-packager\",\n  \"version\": \"0.6.15\",\n  \"description\": \"Plasmo Parcel Packager for Web Extension Manifest\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/constants\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"nullthrows\": \"1.1.1\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-packager/src/get-web-accessible-resources.ts",
    "content": "import type {\n  BundleGraph,\n  Dependency,\n  NamedBundle,\n  PluginOptions\n} from \"@parcel/types\"\nimport nullthrows from \"nullthrows\"\n\nimport type {\n  ExtensionManifest,\n  ExtensionManifestV2,\n  ExtensionManifestV3\n} from \"@plasmo/constants\"\n\nimport { getRelativePath } from \"./utils\"\n\ntype Mv3Wars = ExtensionManifestV3[\"web_accessible_resources\"]\n\nexport const getWarsFromContentScripts = (\n  bundle: NamedBundle,\n  bundleGraph: BundleGraph<NamedBundle>,\n  dependencies: readonly Dependency[],\n  contentScripts: ExtensionManifest[\"content_scripts\"] = []\n): Mv3Wars =>\n  contentScripts\n    .map((contentScript) => {\n      const srcBundles = dependencies\n        .filter(\n          (d) =>\n            contentScript.js?.includes(d.id) ||\n            contentScript.css?.includes(d.id)\n        )\n        .map((d) => nullthrows(bundleGraph.getReferencedBundle(d, bundle)))\n\n      contentScript.css = [\n        ...new Set(\n          (contentScript.css || []).concat(\n            srcBundles\n              .flatMap((b) => bundleGraph.getReferencedBundles(b))\n              .filter((b) => b.type == \"css\")\n              .map((b) => getRelativePath(bundle, b))\n          )\n        )\n      ]\n\n      const resources = srcBundles\n        .flatMap((b) => {\n          const children: NamedBundle[] = []\n          const siblings = bundleGraph.getReferencedBundles(b)\n          bundleGraph.traverseBundles((child) => {\n            if (b !== child && !siblings.includes(child)) {\n              children.push(child)\n            }\n          }, b)\n          return children\n        })\n        .map((b) => getRelativePath(bundle, b))\n\n      return resources.length > 0\n        ? {\n            matches: contentScript.matches.map((match) => {\n              if (/^(((http|ws)s?)|ftp|\\*):\\/\\//.test(match)) {\n                let pathIndex = match.indexOf(\"/\", match.indexOf(\"://\") + 3)\n                // Avoids creating additional errors in invalid match URLs\n                if (pathIndex === -1) {\n                  pathIndex = match.length\n                }\n                return match.slice(0, pathIndex) + \"/*\"\n              }\n\n              return match\n            }),\n            resources\n          }\n        : null\n    })\n    .filter(Boolean)\n\nexport function appendMv2Wars(\n  manifest: ExtensionManifestV2,\n  wars: Mv3Wars,\n  _options: PluginOptions\n) {\n  const warResult = (manifest.web_accessible_resources || []).concat([\n    ...new Set(wars.flatMap((entry) => entry.resources))\n  ])\n\n  if (warResult.length > 0) {\n    manifest.web_accessible_resources = warResult\n  }\n}\n\nexport function appendMv3Wars(\n  manifest: ExtensionManifestV3,\n  wars: Mv3Wars,\n  options: PluginOptions\n) {\n  if (options.hmrOptions) {\n    wars.push({\n      matches: [\"<all_urls>\"],\n      resources: [\"__plasmo_hmr_proxy__\"]\n    })\n  }\n\n  if (wars.length > 0) {\n    manifest.web_accessible_resources = (\n      manifest.web_accessible_resources || []\n    ).concat(wars)\n  }\n}\n"
  },
  {
    "path": "core/parcel-packager/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/tree/v2/packages/packagers/webextension\n * MIT License\n */\nimport assert from \"assert\"\nimport { Packager } from \"@parcel/plugin\"\nimport type { Asset } from \"@parcel/types\"\nimport { replaceURLReferences } from \"@parcel/utils\"\n\nimport type { ExtensionManifest } from \"@plasmo/constants\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport {\n  appendMv2Wars,\n  appendMv3Wars,\n  getWarsFromContentScripts\n} from \"./get-web-accessible-resources\"\n\nexport default new Packager({\n  async package({ bundle, bundleGraph, options }) {\n    vLog(\"@plasmohq/parcel-packager\")\n    const assets: Asset[] = []\n    bundle.traverseAssets((asset) => {\n      assets.push(asset)\n    })\n\n    const manifestEntryAssets = assets.filter(\n      (a) => a.meta.webextEntry === true\n    )\n    assert(\n      assets.length === 2 && manifestEntryAssets.length === 1,\n      \"Web extension bundles must contain exactly one manifest asset and one runtime asset\"\n    )\n    const [manifestAsset] = manifestEntryAssets\n\n    const manifest: ExtensionManifest = JSON.parse(\n      await manifestAsset.getCode()\n    )\n\n    const dependencies = manifestAsset.getDependencies()\n\n    const wars = getWarsFromContentScripts(\n      bundle,\n      bundleGraph,\n      dependencies,\n      manifest.content_scripts\n    )\n\n    if (manifest.manifest_version === 2) {\n      appendMv2Wars(manifest, wars, options)\n    } else {\n      appendMv3Wars(manifest, wars, options)\n    }\n\n    const { contents } = replaceURLReferences({\n      bundle,\n      bundleGraph,\n      contents: JSON.stringify(manifest)\n    })\n\n    return {\n      contents\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-packager/src/utils.ts",
    "content": "import type { NamedBundle } from \"@parcel/types\"\nimport { relativeBundlePath } from \"@parcel/utils\"\n\nexport function getRelativePath(from: NamedBundle, to: NamedBundle): string {\n  return relativeBundlePath(from, to, {\n    leadingDotSlash: false\n  })\n}\n"
  },
  {
    "path": "core/parcel-packager/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-resolver/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-resolver/index.mjs",
    "content": "import { build } from \"esbuild\"\nimport glob from \"fast-glob\"\n\nconst commonConfig = {\n  bundle: true,\n  minify: true,\n\n  platform: \"browser\",\n  format: \"cjs\",\n  target: [\"chrome74\", \"safari11\"],\n  outdir: \"dist/polyfills\"\n}\n\nasync function buildProdPolyfills() {\n  const prodPolyfills = await glob(\"./src/polyfills/**/*.ts\", {\n    onlyFiles: true\n  })\n\n  await build({\n    ...commonConfig,\n    entryPoints: prodPolyfills,\n    alias: {\n      stream: \"stream-browserify\",\n      http: \"stream-http\"\n    }\n  })\n}\n\nasync function buildDevPolyfills() {\n  const devPolyfills = await glob(\"./src/dev-polyfills/**/*.ts\", {\n    onlyFiles: true\n  })\n\n  await build({\n    ...commonConfig,\n    entryPoints: devPolyfills,\n    define: {\n      \"process.env.NODE_ENV\": \"'development'\"\n    }\n  })\n}\n\nasync function main() {\n  await Promise.all([buildProdPolyfills(), buildDevPolyfills()])\n}\n\nmain()\n"
  },
  {
    "path": "core/parcel-resolver/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-resolver\",\n  \"version\": \"0.14.2\",\n  \"description\": \"Plasmo Parcel Resolver\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"dev\": \"run-s build:polyfills dev:resolver\",\n    \"dev:resolver\": \"tsup src/index.ts --watch\",\n    \"build\": \"run-s build:*\",\n    \"build:resolver\": \"tsup src/index.ts --minify --clean\",\n    \"build:polyfills\": \"node index.mjs\",\n    \"prepublishOnly\": \"pnpm build\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"@plasmohq/rps\": \"workspace:*\",\n    \"assert\": \"2.1.0\",\n    \"browserify-zlib\": \"0.2.0\",\n    \"buffer\": \"6.0.3\",\n    \"console-browserify\": \"1.2.0\",\n    \"constants-browserify\": \"1.0.0\",\n    \"crc-32\": \"1.2.2\",\n    \"crypto-browserify\": \"3.12.1\",\n    \"domain-browser\": \"5.7.0\",\n    \"esbuild\": \"0.25.1\",\n    \"events\": \"3.3.0\",\n    \"https-browserify\": \"1.0.0\",\n    \"os-browserify\": \"0.3.0\",\n    \"path-browserify\": \"1.0.1\",\n    \"process\": \"0.11.10\",\n    \"punycode\": \"2.3.1\",\n    \"querystring-es3\": \"0.2.1\",\n    \"react-refresh\": \"0.16.0\",\n    \"stream-browserify\": \"3.0.0\",\n    \"stream-http\": \"3.2.0\",\n    \"string_decoder\": \"1.3.0\",\n    \"timers-browserify\": \"2.0.12\",\n    \"tsup\": \"8.4.0\",\n    \"tty-browserify\": \"0.0.1\",\n    \"url\": \"0.11.4\",\n    \"util\": \"0.12.5\",\n    \"vm-browserify\": \"1.1.2\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/hash\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"fast-glob\": \"3.3.3\",\n    \"fs-extra\": \"11.3.0\",\n    \"got\": \"14.4.6\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/dev-polyfills/react-refresh/runtime.ts",
    "content": "import refreshRuntime from \"react-refresh/runtime\"\n\nexport * from \"react-refresh/runtime\"\nexport default refreshRuntime\n"
  },
  {
    "path": "core/parcel-resolver/src/dev-polyfills/react-refresh.ts",
    "content": "import refresh from \"react-refresh\"\n\nexport * from \"react-refresh\"\nexport default refresh\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-absolute-root.ts",
    "content": "import { extname, isAbsolute, join, resolve } from \"path\"\nimport { pathExists, pathExistsSync } from \"fs-extra\"\n\nimport {\n  relevantExtensionList,\n  resolveSourceIndex,\n  type ResolverProps,\n  type ResolverResult\n} from \"./shared\"\n\nexport async function handleAbsoluteRoot({\n  specifier,\n  dependency\n}: ResolverProps): Promise<ResolverResult> {\n  if (specifier[0] !== \"/\") {\n    return null\n  }\n\n  if (pathExistsSync(specifier)) {\n    return {\n      filePath: specifier\n    }\n  }\n\n  const absoluteBaseFile = resolve(\n    join(process.env.PLASMO_PROJECT_DIR, specifier.slice(1))\n  )\n\n  const importExt = extname(absoluteBaseFile)\n\n  if (importExt.length > 0 && pathExistsSync(absoluteBaseFile)) {\n    return {\n      filePath: absoluteBaseFile\n    }\n  }\n\n  const parentExt = extname(dependency.resolveFrom)\n\n  const checkingExts = [\n    parentExt,\n    ...relevantExtensionList.filter((ext) => ext !== parentExt)\n  ]\n\n  return resolveSourceIndex(absoluteBaseFile, checkingExts)\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-alias.ts",
    "content": "import { resolve } from \"path\"\n\nimport { isReadable } from \"@plasmo/utils/fs\"\n\nimport { state, type ResolverProps, type ResolverResult } from \"./shared\"\n\nexport async function handleAlias({\n  specifier\n}: ResolverProps): Promise<ResolverResult> {\n  if (!state.aliasMap.has(specifier)) {\n    return null\n  }\n\n  const absPath = resolve(state.aliasMap.get(specifier))\n\n  const hasLocalAlias = await isReadable(absPath)\n\n  if (!hasLocalAlias) {\n    return null\n  }\n\n  return {\n    filePath: absPath,\n    priority: \"sync\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-plasmo-internal.ts",
    "content": "import { resolve } from \"path\"\n\nimport {\n  resolveSourceIndex,\n  state,\n  type ResolverProps,\n  type ResolverResult\n} from \"./shared\"\n\nconst resolveByPrefix = (specifier = \"\", prefix = \"\", prefixPath = \"\") => {\n  if (!specifier.startsWith(prefix)) {\n    return null\n  }\n\n  const [_, specifierPath] = specifier.split(prefix)\n\n  return resolveSourceIndex(resolve(prefixPath, specifierPath))\n}\n\nexport async function handlePlasmoInternal({\n  specifier\n}: ResolverProps): Promise<ResolverResult> {\n  return resolveByPrefix(\n    specifier,\n    \"@plasmo-static-common/\",\n    resolve(state.dotPlasmoDirectory, \"static\", \"common\")\n  )\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-polyfill.ts",
    "content": "import { state, type ResolverProps, type ResolverResult } from \"./shared\"\n\nexport async function handlePolyfill({\n  specifier\n}: ResolverProps): Promise<ResolverResult> {\n  if (!state.polyfillMap.has(specifier)) {\n    return null\n  }\n\n  return {\n    filePath: state.polyfillMap.get(specifier),\n    priority: \"sync\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-remote-caching.ts",
    "content": "import { resolve } from \"path\"\nimport { hashString } from \"@parcel/hash\"\n\nimport { injectEnv } from \"@plasmo/utils/env\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport {\n  relevantExtensionList,\n  state,\n  type ResolverProps,\n  type ResolverResult\n} from \"./shared\"\n\nconst cookCode = async (target: URL, code: string) => {\n  if (target.origin === \"https://www.googletagmanager.com\") {\n    return code.replace(/http:/g, \"chrome-extension:\")\n  } else {\n    return code\n  }\n}\n\n// TODO: Some kind of caching mechanism would be nice\nexport async function handleRemoteCaching({\n  specifier,\n  dependency,\n  options\n}: ResolverProps): Promise<ResolverResult> {\n  if (!specifier.startsWith(\"https://\")) {\n    return null\n  }\n\n  // Only these extensions parents are allowed to cache remote dependencies\n  if (\n    !relevantExtensionList.some((ext) => dependency.sourcePath.endsWith(ext))\n  ) {\n    return null\n  }\n  const target = new URL(injectEnv(specifier))\n\n  const fileType = target.searchParams.get(\"plasmo-ext\") || \"js\"\n\n  try {\n    const filePath = resolve(\n      state.remoteCacheDirectory,\n      `${hashString(specifier)}.${fileType}`\n    )\n    const code = (await state.got(target.toString()).text()) as string\n\n    const cookedCode = await cookCode(target, code)\n\n    await options.inputFS.rimraf(filePath)\n\n    await options.inputFS.writeFile(filePath, cookedCode, {\n      mode: 0o664\n    })\n\n    vLog(`Caching HTTPS dependency: ${specifier}`)\n\n    return {\n      priority: \"lazy\",\n      sideEffects: true,\n      filePath\n    }\n  } catch (error) {\n    return {\n      diagnostics: [\n        {\n          message: `Cannot download the resource from ${specifier}`,\n          hints: [error.message]\n        }\n      ]\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/handle-tilde-src.ts",
    "content": "import { extname, join, resolve } from \"path\"\n\nimport {\n  customPipelineSet,\n  relevantExtensionList,\n  relevantExtensionSet,\n  resolveSourceIndex,\n  type ResolverProps,\n  type ResolverResult\n} from \"./shared\"\n\nexport async function handleTildeSrc({\n  pipeline,\n  specifier,\n  dependency\n}: ResolverProps): Promise<ResolverResult> {\n  if (specifier[0] !== \"~\") {\n    return null\n  }\n\n  const absoluteBaseFile = resolve(\n    join(process.env.PLASMO_SRC_DIR, specifier.slice(1))\n  )\n\n  if (customPipelineSet.has(pipeline)) {\n    return {\n      filePath: absoluteBaseFile\n    }\n  }\n\n  const importExt = extname(absoluteBaseFile)\n\n  // TODO: Potentially resolve other type of files (less import etc...) that Parcel doesn't account for\n  if (importExt.length > 0 && relevantExtensionSet.has(importExt as any)) {\n    return {\n      filePath: absoluteBaseFile\n    }\n  }\n\n  const parentExt = extname(dependency.resolveFrom)\n\n  // console.log(`tildeSrc: resolveFrom: ${dependency.resolveFrom}`)\n\n  const checkingExts = [\n    parentExt,\n    ...relevantExtensionList.filter((ext) => ext !== parentExt)\n  ]\n\n  // console.log(`tildeSrc: ${potentialFiles}`)\n\n  return resolveSourceIndex(absoluteBaseFile, checkingExts)\n}\n"
  },
  {
    "path": "core/parcel-resolver/src/index.ts",
    "content": "import { Resolver } from \"@parcel/plugin\"\n\nimport { handleAbsoluteRoot } from \"./handle-absolute-root\"\nimport { handleAlias } from \"./handle-alias\"\nimport { handlePlasmoInternal } from \"./handle-plasmo-internal\"\nimport { handlePolyfill } from \"./handle-polyfill\"\nimport { handleRemoteCaching } from \"./handle-remote-caching\"\nimport { handleTildeSrc } from \"./handle-tilde-src\"\nimport { initializeState } from \"./shared\"\n\nexport default new Resolver({\n  async resolve(props) {\n    await initializeState(props)\n\n    return (\n      (await handleAlias(props)) ||\n      (await handlePlasmoInternal(props)) ||\n      (await handlePolyfill(props)) ||\n      (await handleRemoteCaching(props)) ||\n      (await handleTildeSrc(props)) ||\n      (await handleAbsoluteRoot(props)) ||\n      null\n    )\n  }\n})\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/assert.ts",
    "content": "import assert from \"assert/build/assert\"\n\nexport * from \"assert/build/assert\"\nexport default assert\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/buffer.ts",
    "content": "import buffer from \"buffer/index\"\n\nexport * from \"buffer/index\"\nexport default buffer\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/console.ts",
    "content": "import console from \"console-browserify\"\n\nexport * from \"console-browserify\"\nexport default console\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/constants.ts",
    "content": "import constants from \"constants-browserify/constants.json\"\n\nexport default constants\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/crc-32/crc32c.ts",
    "content": "import crc32c from \"crc-32/crc32c\"\n\nglobalThis.DO_NOT_EXPORT_CRC = true\n\nexport * from \"crc-32/crc32c\"\nexport default crc32c\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/crc-32.ts",
    "content": "import crc32 from \"crc-32\"\n\nglobalThis.DO_NOT_EXPORT_CRC = true\n\nexport * from \"crc-32\"\nexport default crc32\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/crypto.ts",
    "content": "import crypto from \"crypto-browserify\"\n\nexport * from \"crypto-browserify\"\nexport default crypto\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/domain.ts",
    "content": "import domain from \"domain-browser\"\n\nexport * from \"domain-browser\"\nexport default domain\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/events.ts",
    "content": "import events from \"events/events\"\n\nexport * from \"events/events\"\nexport default events\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/http.ts",
    "content": "import http from \"stream-http\"\n\nexport * from \"stream-http\"\nexport default http\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/https.ts",
    "content": "import https from \"https-browserify\"\n\nexport * from \"https-browserify\"\nexport default https\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/os.ts",
    "content": "import os from \"os-browserify/browser\"\n\nexport * from \"os-browserify/browser\"\nexport default os\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/path.ts",
    "content": "import path from \"path-browserify\"\n\nexport * from \"path-browserify\"\nexport default path\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/process.ts",
    "content": "import process from \"process/browser\"\n\nexport * from \"process/browser\"\nexport default process\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/punycode.ts",
    "content": "import punycode from \"punycode/punycode.es6\"\n\nexport * from \"punycode/punycode.es6\"\nexport default punycode\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/querystring.ts",
    "content": "import querystring from \"querystring-es3\"\n\nexport * from \"querystring-es3\"\nexport default querystring\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/stream.ts",
    "content": "import stream from \"stream-browserify\"\n\nexport * from \"stream-browserify\"\nexport default stream\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/string_decoder.ts",
    "content": "import stringDecoder from \"string_decoder/lib/string_decoder\"\n\nexport * from \"string_decoder/lib/string_decoder\"\nexport default stringDecoder\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/sys.ts",
    "content": "import sys from \"util/util\"\n\nexport * from \"util/util\"\nexport default sys\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/timers.ts",
    "content": "import timers from \"timers-browserify\"\n\nexport * from \"timers-browserify\"\nexport default timers\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/tty.ts",
    "content": "import tty from \"tty-browserify\"\n\nexport * from \"tty-browserify\"\nexport default tty\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/url.ts",
    "content": "import url from \"url/url\"\n\nexport * from \"url/url\"\nexport default url\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/util.ts",
    "content": "import util from \"util/util\"\n\nexport * from \"util/util\"\nexport default util\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/vm.ts",
    "content": "import vm from \"vm-browserify\"\n\nexport * from \"vm-browserify\"\nexport default vm\n"
  },
  {
    "path": "core/parcel-resolver/src/polyfills/zlib.ts",
    "content": "import zlib from \"browserify-zlib\"\n\nexport * from \"browserify-zlib\"\nexport default zlib\n"
  },
  {
    "path": "core/parcel-resolver/src/shared.ts",
    "content": "import { statSync } from \"fs\"\nimport { join, resolve } from \"path\"\nimport type { Resolver } from \"@parcel/plugin\"\nimport type { ResolveResult } from \"@parcel/types\"\nimport glob from \"fast-glob\"\nimport { readJson } from \"fs-extra\"\nimport type { Got } from \"got\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\nimport { toPosix } from \"@plasmo/utils/path\"\n\nexport const relevantExtensionList = [\n  \".ts\",\n  \".tsx\",\n  \".svelte\",\n  \".vue\",\n  \".json\"\n] as const\n\nexport const customPipelineSet = new Set([\n  \"data-text\",\n  \"data-base64\",\n  \"data-env\",\n  \"data-text-env\",\n  \"raw\",\n  \"raw-env\"\n])\n\nexport const relevantExtensionSet = new Set(relevantExtensionList)\n\ntype ResolveFx = ConstructorParameters<typeof Resolver>[0][\"resolve\"]\n\nexport type ResolverResult = ResolveResult\n\nexport type ResolverProps = Parameters<ResolveFx>[0]\n\nexport const state = {\n  got: null as Got,\n  dotPlasmoDirectory: null as string,\n  remoteCacheDirectory: null as string,\n  polyfillMap: null as Map<string, string>,\n  aliasMap: null as Map<string, string>\n}\n\nexport const initializeState = async (props: ResolverProps) => {\n  if (state.got === null) {\n    state.got = (await import(\"got\")).default\n  }\n  if (!state.dotPlasmoDirectory) {\n    state.dotPlasmoDirectory = resolve(\n      process.env.PLASMO_PROJECT_DIR,\n      \".plasmo\"\n    )\n  }\n  if (!state.remoteCacheDirectory) {\n    state.remoteCacheDirectory = resolve(\n      state.dotPlasmoDirectory,\n      \"cache\",\n      \"remote-code\"\n    )\n\n    if (!(await props.options.inputFS.exists(state.remoteCacheDirectory))) {\n      vLog(\"Reinitializing remote cache directory\")\n      await props.options.inputFS.mkdirp(state.remoteCacheDirectory)\n    }\n  }\n\n  if (!state.polyfillMap) {\n    const polyfillsDirectory = join(__dirname, \"polyfills\")\n\n    const polyfillHandlers = await glob(\"**/*.js\", {\n      cwd: polyfillsDirectory,\n      onlyFiles: true\n    })\n\n    state.polyfillMap = new Map(\n      polyfillHandlers.map((handler) => [\n        toPosix(handler.slice(0, -3)),\n        join(polyfillsDirectory, handler)\n      ])\n    )\n  }\n\n  if (!state.aliasMap) {\n    const packageJson = await readJson(\n      resolve(process.env.PLASMO_PROJECT_DIR, \"package.json\")\n    )\n\n    state.aliasMap = new Map(Object.entries(packageJson.alias || {}))\n  }\n}\n\n/**\n * Look for source code file (crawl index)\n */\nexport const resolveSourceIndex = async (\n  absoluteBaseFile: string,\n  checkingExts = relevantExtensionList as readonly string[],\n  opts = {} as Partial<ResolveResult>\n): Promise<ResolverResult> => {\n  const potentialFiles = checkingExts.flatMap((ext) => [\n    `${absoluteBaseFile}${ext}`,\n    resolve(absoluteBaseFile, `index${ext}`)\n  ])\n\n  for (const file of potentialFiles) {\n    try {\n      if (statSync(file).isFile()) {\n        return { filePath: file, ...opts }\n      }\n    } catch {}\n  }\n\n  return null\n}\n"
  },
  {
    "path": "core/parcel-resolver/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\", \"tsup.config.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-resolver-post/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-resolver-post\",\n  \"version\": \"0.4.6\",\n  \"description\": \"Plasmo Parcel Resolver Post-processing\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/hash\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"tsup\": \"8.4.0\",\n    \"typescript\": \"5.8.2\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/src/handle-hacks.ts",
    "content": "// These are hack resolvers to get over some module resolution issues, with a PR to fix them upstream.\n\nimport type { ResolverProps, ResolverResult } from \"./shared\"\n\n// Last resort resolver for weird packages:\nexport async function handleHacks({\n  specifier,\n  dependency\n}: ResolverProps): Promise<ResolverResult> {\n  switch (specifier) {\n    // Example of a resolved hack:\n    // TODO: remove this when we have other hacks here...\n    // case \"svelte/internal/disclose-version\": {\n    //   // https://github.com/sveltejs/svelte/pull/8874\n    //   const sveltePjPath = require.resolve(\"svelte/package.json\", {\n    //     paths: [dependency.resolveFrom]\n    //   })\n\n    //   return {\n    //     filePath: join(\n    //       dirname(sveltePjPath),\n    //       \"src\",\n    //       \"runtime\",\n    //       \"internal\",\n    //       \"disclose-version\",\n    //       \"index.js\"\n    //     )\n    //   }\n    // }\n\n    default:\n      return null\n  }\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/src/handle-module-exports.ts",
    "content": "import type { ResolverProps, ResolverResult } from \"./shared\"\n\n// Last resort resolver for weird packages:\nexport async function handleModuleExport({\n  specifier,\n  dependency\n}: ResolverProps): Promise<ResolverResult> {\n  // Ignore relative path\n  if (specifier.startsWith(\"./\") || specifier.startsWith(\"../\")) {\n    return null\n  }\n\n  try {\n    const filePath = require.resolve(specifier, {\n      paths: [dependency.resolveFrom]\n    })\n\n    return {\n      filePath\n    }\n  } catch {}\n\n  return null\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/src/handle-ts-path.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/zachbryant/parcel-resolver-tspaths\n * Copyright (c) 2021 Zach Bryant\n * MIT License\n */\n\nimport { dirname, extname, join, resolve } from \"path\"\nimport { loadConfig } from \"@parcel/utils\"\nimport type { CompilerOptions } from \"typescript\"\n\nimport { isReadable } from \"@plasmo/utils/fs\"\n\nimport type { ResolverProps, ResolverResult } from \"./shared\"\nimport { checkWebpackSpecificImportSyntax, findModule, trimStar } from \"./utils\"\n\nconst tsRegex = /\\.(tsx?)|vue|svelte$/\n\nconst relevantExtList = [\n  \".ts\",\n  \".tsx\",\n  \".svelte\",\n  \".vue\",\n  \".json\",\n\n  \".css\",\n  \".scss\",\n  \".sass\",\n  \".less\",\n\n  \".svg\",\n\n  \".js\",\n  \".jsx\"\n] as const\n\nconst relevantExtSet = new Set(relevantExtList)\n\ntype TsPaths = string[]\n\ntype TsPathsMap = Map<string, TsPaths>\n\nconst state = {\n  pathsMap: null as TsPathsMap,\n  pathsMapRegex: null as [string, TsPaths, RegExp][]\n}\n\nexport async function handleTsPath(\n  props: ResolverProps\n): Promise<ResolverResult> {\n  try {\n    const { dependency, specifier } = props\n\n    checkWebpackSpecificImportSyntax(dependency.specifier)\n\n    const isTypescript = tsRegex.test(dependency.resolveFrom)\n\n    if (!isTypescript) {\n      return null\n    }\n\n    if (specifier.startsWith(\".\")) {\n      return {\n        filePath: findModule(\n          resolve(dependency.resolveFrom, \"..\", specifier),\n          relevantExtList\n        )\n      }\n    }\n\n    const compilerOptions = await getTsconfigCompilerOptions(props)\n\n    if (compilerOptions.length === 0) {\n      return null\n    }\n\n    loadTsPathsMap(compilerOptions)\n\n    const result = attemptResolve(props)\n\n    if (!result) {\n      return null\n    }\n\n    return {\n      filePath: result\n    }\n  } catch {\n    return null\n  }\n}\n\n/** Populate a map with any paths from tsconfig.json starting from baseUrl */\nfunction loadTsPathsMap(tsConfigs: TSConfig[]) {\n  if (state.pathsMap) {\n    return\n  }\n\n  const tsPathsMap = tsConfigs.reduce(\n    (c, tsConfig) => loadPathsFromTSConfig(tsConfig, c),\n    new Map<string, TsPaths>()\n  )\n\n  state.pathsMap = tsPathsMap\n  state.pathsMapRegex = Array.from(tsPathsMap.entries()).map((entry) => [\n    ...entry,\n    new RegExp(`^${entry[0].replace(\"*\", \".*\")}$`)\n  ])\n}\n\nfunction loadPathsFromTSConfig(\n  tsConfig: TSConfig,\n  tsPathsMap: Map<string, TsPaths>\n) {\n  const { filePath, compilerOptions } = tsConfig\n\n  const baseUrl = compilerOptions.baseUrl || \".\"\n  const tsPaths = compilerOptions.paths || {}\n\n  const tsConfigFolderPath = join(dirname(join(filePath)), baseUrl)\n\n  for (const key in tsPaths) {\n    tsPathsMap.set(\n      key,\n      tsPaths[key].map((p) => join(tsConfigFolderPath, p))\n    )\n  }\n  return tsPathsMap\n}\n\nfunction attemptResolve({ specifier, dependency }: ResolverProps) {\n  const { pathsMap, pathsMapRegex } = state\n  if (pathsMap.has(specifier)) {\n    return attemptResolveArray(\n      specifier,\n      specifier,\n      pathsMap.get(specifier),\n      dependency.resolveFrom\n    )\n  }\n\n  const relevantEntry = pathsMapRegex.find(([, , aliasRegex]) =>\n    aliasRegex.test(specifier)\n  )\n\n  if (!!relevantEntry) {\n    return attemptResolveArray(\n      specifier,\n      relevantEntry[0],\n      relevantEntry[1],\n      dependency.resolveFrom\n    )\n  }\n\n  return null\n}\n\n// TODO support resource loaders like 'url:@alias/my.svg'\n/** Attempt to resolve any path associated with the alias to a file or directory index */\nfunction attemptResolveArray(\n  from: string,\n  alias: string,\n  realPaths: TsPaths,\n  parentFile: string\n) {\n  for (const option of realPaths) {\n    const absoluteBaseFile = resolve(\n      from.replace(trimStar(alias), trimStar(option))\n    )\n\n    const importExt = extname(absoluteBaseFile)\n\n    if (importExt.length > 0 && relevantExtSet.has(importExt as any)) {\n      return absoluteBaseFile\n    }\n\n    const parentExt = extname(parentFile)\n\n    const checkingExts = [\n      parentExt,\n      ...relevantExtList.filter((ext) => ext !== parentExt)\n    ]\n\n    const mod = findModule(absoluteBaseFile, checkingExts)\n\n    if (mod !== null) {\n      return mod\n    }\n  }\n  return null\n}\n\ntype TSConfig = { compilerOptions: CompilerOptions; filePath: string }\n\nasync function getTsconfigCompilerOptions(\n  props: ResolverProps & { tsconfigPath?: string },\n  tsConfigs: TSConfig[] = [],\n  depth = 0\n): Promise<TSConfig[]> {\n  if (depth > 42) {\n    throw new Error(\n      \"Something went wrong in loading tsconfig (depth > 42). Circular dependency?\"\n    )\n  }\n\n  const { options, dependency, tsconfigPath } = props\n\n  const tsconfigPathList = tsconfigPath\n    ? [tsconfigPath]\n    : [\"tsconfig.json\", \"tsconfig.js\"]\n\n  const result = await loadConfig(\n    options.inputFS,\n    dependency.resolveFrom,\n    tsconfigPathList,\n    join(process.env.PLASMO_PROJECT_DIR, \"lab\")\n  )\n\n  const compilerOptions = result?.config?.compilerOptions as CompilerOptions\n\n  if (!compilerOptions) {\n    return tsConfigs\n  }\n\n  const filePath = result.files[0].filePath\n\n  const output = { compilerOptions, filePath }\n  try {\n    if (result.config.extends) {\n      const extendsTsconfigPath = (await isReadable(\n        resolve(result.config.extends)\n      ))\n        ? resolve(result.config.extends)\n        : require.resolve(result.config.extends, {\n            paths: [dependency.resolveFrom]\n          })\n\n      return await getTsconfigCompilerOptions(\n        {\n          ...props,\n          tsconfigPath: extendsTsconfigPath\n        },\n        [output, ...tsConfigs],\n        ++depth\n      )\n    }\n  } catch {}\n\n  return [output, ...tsConfigs]\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/src/index.ts",
    "content": "import { Resolver } from \"@parcel/plugin\"\n\nimport { handleHacks } from \"./handle-hacks\"\nimport { handleModuleExport } from \"./handle-module-exports\"\nimport { handleTsPath } from \"./handle-ts-path\"\n\nexport default new Resolver({\n  async resolve(props) {\n    return (\n      (await handleHacks(props)) ||\n      (await handleTsPath(props)) ||\n      (await handleModuleExport(props)) ||\n      null\n    )\n  }\n})\n"
  },
  {
    "path": "core/parcel-resolver-post/src/shared.ts",
    "content": "import type { Resolver } from \"@parcel/plugin\"\nimport type { ResolveResult } from \"@parcel/types\"\n\nexport const relevantExtensionList = [\n  \".ts\",\n  \".tsx\",\n  \".svelte\",\n  \".vue\",\n  \".json\",\n\n  \".js\",\n  \".jsx\"\n] as const\n\nexport const relevantExtensionSet = new Set(relevantExtensionList)\n\ntype ResolveFx = ConstructorParameters<typeof Resolver>[0][\"resolve\"]\n\nexport type ResolverResult = ResolveResult\nexport type ResolverProps = Parameters<ResolveFx>[0]\n"
  },
  {
    "path": "core/parcel-resolver-post/src/utils.ts",
    "content": "import { statSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { ResolveResult } from \"@parcel/types\"\n\nimport { relevantExtensionList, type ResolverResult } from \"./shared\"\n\nconst WEBPACK_IMPORT_REGEX = /\\S+-loader\\S*!\\S+/g\n\nexport function checkWebpackSpecificImportSyntax(specifier = \"\") {\n  // Throw user friendly errors on special webpack loader syntax\n  // ex. `imports-loader?$=jquery!./example.js`\n  if (WEBPACK_IMPORT_REGEX.test(specifier)) {\n    throw new Error(\n      `The import path: ${specifier} is using webpack specific loader import syntax, which isn't supported by Parcel.`\n    )\n  }\n}\n\nexport function trimStar(str: string) {\n  return trim(str, \"*\")\n}\n\nexport function trim(str: string, trim: string) {\n  if (str.endsWith(trim)) {\n    str = str.substring(0, str.length - trim.length)\n  }\n  return str\n}\n\nconst isFile = (filePath: string) => {\n  try {\n    return statSync(filePath).isFile()\n  } catch {\n    return false\n  }\n}\n\nexport function findModule(\n  absoluteBaseFile: string,\n  checkingExts = relevantExtensionList as readonly string[]\n) {\n  return checkingExts\n    .flatMap((ext) => [\n      resolve(`${absoluteBaseFile}${ext}`),\n      resolve(absoluteBaseFile, `index${ext}`)\n    ])\n    .find(isFile)\n}\n\n/**\n * Look for source code file (crawl index)\n */\nexport const resolveSourceIndex = async (\n  absoluteBaseFile: string,\n  checkingExts = relevantExtensionList as readonly string[],\n  opts = {} as Partial<ResolveResult>\n): Promise<ResolverResult> => {\n  const filePath = findModule(absoluteBaseFile, checkingExts)\n\n  if (!filePath) {\n    return null\n  }\n\n  return { filePath, ...opts }\n}\n"
  },
  {
    "path": "core/parcel-resolver-post/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-runtime/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-runtime/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-runtime\",\n  \"version\": \"0.25.3\",\n  \"description\": \"Plasmo Parcel Runtime\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup --minify --clean\",\n    \"dev\": \"tsup --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/framework-shared\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"@plasmohq/persistent\": \"workspace:*\",\n    \"@types/chrome\": \"0.0.312\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@types/trusted-types\": \"2.0.7\",\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"react-refresh\": \"0.16.0\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/index.ts",
    "content": "import fs from \"fs\"\nimport path, { basename, dirname, join } from \"path\"\nimport { Runtime } from \"@parcel/plugin\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport {\n  plasmoRuntimeList,\n  type PlasmoRuntime,\n  type RuntimeData\n} from \"./types\"\n\nconst devRuntimeMap = plasmoRuntimeList.reduce(\n  (accumulatedRuntimeMap, currentRuntime) => ({\n    ...accumulatedRuntimeMap,\n    [currentRuntime]: fs.readFileSync(\n      path.join(__dirname, `./runtimes/${currentRuntime}.js`),\n      \"utf8\"\n    )\n  }),\n  {} as Record<PlasmoRuntime, string>\n)\n\nexport default new Runtime({\n  async loadConfig({ config }) {\n    const pkg = await config\n      .getConfigFrom<{\n        dependencies: Record<string, string>\n        devDependencies: Record<string, string>\n        peerDependencies: Record<string, string>\n      }>(\n        join(process.env.PLASMO_PROJECT_DIR, \"lab\"), // parcel only look up\n        [\"package.json\"],\n        {\n          exclude: true\n        }\n      )\n      .then((cfg) => cfg?.contents)\n\n    // npm workspaces mono repo's do not have dependencies or devDependencies (they are defined in a parent directory), fallback to peerDependencies\n    const hasReact = !!pkg?.dependencies?.react || !!pkg?.devDependencies?.react || !!pkg?.peerDependencies?.react\n\n    return {\n      hasReact\n    }\n  },\n\n  apply({ bundle, options, config, bundleGraph }) {\n    if (bundle.name === \"manifest.json\") {\n      const asset = bundle.getMainEntry()\n      if (asset?.meta.webextEntry !== true) {\n        return\n      }\n\n      // Hack to bust packager cache when any descendants update\n      const descendants = []\n      bundleGraph.traverseBundles((b) => {\n        descendants.push(b.id)\n      }, bundle)\n\n      return {\n        filePath: __filename,\n        code: JSON.stringify(descendants),\n        isEntry: true\n      }\n    }\n\n    if (\n      bundle.type !== \"js\" ||\n      !options.hmrOptions ||\n      bundle.env.isLibrary ||\n      bundle.env.isWorklet() ||\n      options.mode !== \"development\" ||\n      bundle.env.sourceType === \"script\"\n    ) {\n      return\n    }\n\n    const entryFilePath = bundle.getMainEntry()?.filePath\n\n    if (!entryFilePath) {\n      return\n    }\n\n    const isPlasmo = entryFilePath.includes(\".plasmo\")\n\n    const isBackground =\n      entryFilePath.startsWith(\n        join(process.env.PLASMO_SRC_DIR, \"background\")\n      ) ||\n      entryFilePath.endsWith(join(\"static\", \"background\", \"index.ts\")) ||\n      entryFilePath.endsWith(\"plasmo-default-background.ts\")\n\n    const isPlasmoSrc =\n      isPlasmo ||\n      isBackground ||\n      entryFilePath.startsWith(join(process.env.PLASMO_SRC_DIR, \"content\"))\n\n    if (!isPlasmoSrc) {\n      return\n    }\n\n    const isReact = config.hasReact && entryFilePath.endsWith(\".tsx\")\n\n    const entryBasename = basename(entryFilePath).split(\".\")[0]\n\n    const isContentScript =\n      dirname(entryFilePath).endsWith(\"contents\") || entryBasename === \"content\"\n\n    if (\n      process.env.__PLASMO_FRAMEWORK_INTERNAL_NO_CS_RELOAD === \"true\" &&\n      isContentScript\n    ) {\n      return\n    }\n\n    // TODO: add production runtimes\n    const devRuntime: PlasmoRuntime = isBackground\n      ? \"background-service-runtime\"\n      : isContentScript\n      ? \"script-runtime\"\n      : \"page-runtime\"\n\n    vLog(\n      \"@plasmohq/parcel-runtime\",\n      \"Injecting <<\",\n      devRuntime,\n      \">> for\",\n      bundle.displayName,\n      bundle.id,\n      entryFilePath\n    )\n\n    const runtimeData: RuntimeData = {\n      isContentScript,\n      isBackground,\n      isReact,\n\n      runtimes: [devRuntime],\n\n      ...options.hmrOptions,\n      entryFilePath: String.raw`${entryFilePath}`,\n      bundleId: bundle.id,\n      envHash: bundle.env.id,\n\n      verbose: process.env.VERBOSE,\n\n      secure: !!(options.serveOptions && options.serveOptions.https),\n      serverPort: options.serveOptions && options.serveOptions.port\n    }\n\n    const code = devRuntimeMap[devRuntime].replace(\n      `__plasmo_runtime_data__`, // double quote to escape\n      JSON.stringify(runtimeData)\n    )\n\n    return {\n      filePath: __filename,\n      code,\n      isEntry: true,\n      env: {\n        sourceType: \"module\"\n      }\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-runtime/src/runtimes/background-service-runtime.ts",
    "content": "/**\n * This runtime is injected into the background service worker\n */\n\nimport { BuildSocketEvent } from \"@plasmo/framework-shared/build-socket/event\"\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { keepAlive } from \"@plasmohq/persistent/background\"\n\nimport type { BackgroundMessage } from \"../types\"\nimport {\n  extCtx,\n  PAGE_PORT_PREFIX,\n  runtimeData,\n  SCRIPT_PORT_PREFIX\n} from \"../utils/0-patch-module\"\nimport { pollingDevServer } from \"../utils/bgsw\"\nimport { isDependencyOfBundle } from \"../utils/hmr-check\"\nimport { injectBuilderSocket, injectHmrSocket } from \"../utils/inject-socket\"\n\nconst parent = module.bundle.parent\n\nconst state = {\n  buildReady: false,\n  bgChanged: false,\n  csChanged: false,\n  pageChanged: false,\n  scriptPorts: new Set<chrome.runtime.Port>(),\n  pagePorts: new Set<chrome.runtime.Port>()\n}\n\nasync function consolidateUpdate(forced = false) {\n  if (forced || (state.buildReady && state.pageChanged)) {\n    vLog(\"BGSW Runtime - reloading Page\")\n    for (const port of state.pagePorts) {\n      // Mark the active tab for specific reload\n      port.postMessage(null)\n    }\n  }\n\n  if (forced || (state.buildReady && (state.bgChanged || state.csChanged))) {\n    vLog(\"BGSW Runtime - reloading CS\")\n    const activeTabList = await extCtx?.tabs.query({ active: true })\n\n    for (const port of state.scriptPorts) {\n      const isActive = activeTabList.some((t) => t.id === port.sender.tab?.id)\n      // Mark the active tab for specific reload\n      port.postMessage({\n        __plasmo_cs_active_tab__: isActive\n      } as BackgroundMessage)\n    }\n    // Required to actually reload the CS\n    extCtx.runtime.reload()\n  }\n}\n\nif (!parent || !parent.isParcelRequire) {\n  keepAlive()\n\n  const hmrSocket = injectHmrSocket(async (updatedAssets) => {\n    vLog(\"BGSW Runtime - On HMR Update\")\n\n    state.bgChanged ||= updatedAssets\n      .filter((asset) => asset.envHash === runtimeData.envHash)\n      .some((asset) => isDependencyOfBundle(module.bundle, asset.id))\n\n    const manifestChange = updatedAssets.find((e) => e.type === \"json\")\n\n    if (!!manifestChange) {\n      const changedIdSet = new Set(updatedAssets.map((e) => e.id))\n\n      const deps = Object.values(manifestChange.depsByBundle)\n        .map((o) => Object.values(o))\n        .flat()\n\n      state.bgChanged ||= deps.every((dep) => changedIdSet.has(dep))\n    }\n\n    consolidateUpdate()\n  })\n\n  hmrSocket.addEventListener(\"open\", () => {\n    // Send a ping event to the HMR server every 24 seconds to keep the connection alive\n    const interval = setInterval(() => hmrSocket.send(\"ping\"), 24_000)\n    hmrSocket.addEventListener(\"close\", () => clearInterval(interval))\n  })\n\n  hmrSocket.addEventListener(\"close\", async () => {\n    await pollingDevServer()\n    consolidateUpdate(true)\n  })\n}\n\ninjectBuilderSocket(async (event) => {\n  vLog(\"BGSW Runtime - On Build Repackaged\")\n  // maybe we should wait for a bit until we determine if the build is truly ready\n  switch (event.type) {\n    case BuildSocketEvent.BuildReady: {\n      state.buildReady ||= true\n      consolidateUpdate()\n      break\n    }\n    case BuildSocketEvent.CsChanged: {\n      state.csChanged ||= true\n      consolidateUpdate()\n      break\n    }\n  }\n})\n\nextCtx.runtime.onConnect.addListener(function (port) {\n  const isPagePort = port.name.startsWith(PAGE_PORT_PREFIX)\n  const isScriptPort = port.name.startsWith(SCRIPT_PORT_PREFIX)\n\n  if (isPagePort || isScriptPort) {\n    const portSet = isPagePort ? state.pagePorts : state.scriptPorts\n\n    portSet.add(port)\n\n    port.onDisconnect.addListener(() => {\n      portSet.delete(port)\n    })\n\n    port.onMessage.addListener(function (msg: BackgroundMessage) {\n      vLog(\"BGSW Runtime - On source changed\", msg)\n      if (msg.__plasmo_cs_changed__) {\n        state.csChanged ||= true\n      }\n\n      if (msg.__plasmo_page_changed__) {\n        state.pageChanged ||= true\n      }\n\n      consolidateUpdate()\n    })\n  }\n})\n\nextCtx.runtime.onMessage.addListener(function runtimeMessageHandler(\n  msg: BackgroundMessage\n) {\n  if (msg.__plasmo_full_reload__) {\n    vLog(\"BGSW Runtime - On top-level code changed\")\n    consolidateUpdate()\n  }\n\n  return true\n})\n"
  },
  {
    "path": "core/parcel-runtime/src/runtimes/page-runtime.ts",
    "content": "import { vLog } from \"@plasmo/utils/logging\"\n\nimport type { BackgroundMessage } from \"../types\"\nimport {\n  extCtx,\n  PAGE_PORT_PREFIX,\n  runtimeData,\n  triggerReload\n} from \"../utils/0-patch-module\"\nimport {\n  hmrAcceptCheck,\n  hmrState,\n  isDependencyOfBundle,\n  resetHmrState\n} from \"../utils/hmr-check\"\nimport { hmrAccept, hmrApplyUpdates, hmrDispose } from \"../utils/hmr-utils\"\nimport { injectHmrSocket } from \"../utils/inject-socket\"\nimport { injectReactRefresh } from \"../utils/react-refresh\"\n\nconst PORT_NAME = `${PAGE_PORT_PREFIX}${module.id}__`\nlet pagePort: chrome.runtime.Port\n\nconst parent = module.bundle.parent\n\nif (!parent || !parent.isParcelRequire) {\n  try {\n    pagePort = extCtx?.runtime.connect({\n      name: PORT_NAME\n    })\n\n    pagePort.onDisconnect.addListener(() => {\n      triggerReload()\n    })\n\n    // TODO: should prob use canHmr instead of isReact\n    if (!runtimeData.isReact) {\n      pagePort.onMessage.addListener(() => {\n        // bgsw reloaded, all context gone\n        triggerReload()\n      })\n    }\n  } catch (error) {\n    vLog(error)\n  }\n\n  injectHmrSocket(async (updatedAssets) => {\n    vLog(\"Page runtime - On HMR Update\")\n    if (runtimeData.isReact) {\n      resetHmrState()\n      // Is an extension page, can try to hot reload\n      const assets = updatedAssets.filter(\n        (asset) => asset.envHash === runtimeData.envHash\n      )\n\n      const canHmr = assets.some(\n        (asset) =>\n          asset.type === \"css\" ||\n          (asset.type === \"js\" &&\n            hmrAcceptCheck(module.bundle.root, asset.id, asset.depsByBundle))\n      )\n\n      if (canHmr) {\n        try {\n          await hmrApplyUpdates(assets)\n\n          // Dispose all old assets.\n          const disposedAssets = {} as Record<string, boolean>\n\n          for (const [asset, id] of hmrState.assetsToDispose) {\n            if (!disposedAssets[id]) {\n              hmrDispose(asset, id)\n              disposedAssets[id] = true\n            }\n          }\n\n          const acceptedAssets = {} as Record<string, boolean>\n\n          for (let i = 0; i < hmrState.assetsToAccept.length; i++) {\n            const [asset, id] = hmrState.assetsToAccept[i]\n            if (!acceptedAssets[id]) {\n              hmrAccept(asset, id)\n              acceptedAssets[id] = true\n            }\n          }\n        } catch (e) {\n          if (runtimeData.verbose === \"true\") {\n            console.trace(e)\n            alert(JSON.stringify(e))\n          }\n          await triggerReload(true)\n        }\n      }\n    } else {\n      const sourceChanged = updatedAssets\n        .filter((asset) => asset.envHash === runtimeData.envHash)\n        .some((asset) => isDependencyOfBundle(module.bundle, asset.id))\n      vLog(`Page runtime -`, { sourceChanged })\n\n      if (sourceChanged) {\n        // @ts-ignore\n        // if (module.hot) {\n        //   // @ts-ignore\n        //   module.hot.accept()\n        // }\n\n        pagePort.postMessage({\n          __plasmo_page_changed__: true\n        } as BackgroundMessage)\n      }\n    }\n  })\n}\n\nif (runtimeData.isReact) {\n  vLog(\"Injecting react refresh\")\n  injectReactRefresh()\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/runtimes/script-runtime.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Port refreshing code is based on https://github.com/crxjs/chrome-extension-tools/blob/963e41149cdf327eaa338c643c58909e30d69651/packages/vite-plugin/src/client/es/hmr-client-worker.ts#L88\n *  MIT License\n * Copyright (c) 2019 jacksteamdev\n */\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport type { BackgroundMessage } from \"../types\"\nimport {\n  extCtx,\n  runtimeData,\n  SCRIPT_PORT_PREFIX\n} from \"../utils/0-patch-module\"\nimport { isDependencyOfBundle } from \"../utils/hmr-check\"\nimport { injectHmrSocket } from \"../utils/inject-socket\"\nimport { createLoadingIndicator } from \"../utils/loading-indicator\"\n\nconst PORT_NAME = `${SCRIPT_PORT_PREFIX}${module.id}__`\nlet scriptPort: chrome.runtime.Port\nlet isActiveTab = false\n\nconst loadingIndicator = createLoadingIndicator()\n\nasync function consolidateUpdate() {\n  vLog(\"Script Runtime - reloading\")\n  if (isActiveTab) {\n    globalThis.location?.reload?.()\n  } else {\n    loadingIndicator.show({\n      reloadButton: true\n    })\n  }\n}\n\nfunction reloadPort() {\n  scriptPort?.disconnect()\n  // Potentially, if MAIN world, we use the external connection instead (?)\n  scriptPort = extCtx?.runtime.connect({\n    name: PORT_NAME\n  })\n\n  scriptPort.onDisconnect.addListener(() => {\n    consolidateUpdate()\n  })\n\n  scriptPort.onMessage.addListener((msg: BackgroundMessage) => {\n    // bgsw reloaded, all context gone\n    if (msg.__plasmo_cs_reload__) {\n      consolidateUpdate()\n    }\n\n    if (msg.__plasmo_cs_active_tab__) {\n      isActiveTab = true\n    }\n    return\n  })\n}\n\nfunction setupPort() {\n  if (!extCtx?.runtime) {\n    return\n  }\n\n  try {\n    reloadPort()\n    setInterval(reloadPort, 24_000)\n  } catch {\n    return\n  }\n}\n\nsetupPort()\n\ninjectHmrSocket(async (updatedAssets) => {\n  vLog(\"Script runtime - on updated assets\")\n\n  const isChanged = updatedAssets\n    .filter((asset) => asset.envHash === runtimeData.envHash)\n    .some((asset) => isDependencyOfBundle(module.bundle, asset.id))\n\n  if (isChanged) {\n    loadingIndicator.show()\n\n    if (extCtx?.runtime) {\n      scriptPort.postMessage({\n        __plasmo_cs_changed__: true\n      } as BackgroundMessage)\n    } else {\n      setTimeout(() => {\n        consolidateUpdate()\n      }, 4_700)\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-runtime/src/types.ts",
    "content": "export const plasmoRuntimeList = [\n  \"page-runtime\",\n  \"script-runtime\",\n  \"background-service-runtime\"\n] as const\n\nexport type PlasmoRuntime = (typeof plasmoRuntimeList)[number]\ndeclare global {\n  const __parcel__import__: Function\n  const __parcel__importScripts__: Function\n  interface Window {\n    $RefreshReg$: any\n    $RefreshSig$: any\n  }\n\n  interface NodeModule {\n    bundle: ParcelBundle\n  }\n}\n\nexport type ExtensionApi = typeof globalThis.chrome\n\ninterface ParcelModule {\n  hot: {\n    data: unknown\n    accept(cb: (arg0: (...args: Array<any>) => any) => void): void\n    dispose(cb: (arg0: unknown) => void): void\n    _acceptCallbacks: Array<(arg0: (...args: Array<any>) => any) => any>\n    _disposeCallbacks: Array<(arg0: unknown) => void>\n  }\n}\nexport interface ParcelBundle {\n  (arg0: string): unknown\n  cache: Record<string, ParcelModule>\n  hotData: Record<string, any>\n  Module: any\n  parent: ParcelBundle | null | undefined\n  isParcelRequire: true\n  modules: Record<\n    string,\n    [(...args: Array<any>) => any, Record<string, string>]\n  >\n  HMR_BUNDLE_ID: string\n  root: ParcelBundle\n}\n\nexport type ParcelAsset = [ParcelBundle, string]\n\nexport type RuntimeData = {\n  isContentScript: boolean\n  isBackground: boolean\n  isReact: boolean\n\n  runtimes: PlasmoRuntime[]\n\n  host?: string\n  port?: number\n\n  secure: boolean\n  serverPort?: number\n\n  verbose: \"true\" | \"false\"\n\n  entryFilePath: string\n  bundleId: string\n  envHash: string\n}\n\nexport type HmrAsset = {\n  id: string\n  url: string\n  type: string\n  output: string\n  envHash: string\n  outputFormat: string\n  depsByBundle: Record<string, Record<string, string>>\n}\n\nexport type HmrMessage =\n  | {\n      type: \"update\"\n      assets: Array<HmrAsset>\n    }\n  | {\n      type: \"error\"\n      diagnostics: {\n        ansi: Array<any>\n        html: Array<{\n          codeframe: string\n        }>\n      }\n    }\n\nexport type BackgroundMessage = {\n  __plasmo_full_reload__?: boolean\n  __plasmo_build_updated__?: boolean\n  __plasmo_cs_ping__?: boolean\n\n  __plasmo_page_changed__?: boolean\n  __plasmo_cs_reload__?: boolean\n  __plasmo_cs_changed__?: boolean\n  __plasmo_cs_active_tab__?: boolean\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/0-patch-module.ts",
    "content": "import { vLog } from \"@plasmo/utils/logging\"\n\nimport type { BackgroundMessage, ExtensionApi, RuntimeData } from \"../types\"\n\n// @ts-ignore\nexport const runtimeData = __plasmo_runtime_data__ as RuntimeData\n\nmodule.bundle.HMR_BUNDLE_ID = runtimeData.bundleId\n\nglobalThis.process = {\n  argv: [],\n  env: {\n    VERBOSE: runtimeData.verbose\n  }\n} as any\n\nconst OldModule = module.bundle.Module\n\nfunction Module(moduleName: string) {\n  OldModule.call(this, moduleName)\n  this.hot = {\n    data: module.bundle.hotData[moduleName],\n    _acceptCallbacks: [],\n    _disposeCallbacks: [],\n    accept: function (fn) {\n      this._acceptCallbacks.push(fn || function () {})\n    },\n    dispose: function (fn) {\n      this._disposeCallbacks.push(fn)\n    }\n  }\n  module.bundle.hotData[moduleName] = undefined\n}\n\nmodule.bundle.Module = Module\nmodule.bundle.hotData = {}\n\nexport const extCtx: ExtensionApi =\n  globalThis.browser || globalThis.chrome || null\n\nexport async function triggerReload(fullReload = false) {\n  if (fullReload) {\n    vLog(\"Triggering full reload\")\n    extCtx.runtime.sendMessage<BackgroundMessage>({\n      __plasmo_full_reload__: true\n    })\n  } else {\n    globalThis.location?.reload?.()\n  }\n}\n\nexport function getHostname() {\n  if (!runtimeData.host || runtimeData.host === \"0.0.0.0\") {\n    return location.protocol.indexOf(\"http\") === 0\n      ? location.hostname\n      : \"localhost\"\n  }\n\n  return runtimeData.host\n}\n\nexport function getSocketHostname() {\n  if (!runtimeData.host || runtimeData.host === \"0.0.0.0\") {\n    return \"localhost\";\n  }\n\n  return runtimeData.host\n}\n\nexport function getPort() {\n  return runtimeData.port || location.port\n}\n\nexport const PAGE_PORT_PREFIX = `__plasmo_runtime_page_`\nexport const SCRIPT_PORT_PREFIX = `__plasmo_runtime_script_`\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/bgsw.ts",
    "content": "import { extCtx, getHostname, getPort, runtimeData } from \"./0-patch-module\"\n\ndeclare const globalThis: ServiceWorkerGlobalScope\n\nconst devServer = `${\n  runtimeData.secure ? \"https\" : \"http\"\n}://${getHostname()}:${getPort()}/`\n\nexport async function pollingDevServer(delay = 1470) {\n  while (true) {\n    try {\n      await fetch(devServer)\n      break\n    } catch (e) {\n      await new Promise((resolve) => setTimeout(resolve, delay))\n    }\n  }\n}\n\nif (extCtx.runtime.getManifest().manifest_version === 3) {\n  const proxyLoc = extCtx.runtime.getURL(\"/__plasmo_hmr_proxy__?url=\")\n\n  globalThis.addEventListener(\"fetch\", function (evt) {\n    const reqUrl = evt.request.url\n    if (reqUrl.startsWith(proxyLoc)) {\n      const url = new URL(decodeURIComponent(reqUrl.slice(proxyLoc.length)))\n      if (\n        url.hostname === runtimeData.host &&\n        url.port === `${runtimeData.port}`\n      ) {\n        url.searchParams.set(\"t\", Date.now().toString())\n        evt.respondWith(\n          fetch(url).then(\n            (res) =>\n              new Response(res.body, {\n                headers: {\n                  \"Content-Type\":\n                    res.headers.get(\"Content-Type\") ?? \"text/javascript\"\n                }\n              })\n          )\n        )\n      } else {\n        evt.respondWith(\n          new Response(\"Plasmo HMR\", {\n            status: 200,\n            statusText: \"Testing\"\n          })\n        )\n      }\n    }\n  })\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/hmr-check.ts",
    "content": "import type { ParcelAsset, ParcelBundle } from \"../types\"\n\nexport const hmrState = {\n  checkedAssets: {} as Record<string, boolean>,\n  assetsToDispose: [] as Array<ParcelAsset>,\n  assetsToAccept: [] as Array<ParcelAsset>\n}\n\nexport const resetHmrState = () => {\n  hmrState.checkedAssets = {}\n  hmrState.assetsToDispose = []\n  hmrState.assetsToAccept = []\n}\n\nexport function getParents(\n  bundle: ParcelBundle,\n  id: string\n): Array<ParcelAsset> {\n  const { modules } = bundle\n  if (!modules) {\n    return []\n  }\n\n  let parents = []\n  let modId: string, depId: string, dep: string\n\n  for (modId in modules) {\n    for (depId in modules[modId][1]) {\n      dep = modules[modId][1][depId]\n\n      if (dep === id || (Array.isArray(dep) && dep[dep.length - 1] === id)) {\n        parents.push([bundle, modId])\n      }\n    }\n  }\n\n  if (bundle.parent) {\n    parents = parents.concat(getParents(bundle.parent, id))\n  }\n\n  return parents\n}\n\nexport function hmrAcceptCheck(\n  bundle: ParcelBundle,\n  id: string,\n  depsByBundle: Record<string, Record<string, string>>\n) {\n  if (hmrAcceptCheckOne(bundle, id, depsByBundle)) {\n    return true\n  }\n\n  // Traverse parents breadth first. All possible ancestries must accept the HMR update, or we'll reload.\n  const parents = getParents(module.bundle.root, id)\n\n  let accepted = false\n\n  while (parents.length > 0) {\n    const [parentAsset, parentId] = parents.shift()\n    const canHmr = hmrAcceptCheckOne(parentAsset, parentId, null)\n    if (canHmr) {\n      // If this parent accepts, stop traversing upward, but still consider siblings.\n      accepted = true\n    } else {\n      // Otherwise, queue the parents in the next level upward.\n      const p = getParents(module.bundle.root, parentId)\n      if (p.length === 0) {\n        // If there are no parents, then we've reached an entry without accepting. Reload.\n        accepted = false\n        break\n      }\n      parents.push(...p)\n    }\n  }\n\n  return accepted\n}\n\nfunction hmrAcceptCheckOne(\n  bundle: ParcelBundle,\n  id: string,\n  depsByBundle: Record<string, Record<string, string>>\n) {\n  const { modules } = bundle\n  if (!modules) {\n    return false\n  }\n\n  if (depsByBundle && !depsByBundle[bundle.HMR_BUNDLE_ID]) {\n    // If we reached the root bundle without finding where the asset should go,\n    // there's nothing to do. Mark as \"accepted\" so we don't reload the page.\n    if (!bundle.parent) {\n      return true\n    }\n\n    return hmrAcceptCheck(bundle.parent, id, depsByBundle)\n  }\n\n  if (hmrState.checkedAssets[id]) {\n    return true\n  }\n\n  hmrState.checkedAssets[id] = true\n\n  const cached = bundle.cache[id]\n\n  hmrState.assetsToDispose.push([bundle, id])\n\n  if (!cached || (cached.hot && cached.hot._acceptCallbacks.length)) {\n    hmrState.assetsToAccept.push([bundle, id])\n    return true\n  }\n\n  return false\n}\n\nexport function isDependencyOfBundle(bundle: ParcelBundle, id: string) {\n  const { modules } = bundle\n  if (!modules) {\n    return false\n  }\n\n  return !!modules[id]\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/hmr-utils.ts",
    "content": "import type { HmrAsset, ParcelAsset, ParcelBundle } from \"../types\"\nimport { extCtx, getHostname, getPort } from \"./0-patch-module\"\nimport { getParents, hmrState } from \"./hmr-check\"\n\nexport function hmrDownload(asset: HmrAsset) {\n  if (asset.type === \"js\") {\n    if (typeof document !== \"undefined\") {\n      return new Promise<HTMLScriptElement>((resolve, reject) => {\n        const script = document.createElement(\"script\")\n        script.src = `${asset.url}?t=${Date.now()}`\n\n        if (asset.outputFormat === \"esmodule\") {\n          script.type = \"module\"\n        }\n\n        script.addEventListener(\"load\", () => resolve(script))\n\n        script.addEventListener(\"error\", () =>\n          reject(new Error(`Failed to download asset: ${asset.id}`))\n        )\n\n        document.head?.appendChild(script)\n      })\n    }\n  }\n}\n\nexport async function hmrApplyUpdates(assets: Array<HmrAsset>) {\n  global.parcelHotUpdate = Object.create(null)\n\n  // If sourceURL comments aren't supported in eval, we need to load\n  // the update from the dev server over HTTP so that stack traces\n  // are correct in errors/logs. This is much slower than eval, so\n  // we only do it if needed (currently just Safari).\n  // https://bugs.webkit.org/show_bug.cgi?id=137297\n  // This path is also taken if a CSP disallows eval.\n\n  assets.forEach((asset) => {\n    asset.url = extCtx.runtime.getURL(\n      \"/__plasmo_hmr_proxy__?url=\" +\n        encodeURIComponent(`${asset.url}?t=${Date.now()}`)\n    )\n  })\n\n  const scriptsToRemove = await Promise.all(assets.map(hmrDownload))\n\n  try {\n    assets.forEach(function (asset) {\n      hmrApply(module.bundle.root, asset)\n    })\n  } finally {\n    delete global.parcelHotUpdate\n    if (scriptsToRemove) {\n      scriptsToRemove.forEach((script) => {\n        if (script) {\n          document.head?.removeChild(script)\n        }\n      })\n    }\n  }\n}\n\nfunction updateLink(link: Element) {\n  const newLink = link.cloneNode() as HTMLLinkElement\n\n  newLink.onload = function () {\n    if (link.parentNode !== null) {\n      link.parentNode.removeChild(link)\n    }\n  }\n  newLink.setAttribute(\n    \"href\",\n    link.getAttribute(\"href\").split(\"?\")[0] + \"?\" + Date.now()\n  )\n\n  link.parentNode.insertBefore(newLink, link.nextSibling)\n}\n\nlet cssTimeout = null\nfunction reloadCSS() {\n  if (cssTimeout) {\n    return\n  }\n\n  cssTimeout = setTimeout(function () {\n    const links = document.querySelectorAll('link[rel=\"stylesheet\"]')\n    for (var i = 0; i < links.length; i++) {\n      const href = links[i].getAttribute(\"href\")\n      const hostname = getHostname()\n\n      const servedFromHmrServer =\n        hostname === \"localhost\"\n          ? new RegExp(\n              \"^(https?:\\\\/\\\\/(0.0.0.0|127.0.0.1)|localhost):\" + getPort()\n            ).test(href)\n          : href.indexOf(hostname + \":\" + getPort())\n\n      const absolute =\n        /^https?:\\/\\//i.test(href) &&\n        href.indexOf(location.origin) !== 0 &&\n        !servedFromHmrServer\n\n      if (!absolute) {\n        updateLink(links[i])\n      }\n    }\n\n    cssTimeout = null\n  }, 47)\n}\n\nfunction hmrApply(bundle: ParcelBundle, asset: HmrAsset) {\n  const { modules } = bundle\n  if (!modules) {\n    return\n  }\n\n  if (asset.type === \"css\") {\n    reloadCSS()\n  } else if (asset.type === \"js\") {\n    const deps = asset.depsByBundle[bundle.HMR_BUNDLE_ID]\n\n    if (deps) {\n      if (modules[asset.id]) {\n        // Remove dependencies that are removed and will become orphaned.\n        // This is necessary so that if the asset is added back again, the cache is gone, and we prevent a full page reload.\n        // debugger\n        let oldDeps = modules[asset.id][1]\n        for (let dep in oldDeps) {\n          if (!deps[dep] || deps[dep] !== oldDeps[dep]) {\n            let id = oldDeps[dep]\n            let parents = getParents(module.bundle.root, id)\n            if (parents.length === 1) {\n              hmrDelete(module.bundle.root, id)\n            }\n          }\n        }\n      }\n\n      const fn = global.parcelHotUpdate[asset.id]\n      modules[asset.id] = [fn, deps]\n    } else if (bundle.parent) {\n      hmrApply(bundle.parent, asset)\n    }\n  }\n}\n\nfunction hmrDelete(bundle: ParcelBundle, id: string) {\n  let modules = bundle.modules\n  if (!modules) {\n    return\n  }\n\n  if (modules[id]) {\n    // Collect dependencies that will become orphaned when this module is deleted.\n    let deps = modules[id][1]\n    let orphans = []\n    for (let dep in deps) {\n      let parents = getParents(module.bundle.root, deps[dep])\n      if (parents.length === 1) {\n        orphans.push(deps[dep])\n      }\n    }\n\n    // Delete the module. This must be done before deleting dependencies in case of circular dependencies.\n    delete modules[id]\n    delete bundle.cache[id]\n\n    // Now delete the orphans.\n    orphans.forEach((id) => {\n      hmrDelete(module.bundle.root, id)\n    })\n  } else if (bundle.parent) {\n    hmrDelete(bundle.parent, id)\n  }\n}\n\nexport function hmrDispose(bundle: ParcelBundle, id: string) {\n  const cached = bundle.cache[id]\n  bundle.hotData[id] = {}\n  if (cached && cached.hot) {\n    cached.hot.data = bundle.hotData[id]\n  }\n\n  if (cached && cached.hot && cached.hot._disposeCallbacks.length) {\n    cached.hot._disposeCallbacks.forEach(function (cb) {\n      cb(bundle.hotData[id])\n    })\n  }\n\n  delete bundle.cache[id]\n}\n\nexport function hmrAccept(bundle: ParcelBundle, id: string) {\n  // Execute the module\n  bundle(id)\n\n  const cached = bundle.cache[id]\n\n  if (cached && cached.hot && cached.hot._acceptCallbacks.length) {\n    const parents = getParents(module.bundle.root, id)\n    cached.hot._acceptCallbacks.forEach(function (cb) {\n      const assetsToAlsoAccept: ParcelAsset[] = cb(() => parents)\n\n      if (assetsToAlsoAccept && assetsToAlsoAccept.length) {\n        assetsToAlsoAccept.forEach(([extraAsset, extraAssetId]) => {\n          hmrDispose(extraAsset, extraAssetId)\n        })\n\n        hmrState.assetsToAccept.push.apply(\n          hmrState.assetsToAccept,\n          assetsToAlsoAccept\n        )\n      }\n    })\n  }\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/inject-socket.ts",
    "content": "import { type BuildSocketEvent } from \"@plasmo/framework-shared/build-socket/event\"\nimport { eLog, iLog, wLog } from \"@plasmo/utils/logging\"\n\nimport type { HmrAsset, HmrMessage } from \"../types\"\nimport { getSocketHostname, getPort, runtimeData } from \"./0-patch-module\"\n\nfunction getBaseSocketUri(port = getPort()) {\n  const hostname = getSocketHostname()\n  const protocol =\n    runtimeData.secure ||\n    (location.protocol === \"https:\" &&\n      !/localhost|127.0.0.1|0.0.0.0/.test(hostname))\n      ? \"wss\"\n      : \"ws\"\n\n  return `${protocol}://${hostname}:${port}/`\n}\n\nfunction wsErrorHandler(e: ErrorEvent) {\n  if (typeof e.message === \"string\") {\n    eLog(\"[plasmo/parcel-runtime]: \" + e.message)\n  }\n}\n\nexport function injectBuilderSocket(\n  onData?: (data: { type: BuildSocketEvent }) => Promise<void>\n) {\n  if (typeof globalThis.WebSocket === \"undefined\") {\n    return\n  }\n\n  const builderWs = new WebSocket(getBaseSocketUri(Number(getPort()) + 1))\n\n  builderWs.addEventListener(\"message\", async function (event) {\n    const data = JSON.parse(event.data)\n    await onData(data)\n  })\n\n  builderWs.addEventListener(\"error\", wsErrorHandler)\n\n  return builderWs\n}\n\nexport function injectHmrSocket(\n  onUpdate: (assets: Array<HmrAsset>) => Promise<void>\n) {\n  if (typeof globalThis.WebSocket === \"undefined\") {\n    return\n  }\n\n  const hmrWs = new WebSocket(getBaseSocketUri())\n\n  hmrWs.addEventListener(\"message\", async function (event) {\n    const data = JSON.parse(event.data) as HmrMessage\n\n    if (data.type === \"update\") {\n      await onUpdate(data.assets)\n    }\n\n    if (data.type === \"error\") {\n      // Log parcel errors to console\n      for (const ansiDiagnostic of data.diagnostics.ansi) {\n        const stack = ansiDiagnostic.codeframe || ansiDiagnostic.stack\n\n        wLog(\n          \"[plasmo/parcel-runtime]: \" +\n            ansiDiagnostic.message +\n            \"\\n\" +\n            stack +\n            \"\\n\\n\" +\n            ansiDiagnostic.hints.join(\"\\n\")\n        )\n      }\n    }\n  })\n\n  hmrWs.addEventListener(\"error\", wsErrorHandler)\n\n  hmrWs.addEventListener(\"open\", () => {\n    iLog(\n      `[plasmo/parcel-runtime]: Connected to HMR server for ${runtimeData.entryFilePath}`\n    )\n  })\n\n  hmrWs.addEventListener(\"close\", () => {\n    wLog(\n      `[plasmo/parcel-runtime]: Connection to the HMR server is closed for ${runtimeData.entryFilePath}`\n    )\n  })\n\n  return hmrWs\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/loading-indicator.ts",
    "content": "/**\n * Copyright (c) Plasmo Corp, foss@plasmo.com, MIT Licensed\n *\n **************************************************\n * SVG Generated by SVG Artista on 2/8/2023, 4:53:34PM\n * MIT license (https://opensource.org/licenses/MIT)\n * W. https://svgartista.net\n **************************************************\n */\n\nconst LOADING_ID = \"__plasmo-loading__\"\n\nfunction createTrustedPolicy() {\n  const trustedTypes = globalThis.window?.trustedTypes\n  if (typeof trustedTypes === \"undefined\") {\n    return undefined\n  }\n\n  const trustedTypeLists = (\n    document.querySelector('meta[name=\"trusted-types\"]') as HTMLMetaElement\n  )?.content?.split(\" \")\n\n  const trustedKey = trustedTypeLists\n    ? trustedTypeLists[trustedTypeLists?.length - 1].replace(/;/g, \"\")\n    : undefined\n\n  // Function to update the CSP to allow the new trusted type policy or use existing policy\n  const trustedPolicy =\n    typeof trustedTypes !== \"undefined\"\n      ? trustedTypes.createPolicy(trustedKey || `trusted-html-${LOADING_ID}`, {\n          createHTML: (str) => str\n        })\n      : undefined\n\n  return trustedPolicy\n}\n\nconst trustedPolicy = createTrustedPolicy()\n\nfunction getLoader() {\n  return document.getElementById(LOADING_ID)\n}\n\nfunction isLoaderUnavailable() {\n  return !getLoader()\n}\n\nfunction createLoader() {\n  const loadingEl = document.createElement(\"div\")\n  loadingEl.id = LOADING_ID\n\n  const htmlText = `\n  <style>\n    #${LOADING_ID} {\n      background: #f3f3f3;\n      color: #333;\n      border: 1px solid #333;\n      box-shadow: #333 4.7px 4.7px;\n    }\n\n    #${LOADING_ID}:hover {\n      background: #e3e3e3;\n      color: #444;\n    }\n\n    @keyframes plasmo-loading-animate-svg-fill {\n      0% {\n        fill: transparent;\n      }\n    \n      100% {\n        fill: #333;\n      }\n    }\n\n    #${LOADING_ID} .svg-elem-1 {\n      animation: plasmo-loading-animate-svg-fill 1.47s cubic-bezier(0.47, 0, 0.745, 0.715) 0.8s both infinite;\n    }\n\n    #${LOADING_ID} .svg-elem-2 {\n      animation: plasmo-loading-animate-svg-fill 1.47s cubic-bezier(0.47, 0, 0.745, 0.715) 0.9s both infinite;\n    }\n    \n    #${LOADING_ID} .svg-elem-3 {\n      animation: plasmo-loading-animate-svg-fill 1.47s cubic-bezier(0.47, 0, 0.745, 0.715) 1s both infinite;\n    }\n\n    #${LOADING_ID} .hidden {\n      display: none;\n    }\n\n  </style>\n  \n  <svg height=\"32\" width=\"32\" viewBox=\"0 0 264 354\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\">\n    <path d=\"M139.221 282.243C154.252 282.243 166.903 294.849 161.338 308.812C159.489 313.454 157.15 317.913 154.347 322.109C146.464 333.909 135.26 343.107 122.151 348.538C109.043 353.969 94.6182 355.39 80.7022 352.621C66.7861 349.852 54.0034 343.018 43.9705 332.983C33.9375 322.947 27.105 310.162 24.3369 296.242C21.5689 282.323 22.9895 267.895 28.4193 254.783C33.8491 241.671 43.0441 230.464 54.8416 222.579C59.0353 219.777 63.4908 217.438 68.1295 215.588C82.0915 210.021 94.6978 222.671 94.6978 237.703L94.6978 255.027C94.6978 270.058 106.883 282.243 121.914 282.243H139.221Z\" fill=\"#333\" class=\"svg-elem-1\" ></path>\n    <path d=\"M192.261 142.028C192.261 126.996 204.867 114.346 218.829 119.913C223.468 121.763 227.923 124.102 232.117 126.904C243.915 134.789 253.11 145.996 258.539 159.108C263.969 172.22 265.39 186.648 262.622 200.567C259.854 214.487 253.021 227.272 242.988 237.308C232.955 247.343 220.173 254.177 206.256 256.946C192.34 259.715 177.916 258.294 164.807 252.863C151.699 247.432 140.495 238.234 132.612 226.434C129.808 222.238 127.47 217.779 125.62 213.137C120.056 199.174 132.707 186.568 147.738 186.568L165.044 186.568C180.076 186.568 192.261 174.383 192.261 159.352L192.261 142.028Z\" fill=\"#333\" class=\"svg-elem-2\" ></path>\n    <path d=\"M95.6522 164.135C95.6522 179.167 83.2279 191.725 68.8013 187.505C59.5145 184.788 50.6432 180.663 42.5106 175.227C26.7806 164.714 14.5206 149.772 7.28089 132.289C0.041183 114.807 -1.85305 95.5697 1.83772 77.0104C5.52849 58.4511 14.6385 41.4033 28.0157 28.0228C41.393 14.6423 58.4366 5.53006 76.9914 1.83839C95.5461 -1.85329 114.779 0.0414162 132.257 7.2829C149.735 14.5244 164.674 26.7874 175.184 42.5212C180.62 50.6576 184.744 59.5332 187.46 68.8245C191.678 83.2519 179.119 95.6759 164.088 95.6759L122.869 95.6759C107.837 95.6759 95.6522 107.861 95.6522 122.892L95.6522 164.135Z\" fill=\"#333\" class=\"svg-elem-3\"></path>\n  </svg>\n  <span class=\"hidden\">Context Invalidated, Press to Reload</span>\n  `\n\n  loadingEl.innerHTML = trustedPolicy\n    ? (trustedPolicy.createHTML(htmlText) as any)\n    : htmlText\n\n  loadingEl.style.pointerEvents = \"none\"\n\n  loadingEl.style.position = \"fixed\"\n  loadingEl.style.bottom = \"14.7px\"\n  loadingEl.style.right = \"14.7px\"\n  loadingEl.style.fontFamily = \"sans-serif\"\n\n  loadingEl.style.display = \"flex\"\n  loadingEl.style.justifyContent = \"center\"\n  loadingEl.style.alignItems = \"center\"\n\n  loadingEl.style.padding = \"14.7px\"\n  loadingEl.style.gap = \"14.7px\"\n\n  loadingEl.style.borderRadius = \"4.7px\"\n\n  loadingEl.style.zIndex = \"2147483647\"\n\n  loadingEl.style.opacity = \"0\"\n  loadingEl.style.transition = \"all 0.47s ease-in-out\"\n\n  return loadingEl\n}\n\nfunction injectLoaderEl(loaderEl: HTMLElement) {\n  return new Promise<void>((resolve) => {\n    if (!document.documentElement) {\n      globalThis.addEventListener(\"DOMContentLoaded\", () => {\n        if (isLoaderUnavailable()) {\n          document.documentElement.appendChild(loaderEl)\n        }\n        resolve()\n      })\n    } else {\n      if (isLoaderUnavailable()) {\n        document.documentElement.appendChild(loaderEl)\n        resolve()\n      }\n      resolve()\n    }\n  })\n}\n\nexport const createLoadingIndicator = () => {\n  let injectPromise: Promise<void>\n  if (isLoaderUnavailable()) {\n    const initialLoaderEl = createLoader()\n    injectPromise = injectLoaderEl(initialLoaderEl)\n  }\n\n  return {\n    show: async ({ reloadButton = false } = {}) => {\n      await injectPromise\n      const loadingEl = getLoader()\n      loadingEl.style.opacity = \"1\"\n\n      if (!reloadButton) {\n        return\n      }\n\n      loadingEl.onclick = (e) => {\n        e.stopPropagation()\n        globalThis.location.reload()\n      }\n      loadingEl.querySelector(\"span\").classList.remove(\"hidden\")\n\n      loadingEl.style.cursor = \"pointer\"\n      loadingEl.style.pointerEvents = \"all\"\n    },\n    hide: async () => {\n      await injectPromise\n      const loadingEl = getLoader()\n      loadingEl.style.opacity = \"0\"\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-runtime/src/utils/react-refresh.ts",
    "content": "import refreshRuntime from \"react-refresh/runtime\"\n\nexport async function injectReactRefresh() {\n  refreshRuntime.injectIntoGlobalHook(window)\n\n  window.$RefreshReg$ = function () {}\n  window.$RefreshSig$ = function () {\n    return function (type) {\n      return type\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-runtime/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"lib\": [\"WebWorker\", \"dom\"]\n  }\n}\n"
  },
  {
    "path": "core/parcel-runtime/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\"\n\nexport default defineConfig({\n  entry: [\"src/index.ts\", \"src/runtimes/*\"]\n})\n"
  },
  {
    "path": "core/parcel-transformer-inject-env/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-inject-env/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-inject-env\",\n  \"version\": \"0.2.12\",\n  \"description\": \"Plasmo Parcel Transformer to inject environment variables\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-inject-env/src/index.ts",
    "content": "import { Transformer } from \"@parcel/plugin\"\n\nimport { injectEnv } from \"@plasmo/utils/env\"\n\nexport default new Transformer({\n  async transform({ asset, options }) {\n    const code = await asset.getCode()\n\n    const injectedCode = injectEnv(code, options.env)\n\n    asset.setCode(injectedCode)\n\n    return [asset]\n  }\n})\n"
  },
  {
    "path": "core/parcel-transformer-inject-env/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-transformer-inline-css/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-inline-css/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-inline-css\",\n  \"version\": \"0.3.12\",\n  \"description\": \"Plasmo Parcel Transformer for inline CSS\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"browserslist\": \"4.24.4\",\n    \"lightningcss\": \"1.21.8\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-inline-css/src/get-tagets.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/blob/7023c08b7e99a9b8fd3c04995e4ef7ca92dee5c1/packages/transformers/css/src/CSSTransformer.js\n * MIT License\n */\n\nimport browserslist from \"browserslist\"\nimport { browserslistToTargets } from \"lightningcss\"\n\nlet cache = new Map()\n\nexport function getTargets(browsers) {\n  if (browsers == null) {\n    return undefined\n  }\n\n  let cached = cache.get(browsers)\n  if (cached != null) {\n    return cached\n  }\n\n  let targets = browserslistToTargets(browserslist(browsers))\n\n  cache.set(browsers, targets)\n  return targets\n}\n"
  },
  {
    "path": "core/parcel-transformer-inline-css/src/index.ts",
    "content": "/**\n * Copyright (c) 2024 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/blob/7023c08b7e99a9b8fd3c04995e4ef7ca92dee5c1/packages/transformers/css/src/CSSTransformer.js\n * MIT License\n */\n\nimport { relative } from \"path\"\nimport { Transformer } from \"@parcel/plugin\"\nimport { remapSourceLocation } from \"@parcel/utils\"\nimport { transform } from \"lightningcss\"\n\nimport { getTargets } from \"./get-tagets\"\n\nexport default new Transformer({\n  async transform({ asset, options }) {\n    // Normalize the asset's environment so that properties that only affect JS don't cause CSS to be duplicated.\n    // For example, with ESModule and CommonJS targets, only a single shared CSS bundle should be produced.\n    const [code, originalMap] = await Promise.all([\n      asset.getBuffer(),\n      asset.getMap()\n    ])\n\n    const targets = getTargets(asset.env.engines.browsers)\n\n    const res = transform({\n      filename: relative(options.projectRoot, asset.filePath),\n      targets,\n      code,\n      cssModules: true,\n      analyzeDependencies: asset.meta.hasDependencies !== false,\n      sourceMap: !!asset.env.sourceMap\n    })\n\n    asset.setBuffer(Buffer.from(res.code))\n\n    if (res.dependencies) {\n      for (let dep of res.dependencies) {\n        const loc = !originalMap\n          ? dep.loc\n          : remapSourceLocation(dep.loc, originalMap)\n\n        if (dep.type === \"import\" && !res.exports) {\n          asset.addDependency({\n            specifier: dep.url,\n            specifierType: \"url\",\n            loc,\n            meta: {\n              // For the glob resolver to distinguish between `@import` and other URL dependencies.\n              isCSSImport: true,\n              media: dep.media\n            }\n          })\n        } else if (dep.type === \"url\") {\n          asset.addURLDependency(dep.url, {\n            loc,\n            meta: {\n              placeholder: dep.placeholder\n            }\n          })\n        }\n      }\n    }\n\n    return [asset]\n  }\n})\n"
  },
  {
    "path": "core/parcel-transformer-inline-css/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-transformer-lab/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-lab/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-lab\",\n  \"version\": \"0.1.12\",\n  \"description\": \"Plasmo Parcel Transformer Laboratory - a way to experiment with how the transformer works\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/fs\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-lab/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n */\nimport { Transformer } from \"@parcel/plugin\"\n\nimport { iLog, vLog } from \"@plasmo/utils/logging\"\n\nimport { initState } from \"./state\"\n\nasync function collectDependencies() {}\n\nexport default new Transformer({\n  async transform({ asset, options }) {\n    vLog(\"@plasmohq/parcel-transformer-lab\")\n    const code = await asset.getCode()\n\n    const { state, getAssets } = initState(asset, code, options.hmrOptions)\n\n    if (asset.filePath.includes(\"hook.ts\")) {\n      iLog(\"Hook file: \", asset.filePath)\n    }\n\n    if (asset.filePath.endsWith(\"options.tsx\")) {\n      iLog(\"MONITORING: \", asset.filePath)\n\n      // asset.addDependency({\n      //   specifier: \"storage-hook\",\n      //   specifierType: \"esm\",\n      //   bundleBehavior: \"isolated\",\n      //   resolveFrom: asset.filePath\n      // })\n    }\n\n    await collectDependencies()\n\n    return getAssets()\n  }\n})\n"
  },
  {
    "path": "core/parcel-transformer-lab/src/state.ts",
    "content": "import type { FileSystem } from \"@parcel/fs\"\nimport type {\n  DependencyOptions,\n  HMROptions,\n  MutableAsset,\n  TransformerResult\n} from \"@parcel/types\"\n\nexport const state = {\n  code: \"\",\n  hot: false,\n  fs: null as FileSystem,\n\n  filePath: \"\",\n\n  asset: null as MutableAsset,\n\n  extraAssets: [] as TransformerResult[]\n}\n\nexport const addExtraAssets = async (\n  filePath: string,\n  bundlePath: string,\n  type = \"json\",\n  dependencies = [] as DependencyOptions[]\n) => {\n  state.extraAssets.push({\n    type,\n    uniqueKey: bundlePath,\n    content: await state.asset.fs.readFile(filePath, \"utf8\"),\n    pipeline: type === \"json\" ? \"raw-env\" : undefined,\n    bundleBehavior: \"isolated\",\n    isBundleSplittable: type !== \"json\",\n    env: state.asset.env,\n    dependencies,\n    meta: {\n      bundlePath,\n      webextEntry: false\n    }\n  })\n}\n\nexport const initState = (\n  asset: MutableAsset,\n  code: string,\n  hmrOptions: HMROptions | null | undefined\n) => {\n  state.code = code\n  state.fs = asset.fs\n  state.filePath = asset.filePath\n\n  state.asset = asset\n\n  return {\n    state,\n    getAssets: () => [...state.extraAssets, asset]\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-lab/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-manifest/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-manifest\",\n  \"version\": \"0.21.1\",\n  \"description\": \"Plasmo Parcel Transformer for Web Extension Manifest\",\n  \"files\": [\n    \"dist\",\n    \"runtime\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@mischnic/json-sourcemap\": \"0.1.1\",\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/fs\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"content-security-policy-parser\": \"0.6.0\",\n    \"json-schema-to-ts\": \"3.1.1\",\n    \"nullthrows\": \"1.1.1\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/runtime/plasmo-default-background.ts",
    "content": ""
  },
  {
    "path": "core/parcel-transformer-manifest/src/csp-patch-hmr.ts",
    "content": "import parseCSP from \"content-security-policy-parser\"\n\nconst DEFAULT_INSERT = \"'unsafe-eval'\"\n\nexport function cspPatchHMR(\n  policy: string | null | undefined,\n  insert = DEFAULT_INSERT\n) {\n  const defaultSrc =\n    insert === DEFAULT_INSERT ? \"'self' blob: filesystem:\" : \"'self'\"\n\n  if (policy) {\n    const csp = parseCSP(policy)\n    policy = \"\"\n\n    if (!csp[\"script-src\"]) {\n      csp[\"script-src\"] = [defaultSrc]\n    }\n\n    if (!csp[\"script-src\"].includes(insert)) {\n      csp[\"script-src\"].push(insert)\n    }\n\n    if (csp.sandbox && !csp.sandbox.includes(\"allow-scripts\")) {\n      csp.sandbox.push(\"allow-scripts\")\n    }\n\n    for (const k in csp) {\n      policy += `${k} ${csp[k].join(\" \")};`\n    }\n\n    return policy\n  } else {\n    return `script-src ${defaultSrc} ${insert};` + `object-src ${defaultSrc};`\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-action.ts",
    "content": "import { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { checkMV2, getState } from \"./state\"\n\nexport async function handleAction() {\n  const { program, filePath, ptrs, asset } = getState()\n  const isMV2 = checkMV2(program)\n\n  const browserActionName = isMV2 ? \"browser_action\" : \"action\"\n\n  const browserAction = isMV2 ? program.browser_action : program.action\n\n  if (!browserAction) {\n    return\n  }\n\n  if (browserAction.theme_icons) {\n    browserAction.theme_icons = browserAction.theme_icons.map(\n      (themeIcon, themeIndex) => {\n        for (const k of [\"light\", \"dark\"]) {\n          const loc = getJSONSourceLocation(\n            ptrs[`/${browserActionName}/theme_icons/${themeIndex}/${k}`],\n            \"value\"\n          )\n\n          themeIcon[k] = asset.addURLDependency(themeIcon[k], {\n            loc: {\n              ...loc,\n              filePath\n            }\n          })\n        }\n\n        return themeIcon\n      }\n    )\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-background.ts",
    "content": "import { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { cspPatchHMR } from \"./csp-patch-hmr\"\nimport type { MV2Data, MV3Data } from \"./schema\"\nimport { checkMV2, getState } from \"./state\"\n\nexport const handleBackground = () => {\n  const { program } = getState()\n  const isMV2 = checkMV2(program)\n  if (isMV2) {\n    handleMV2Background(program)\n  } else {\n    handleMV3Background(program)\n  }\n}\n\nconst defaultBackgroundScriptPath = \"../runtime/plasmo-default-background.ts\"\n\nfunction handleMV2Background(program: MV2Data) {\n  handleMV2BackgroundScript(program)\n  handleMV2HotCsp(program)\n}\n\nfunction handleMV3Background(program: MV3Data) {\n  const { env } = getState()\n\n  const isFirefox =\n    env.PLASMO_BROWSER === \"firefox\" || env.PLASMO_BROWSER === \"gecko\"\n\n  // Handle Firefox preliminary MV3 support:\n  if (isFirefox) {\n    handleFirefoxMV3Background(program)\n    return\n  }\n\n  handleMV3BackgroundServiceWorker(program)\n  handleMV3HotCsp(program)\n}\n\nfunction handleFirefoxMV3Background(program: MV3Data) {\n  const mv2Program = program as unknown as MV2Data\n  if (program.background?.service_worker) {\n    mv2Program.background = {\n      scripts: [program.background.service_worker]\n    }\n  }\n\n  handleMV2BackgroundScript(mv2Program)\n  handleMV3HotCsp(program)\n}\n\nfunction handleMV2BackgroundScript(program: MV2Data) {\n  const { hot, asset } = getState()\n\n  if (program.background?.scripts) {\n    vLog(`Handling MV2 background scripts`)\n    program.background.scripts = program.background.scripts.map((bgScript) =>\n      asset.addURLDependency(bgScript, {\n        bundleBehavior: \"isolated\",\n        needsStableName: true\n      })\n    )\n  }\n\n  if (hot) {\n    if (!program.background?.scripts) {\n      program.background = {\n        scripts: [\n          asset.addURLDependency(defaultBackgroundScriptPath, {\n            resolveFrom: __filename\n          })\n        ]\n      }\n    }\n  }\n}\n\nfunction handleMV3BackgroundServiceWorker(program: MV3Data) {\n  const { hot, asset, filePath, ptrs } = getState()\n\n  if (program.background?.service_worker) {\n    vLog(`Handling MV3 background service worker`)\n    program.background.service_worker = asset.addURLDependency(\n      program.background.service_worker,\n      {\n        bundleBehavior: \"isolated\",\n        needsStableName: true,\n        loc: {\n          filePath,\n          ...getJSONSourceLocation(ptrs[\"/background/service_worker\"], \"value\")\n        },\n        env: {\n          context: \"web-worker\"\n        }\n      }\n    )\n\n    // Since we bundle everything, and sw import is static (not async), we can ignore type module.\n    if (!!program.background.type) {\n      delete program.background.type\n    }\n  }\n\n  if (hot) {\n    if (!program.background) {\n      program.background = {\n        service_worker: asset.addURLDependency(defaultBackgroundScriptPath, {\n          resolveFrom: __filename,\n          env: {\n            context: \"web-worker\"\n          }\n        })\n      }\n    }\n  }\n}\n\nfunction handleMV2HotCsp(program: MV2Data) {\n  const { hot } = getState()\n\n  if (hot) {\n    // To enable HMR, we must override the CSP to allow 'unsafe-eval'\n    program.content_security_policy = cspPatchHMR(\n      program.content_security_policy\n    )\n  }\n}\n\n// Enable eval HMR for sandbox,\nfunction handleMV3HotCsp(program: MV3Data) {\n  const { hot } = getState()\n\n  if (hot) {\n    const csp = program.content_security_policy || {}\n    csp.extension_pages = cspPatchHMR(csp.extension_pages, `http://localhost`)\n    // Sandbox allows eval by default\n    if (csp.sandbox) {\n      csp.sandbox = cspPatchHMR(csp.sandbox)\n    }\n    program.content_security_policy = csp\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-content-scripts.ts",
    "content": "import { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { getState } from \"./state\"\n\nexport function handleContentScripts() {\n  const { program, asset, filePath, ptrs } = getState()\n  if (!program.content_scripts) {\n    return\n  }\n\n  for (let i = 0; i < program.content_scripts.length; ++i) {\n    const contentScript = program.content_scripts[i]\n\n    for (const k of [\"css\", \"js\"]) {\n      const assets = contentScript[k] || []\n\n      for (let j = 0; j < assets.length; ++j) {\n        assets[j] = asset.addURLDependency(assets[j], {\n          bundleBehavior: \"isolated\",\n          loc: {\n            filePath,\n            ...getJSONSourceLocation(\n              ptrs[`/content_scripts/${i}/${k}/${j}`],\n              \"value\"\n            )\n          }\n        })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-declarative-net-request.ts",
    "content": "import { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { getState } from \"./state\"\n\nexport async function handleDeclarativeNetRequest() {\n  const { program, filePath, ptrs, asset } = getState()\n  if (!program.declarative_net_request) {\n    return\n  }\n\n  const rrs = program.declarative_net_request.rule_resources\n\n  if (!rrs) {\n    return\n  }\n\n  program.declarative_net_request.rule_resources = rrs.map((resources, i) => {\n    resources.path = asset.addURLDependency(resources.path, {\n      pipeline: \"raw-env\",\n      loc: {\n        filePath,\n        ...getJSONSourceLocation(\n          ptrs[`/declarative_net_request/rule_resources/${i}/path`],\n          \"value\"\n        )\n      }\n    })\n\n    return resources\n  })\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-deep-loc.ts",
    "content": "import { extname } from \"path\"\nimport { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { getState } from \"./state\"\n\nconst DEEP_LOCS = [\n  [\"icons\"],\n  [\"browser_action\", \"default_icon\"],\n  [\"browser_action\", \"default_popup\"],\n  [\"page_action\", \"default_icon\"],\n  [\"page_action\", \"default_popup\"],\n  [\"action\", \"default_icon\"],\n  [\"action\", \"default_popup\"],\n  [\"chrome_url_overrides\"],\n  [\"devtools_page\"],\n  [\"options_ui\", \"page\"],\n  [\"sidebar_action\", \"default_icon\"],\n  [\"sidebar_action\", \"default_panel\"],\n  [\"side_panel\", \"default_path\"],\n  [\"storage\", \"managed_schema\"],\n  [\"theme\", \"images\", \"theme_frame\"],\n  [\"theme\", \"images\", \"additional_backgrounds\"],\n  [\"user_scripts\", \"api_script\"]\n]\n\nexport const handleDeepLOC = () => {\n  const { program, filePath, ptrs, asset } = getState()\n  const relevantLocs = DEEP_LOCS.map(\n    (loc) => [loc, \"/\" + loc.join(\"/\")] as const\n  ).filter(([_, location]) => !!ptrs[location])\n\n  for (const [loc, location] of relevantLocs) {\n    const lastIndex = loc.length - 1\n    const lastLoc = loc[lastIndex]\n\n    // Reduce it right before the last loc\n    const programPtr = loc.reduce(\n      (acc, key, index) => (index === lastIndex ? acc : acc[key]),\n      program\n    )\n\n    const obj = programPtr[lastLoc]\n\n    vLog(`Adding ${lastLoc}`)\n\n    if (typeof obj === \"string\") {\n      const ext = extname(obj)\n      programPtr[lastLoc] = asset.addURLDependency(obj, {\n        bundleBehavior: \"isolated\",\n        loc: {\n          filePath,\n          ...getJSONSourceLocation(ptrs[location], \"value\")\n        },\n        needsStableName: ext === \".html\",\n        pipeline: ext === \".json\" ? \"raw-env\" : undefined\n      })\n    } else {\n      for (const k of Object.keys(obj)) {\n        const ext = extname(obj[k])\n        obj[k] = asset.addURLDependency(obj[k], {\n          bundleBehavior: \"isolated\",\n          loc: {\n            filePath,\n            ...getJSONSourceLocation(ptrs[location + \"/\" + k], \"value\")\n          },\n          needsStableName: ext === \".html\",\n          pipeline: ext === \".json\" ? \"raw-env\" : undefined\n        })\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-dictionaries.ts",
    "content": "import path from \"path\"\nimport ThrowableDiagnostic, { getJSONSourceLocation } from \"@parcel/diagnostic\"\n\nimport { getState } from \"./state\"\n\nexport function handleDictionaries() {\n  const { program, ptrs, filePath, asset } = getState()\n  if (!program.dictionaries) {\n    return\n  }\n\n  for (const dict in program.dictionaries) {\n    const sourceLoc = getJSONSourceLocation(\n      ptrs[`/dictionaries/${dict}`],\n      \"value\"\n    )\n    const loc = {\n      filePath,\n      ...sourceLoc\n    }\n    const dictFile = program.dictionaries[dict]\n\n    if (path.extname(dictFile) !== \".dic\") {\n      throw new ThrowableDiagnostic({\n        diagnostic: [\n          {\n            message: \"Invalid Web Extension manifest\",\n            origin: \"@plasmohq/parcel-transformer-manifest\",\n            codeFrames: [\n              {\n                filePath,\n                codeHighlights: [\n                  {\n                    ...sourceLoc,\n                    message: \"Dictionaries must be .dic files\"\n                  }\n                ]\n              }\n            ]\n          }\n        ]\n      })\n    }\n\n    program.dictionaries[dict] = asset.addURLDependency(dictFile, {\n      needsStableName: true,\n      loc\n    })\n    asset.addURLDependency(dictFile.slice(0, -4) + \".aff\", {\n      needsStableName: true,\n      loc\n    })\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-locales.ts",
    "content": "import { resolve } from \"path\"\n\nimport { vLog, wLog } from \"@plasmo/utils/logging\"\n\nimport { getState } from \"./state\"\nimport { addExtraAssets, wLogOnce } from \"./utils\"\n\nexport async function handleLocales() {\n  const { program, asset, assetsDir, projectDir } = getState()\n  const localesDir = [\n    resolve(projectDir, \"locales\"),\n    resolve(assetsDir, \"locales\"),\n    resolve(assetsDir, \"_locales\")\n  ].find((dir) => asset.fs.existsSync(dir))\n\n  if (!localesDir) {\n    return\n  }\n\n  const localeEntries = await asset.fs.readdir(localesDir)\n\n  if (localeEntries.length === 0) {\n    vLog(\"No locale found, skipping\")\n    return\n  }\n\n  if (!program.default_locale) {\n    program.default_locale = localeEntries[0]\n    wLogOnce(`default_locale not set, fallback to ${localeEntries[0]}`)\n  }\n\n  const defaultLocaleMessageExists = await asset.fs.exists(\n    resolve(localesDir, program.default_locale, \"messages.json\")\n  )\n\n  if (!defaultLocaleMessageExists) {\n    wLog(\"Default locale message.json not found, skipping locale!\")\n    delete program.default_locale\n    return\n  }\n\n  await Promise.all(\n    localeEntries.map(async (locale) => {\n      vLog(`Adding locale ${locale}`)\n      const localeFilePath = resolve(localesDir, locale, \"messages.json\")\n      if (await asset.fs.exists(localeFilePath)) {\n        const bundlePath = `_locales/${locale}/messages.json`\n        asset.invalidateOnFileChange(localeFilePath)\n        await addExtraAssets(localeFilePath, bundlePath)\n      }\n    })\n  )\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-sandboxes.ts",
    "content": "import { resolve } from \"path\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { getState } from \"./state\"\n\nexport async function handleSandboxes() {\n  const { asset, srcDir, dotPlasmoDir, program, env } = getState()\n\n  // firefox does not support sandbox\n  if (env.PLASMO_BROWSER === \"firefox\" || env.PLASMO_BROWSER === \"gecko\") {\n    return\n  }\n\n  const srcPaths = [\n    \"sandboxes\",\n    \"sandbox.ts\",\n    \"sandbox.tsx\",\n    \"sandbox.svelte\",\n    \"sandbox.vue\"\n  ].map((file) => resolve(srcDir, file))\n\n  const dotSandboxesDir = resolve(dotPlasmoDir, \"sandboxes\")\n\n  const [\n    srcSandboxesDirExists,\n    srcSandboxTsFileExists,\n    srcSandboxTsxFileExists,\n    srcSandboxSvelteFileExists,\n    srcSandboxVueFileExists,\n    dotSandboxesDirExists\n  ] = await Promise.all(\n    [...srcPaths, dotSandboxesDir].map((p) => asset.fs.exists(p))\n  )\n\n  const [srcSandboxesDir] = srcPaths\n\n  const sandboxPages = []\n\n  if (srcSandboxesDirExists && dotSandboxesDirExists) {\n    const sandboxEntries = await asset.fs.readdir(srcSandboxesDir)\n\n    if (sandboxEntries.length > 0) {\n      sandboxEntries.forEach((entry) => {\n        const token = entry.split(\".\")\n        token.pop()\n        sandboxPages.push([entry, `${token.join(\".\")}.html`])\n      })\n    }\n  }\n\n  const hasSandboxFile =\n    srcSandboxTsFileExists ||\n    srcSandboxTsxFileExists ||\n    srcSandboxSvelteFileExists ||\n    srcSandboxVueFileExists\n\n  if (!hasSandboxFile && sandboxPages.length === 0) {\n    return\n  }\n\n  if (!program.sandbox) {\n    program.sandbox = {}\n  }\n\n  if (!program.sandbox.pages) {\n    program.sandbox.pages = []\n  }\n\n  if (hasSandboxFile) {\n    program.sandbox.pages.push(\n      asset.addURLDependency(\"sandbox.html\", {\n        needsStableName: true\n      })\n    )\n  }\n\n  await Promise.all(\n    sandboxPages.map(async ([entry, htmlEntry]) => {\n      const srcEntryPath = resolve(srcSandboxesDir, entry)\n      const entryPath = resolve(dotSandboxesDir, htmlEntry)\n      if (\n        (await asset.fs.exists(srcEntryPath)) &&\n        (await asset.fs.exists(entryPath))\n      ) {\n        vLog(`Adding sandbox page: ${entry}`)\n        program.sandbox.pages.push(\n          asset.addURLDependency(`sandboxes/${htmlEntry}`, {\n            needsStableName: true\n          })\n        )\n      }\n    })\n  )\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/handle-tabs.ts",
    "content": "import { resolve } from \"path\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { getState } from \"./state\"\n\nexport async function handleTabs() {\n  const { asset, dotPlasmoDir, srcDir } = getState()\n  const srcTabsDir = resolve(srcDir, \"tabs\")\n  const dotTabsDir = resolve(dotPlasmoDir, \"tabs\")\n\n  const [dotTabsDirExists, srcTabsDirExists] = await Promise.all([\n    asset.fs.exists(dotTabsDir),\n    asset.fs.exists(srcTabsDir)\n  ])\n\n  if (!dotTabsDirExists || !srcTabsDirExists) {\n    return\n  }\n\n  const tabsEntries = await asset.fs.readdir(srcTabsDir)\n\n  if (tabsEntries.length === 0) {\n    vLog(`No tab found in ${srcTabsDir}, skipping`)\n    return\n  }\n\n  const entryNames = tabsEntries.map((entry) => {\n    const token = entry.split(\".\")\n    token.pop()\n    return [entry, `${token.join(\".\")}.html`]\n  })\n\n  await Promise.all(\n    entryNames.map(async ([entry, htmlEntry]) => {\n      const entryPath = resolve(dotTabsDir, htmlEntry)\n      const srcEntryPath = resolve(srcTabsDir, entry)\n      if (\n        (await asset.fs.exists(entryPath)) &&\n        (await asset.fs.exists(srcEntryPath))\n      ) {\n        vLog(`Adding tab ${entry}`)\n        asset.addURLDependency(`./tabs/${htmlEntry}`, {\n          bundleBehavior: \"isolated\",\n          needsStableName: true\n        })\n      }\n    })\n  )\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/tree/v2/packages/transformers/webextension\n * MIT License\n */\nimport { parse } from \"@mischnic/json-sourcemap\"\nimport { Transformer } from \"@parcel/plugin\"\nimport type { TargetSourceMapOptions } from \"@parcel/types\"\nimport { validateSchema } from \"@parcel/utils\"\n\nimport { vLog } from \"@plasmo/utils/logging\"\n\nimport { handleAction } from \"./handle-action\"\nimport { handleBackground } from \"./handle-background\"\nimport { handleContentScripts } from \"./handle-content-scripts\"\nimport { handleDeclarativeNetRequest } from \"./handle-declarative-net-request\"\nimport { handleDeepLOC } from \"./handle-deep-loc\"\nimport { handleDictionaries } from \"./handle-dictionaries\"\nimport { handleLocales } from \"./handle-locales\"\nimport { handleSandboxes } from \"./handle-sandboxes\"\nimport { handleTabs } from \"./handle-tabs\"\nimport { normalizeManifest } from \"./normalize-manifest\"\nimport { MV2Schema, MV3Schema } from \"./schema\"\nimport { getState, initState } from \"./state\"\n\nasync function collectDependencies() {\n  normalizeManifest()\n\n  await Promise.all([\n    handleTabs(),\n    handleSandboxes(),\n    handleLocales(),\n    handleAction(),\n    handleDeclarativeNetRequest()\n  ])\n\n  handleContentScripts()\n  handleDictionaries()\n  handleDeepLOC()\n  handleBackground()\n}\n\nconst getSourceMapConfig = (): TargetSourceMapOptions => {\n  switch (process.env.__PLASMO_FRAMEWORK_INTERNAL_SOURCE_MAPS) {\n    case \"inline\": {\n      return {\n        inline: true,\n        inlineSources: true\n      }\n    }\n    case \"external\": {\n      return {\n        inline: false,\n        inlineSources: false\n      }\n    }\n    default: {\n      return undefined\n    }\n  }\n}\n\nexport default new Transformer({\n  async transform({ asset, options }) {\n    vLog(\"@plasmohq/parcel-transformer-manifest\")\n    // Set environment to browser, since web extensions are always used in\n    // browsers, and because it avoids delegating extra config to the user\n\n    asset.setEnvironment({\n      context: \"browser\",\n      outputFormat:\n        asset.env.outputFormat === \"commonjs\"\n          ? \"global\"\n          : asset.env.outputFormat,\n      engines: {\n        browsers: asset.env.engines.browsers\n      },\n      sourceMap: asset.env.sourceMap && getSourceMapConfig(),\n      includeNodeModules: asset.env.includeNodeModules,\n      sourceType: asset.env.sourceType,\n      isLibrary: asset.env.isLibrary,\n      shouldOptimize: asset.env.shouldOptimize,\n      shouldScopeHoist: asset.env.shouldScopeHoist\n    })\n\n    const code = await asset.getCode()\n    const parsed = parse(code)\n    const data = parsed.data\n\n    const schema = data.manifest_version === 3 ? MV3Schema : MV2Schema\n\n    validateSchema.diagnostic(\n      schema,\n      {\n        data,\n        source: code,\n        filePath: asset.filePath\n      },\n      \"@plasmohq/parcel-transformer-manifest\",\n      \"Invalid Web Extension manifest\"\n    )\n\n    await initState(asset, data, parsed.pointers, options)\n\n    const state = getState()\n\n    await collectDependencies()\n\n    state.asset.setCode(JSON.stringify(data, null, 2))\n    state.asset.meta.webextEntry = true\n\n    vLog(\"+ Finished transforming manifest\")\n    return state.getAssets()\n  }\n})\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/normalize-manifest.ts",
    "content": "import { getState } from \"./state\"\n\nexport const normalizeManifest = () => {\n  const { program } = getState()\n  delete program.$schema\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/schema.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/webextension/src/schema.js\n * MIT License\n */\n\nimport type { FromSchema } from \"json-schema-to-ts\"\n\nimport {\n  validateBrowserVersion,\n  validateSemanticVersion\n} from \"./validate-version\"\n\nconst stringSchema = {\n  type: \"string\"\n} as const\n\nconst booleanSchema = {\n  type: \"boolean\"\n} as const\n\nconst iconsSchema = {\n  type: \"object\",\n  properties: {},\n  additionalProperties: stringSchema\n} as const\n\nconst actionProps = {\n  // FF only\n  browser_style: booleanSchema,\n  chrome_style: booleanSchema,\n  // You can also have a raw string, but not in Edge, apparently...\n  default_icon: {\n    oneOf: [iconsSchema, stringSchema]\n  },\n  default_popup: stringSchema,\n  default_title: stringSchema\n} as const\n\nconst arraySchema = {\n  type: \"array\",\n  items: stringSchema\n} as const\n\nconst browserAction = {\n  type: \"object\",\n  properties: {\n    ...actionProps,\n    // rest are FF only\n    default_area: {\n      type: \"string\",\n      enum: [\"navbar\", \"menupanel\", \"tabstrip\", \"personaltoolbar\"]\n    },\n    theme_icons: {\n      type: \"array\",\n      items: {\n        type: \"object\",\n        properties: {\n          light: stringSchema,\n          dark: stringSchema,\n          size: {\n            type: \"number\"\n          }\n        },\n        additionalProperties: false,\n        required: [\"light\", \"dark\", \"size\"]\n      }\n    }\n  },\n  additionalProperties: false\n} as const\n\nconst warBase = {\n  type: \"object\",\n  properties: {\n    resources: arraySchema,\n    matches: arraySchema,\n    extension_ids: arraySchema,\n    use_dynamic_url: booleanSchema\n  },\n  additionalProperties: false\n} as const\n\nconst commonProps = {\n  $schema: stringSchema,\n  name: stringSchema,\n  version: {\n    type: \"string\",\n    __validate: validateSemanticVersion\n  },\n  default_locale: stringSchema,\n  description: stringSchema,\n  icons: iconsSchema,\n  author: stringSchema,\n  browser_specific_settings: {\n    type: \"object\",\n    properties: {},\n    additionalProperties: {\n      type: \"object\",\n      properties: {}\n    }\n  },\n  chrome_settings_overrides: {\n    type: \"object\",\n    properties: {\n      homepage: stringSchema,\n      search_provider: {\n        type: \"object\",\n        properties: {\n          name: stringSchema,\n          keyword: stringSchema,\n          favicon_url: stringSchema,\n          search_url: stringSchema,\n          encoding: stringSchema,\n          suggest_url: stringSchema,\n          image_url: stringSchema,\n          instant_url: stringSchema,\n          search_url_post_params: stringSchema,\n          suggest_url_post_params: stringSchema,\n          image_url_post_params: stringSchema,\n          instant_url_post_params: stringSchema,\n          alternate_urls: arraySchema,\n          prepopulated_id: {\n            type: \"number\"\n          },\n          is_default: booleanSchema\n        },\n        additionalProperties: false,\n        required: [\"name\", \"search_url\"]\n      },\n      startup_pages: arraySchema\n    },\n    additionalProperties: false\n  },\n  chrome_url_overrides: {\n    type: \"object\",\n    properties: {\n      bookmarks: stringSchema,\n      history: stringSchema,\n      newtab: stringSchema\n    },\n    additionalProperties: false\n  },\n  commands: {\n    type: \"object\",\n    properties: {},\n    additionalProperties: {\n      type: \"object\",\n      properties: {\n        suggested_key: {\n          type: \"object\",\n          properties: {\n            default: stringSchema,\n            mac: stringSchema,\n            linux: stringSchema,\n            windows: stringSchema,\n            chromeos: stringSchema,\n            android: stringSchema,\n            ios: stringSchema\n          },\n          additionalProperties: false\n        },\n        description: stringSchema,\n        global: booleanSchema\n      },\n      additionalProperties: false\n    }\n  },\n  content_scripts: {\n    type: \"array\",\n    items: {\n      type: \"object\",\n      properties: {\n        matches: arraySchema,\n        css: arraySchema,\n        js: arraySchema,\n        match_about_blank: booleanSchema,\n        match_origin_as_fallback: booleanSchema,\n        exclude_matches: arraySchema,\n        include_globs: arraySchema,\n        exclude_globs: arraySchema,\n        run_at: {\n          type: \"string\",\n          enum: [\"document_idle\", \"document_start\", \"document_end\"]\n        },\n        all_frames: booleanSchema\n      },\n      additionalProperties: false,\n      required: [\"matches\"]\n    }\n  },\n  declarative_net_request: {\n    type: \"object\",\n    properties: {\n      rule_resources: {\n        type: \"array\",\n        items: {\n          type: \"object\",\n          properties: {\n            id: stringSchema,\n            enabled: booleanSchema,\n            path: stringSchema\n          },\n          additionalProperties: false,\n          required: [\"id\", \"enabled\", \"path\"]\n        }\n      }\n    },\n    additionalProperties: false,\n    required: [\"rule_resources\"]\n  },\n  devtools_page: stringSchema,\n  // looks to be FF only\n  dictionaries: {\n    type: \"object\",\n    properties: {},\n    additionalProperties: stringSchema\n  },\n  externally_connectable: {\n    type: \"object\",\n    properties: {\n      ids: arraySchema,\n      matches: arraySchema,\n      accept_tls_channel_id: booleanSchema\n    },\n    additionalProperties: false\n  },\n  // These next two are where it gets a bit Chrome-y\n  // (we don't include all because some have next to no actual use)\n  file_browser_handlers: {\n    type: \"array\",\n    items: {\n      type: \"object\",\n      properties: {\n        id: stringSchema,\n        default_title: stringSchema,\n        file_filters: arraySchema\n      },\n      additionalProperties: false,\n      required: [\"id\", \"default_title\", \"file_filters\"]\n    }\n  },\n  file_system_provider_capabilities: {\n    type: \"object\",\n    properties: {\n      configurable: booleanSchema,\n      multiple_mounts: booleanSchema,\n      watchable: booleanSchema,\n      source: {\n        type: \"string\",\n        enum: [\"file\", \"device\", \"network\"]\n      }\n    },\n    additionalProperties: false,\n    required: [\"source\"]\n  },\n  homepage_url: stringSchema,\n  incognito: {\n    type: \"string\",\n    enum: [\"spanning\", \"split\", \"not_allowed\"]\n  },\n  key: stringSchema,\n  minimum_chrome_version: {\n    type: \"string\",\n    __validate: validateBrowserVersion\n  },\n  // No NaCl modules because deprecated\n  oauth2: {\n    type: \"object\",\n    properties: {\n      client_id: stringSchema,\n      scopes: arraySchema\n    },\n    additionalProperties: false\n  },\n  offline_enabled: booleanSchema,\n  omnibox: {\n    type: \"object\",\n    properties: {},\n    additionalProperties: stringSchema\n  },\n  optional_host_permissions: arraySchema,\n  optional_permissions: arraySchema,\n  // options_page is deprecated\n  options_ui: {\n    type: \"object\",\n    properties: {\n      browser_style: booleanSchema,\n      chrome_style: booleanSchema,\n      open_in_tab: booleanSchema,\n      page: stringSchema\n    },\n    additionalProperties: false,\n    required: [\"page\"]\n  },\n  permissions: arraySchema,\n  // FF only, but has some use\n  protocol_handlers: {\n    type: \"array\",\n    items: {\n      type: \"object\",\n      properties: {\n        protocol: stringSchema,\n        name: stringSchema,\n        uriTemplate: stringSchema\n      },\n      additionalProperties: false,\n      required: [\"protocol\", \"name\", \"uriTemplate\"]\n    }\n  },\n  // Chrome only\n  requirements: {\n    type: \"object\",\n    properties: {\n      \"3D\": {\n        type: \"object\",\n        properties: {\n          features: arraySchema\n        },\n        additionalProperties: false\n      }\n    }\n  },\n  short_name: stringSchema,\n  // FF only, but has some use\n  sidebar_action: {\n    type: \"object\",\n    properties: {\n      browser_style: actionProps.browser_style,\n      default_icon: actionProps.default_icon,\n      default_panel: stringSchema,\n      default_title: stringSchema,\n      open_at_install: booleanSchema\n    },\n    additionalProperties: false,\n    required: [\"default_panel\"]\n  },\n  storage: {\n    type: \"object\",\n    properties: {\n      managed_schema: stringSchema\n    },\n    additionalProperties: false\n  },\n  theme: {\n    type: \"object\",\n    properties: {\n      images: {\n        type: \"object\",\n        properties: {\n          theme_frame: stringSchema,\n          additional_backgrounds: arraySchema\n        },\n        additionalProperties: false\n      },\n      colors: {\n        type: \"object\",\n        properties: {\n          bookmark_text: stringSchema,\n          button_background_active: stringSchema,\n          button_background_hover: stringSchema,\n          icons: stringSchema,\n          icons_attention: stringSchema,\n          frame: stringSchema,\n          frame_inactive: stringSchema,\n          ntp_background: stringSchema,\n          ntp_text: stringSchema,\n          popup: stringSchema,\n          popup_border: stringSchema,\n          popup_highlight: stringSchema,\n          popup_highlight_text: stringSchema,\n          popup_text: stringSchema,\n          sidebar: stringSchema,\n          sidebar_border: stringSchema,\n          sidebar_highlight: stringSchema,\n          sidebar_highlight_text: stringSchema,\n          sidebar_text: stringSchema,\n          tab_background_separator: stringSchema,\n          tab_background_text: stringSchema,\n          tab_line: stringSchema,\n          tab_loading: stringSchema,\n          tab_selected: stringSchema,\n          tab_text: stringSchema,\n          toolbar: stringSchema,\n          toolbar_bottom_separator: stringSchema,\n          toolbar_field: stringSchema,\n          toolbar_field_border: stringSchema,\n          toolbar_field_border_focus: stringSchema,\n          toolbar_field_focus: stringSchema,\n          toolbar_field_highlight: stringSchema,\n          toolbar_field_highlight_text: stringSchema,\n          toolbar_field_separator: stringSchema,\n          toolbar_field_text: stringSchema,\n          toolbar_field_text_focus: stringSchema,\n          toolbar_text: stringSchema,\n          toolbar_top_separator: stringSchema,\n          toolbar_vertical_separator: stringSchema\n        },\n        additionalProperties: false\n      },\n      properties: {\n        type: \"object\",\n        properties: {\n          additional_backgrounds_alignment: arraySchema,\n          additional_backgrounds_tiling: {\n            type: \"array\",\n            items: {\n              type: \"string\",\n              enum: [\"no-repeat\", \"repeat\", \"repeat-x\", \"repeat-y\"]\n            }\n          }\n        },\n        additionalProperties: false\n      }\n    },\n    additionalProperties: false,\n    required: [\"colors\"]\n  },\n  tts_engine: {\n    type: \"object\",\n    properties: {\n      voices: {\n        type: \"array\",\n        items: {\n          type: \"object\",\n          properties: {\n            voice_name: stringSchema,\n            lang: stringSchema,\n            event_type: {\n              type: \"string\",\n              enum: [\"start\", \"word\", \"sentence\", \"marker\", \"end\", \"error\"]\n            }\n          },\n          additionalProperties: false,\n          required: [\"voice_name\", \"event_type\"]\n        }\n      }\n    },\n    additionalProperties: false\n  },\n  update_url: stringSchema,\n  user_scripts: {\n    type: \"object\",\n    properties: {\n      api_script: stringSchema\n    },\n    additionalProperties: false\n  },\n  version_name: stringSchema\n} as const\n\nexport const MV3Schema = {\n  type: \"object\",\n  properties: {\n    ...commonProps,\n    manifest_version: {\n      type: \"number\",\n      enum: [3]\n    },\n    action: browserAction,\n    background: {\n      type: \"object\",\n      properties: {\n        service_worker: stringSchema,\n        type: {\n          type: \"string\",\n          enum: [\"classic\", \"module\"]\n        }\n      },\n      additionalProperties: false,\n      required: [\"service_worker\"]\n    },\n    content_security_policy: {\n      type: \"object\",\n      properties: {\n        extension_pages: stringSchema,\n        sandbox: stringSchema\n      },\n      additionalProperties: false\n    },\n    host_permissions: arraySchema,\n    sandbox: {\n      type: \"object\",\n      properties: {\n        pages: arraySchema\n      },\n      additionalProperties: false\n    },\n    web_accessible_resources: {\n      type: \"array\",\n      items: {\n        oneOf: [\n          { ...warBase, required: [\"resources\", \"matches\"] },\n          { ...warBase, required: [\"resources\", \"extension_ids\"] }\n        ]\n      }\n    }\n  },\n  required: [\"manifest_version\", \"name\", \"version\"]\n} as const\n\nexport type MV3Data = FromSchema<typeof MV3Schema>\n\nexport const MV2Schema = {\n  type: \"object\",\n  properties: {\n    ...commonProps,\n    manifest_version: {\n      type: \"number\",\n      enum: [2]\n    },\n    background: {\n      type: \"object\",\n      properties: {\n        scripts: arraySchema,\n        page: stringSchema,\n        persistent: booleanSchema\n      },\n      additionalProperties: false\n    },\n    browser_action: browserAction,\n    content_security_policy: stringSchema,\n    page_action: {\n      type: \"object\",\n      properties: {\n        ...actionProps,\n        // rest are FF only\n        hide_matches: arraySchema,\n        show_matches: arraySchema,\n        pinned: booleanSchema\n      },\n      additionalProperties: false\n    },\n    sandbox: {\n      type: \"object\",\n      properties: {\n        pages: arraySchema,\n        content_security_policy: stringSchema\n      },\n      additionalProperties: false\n    },\n    web_accessible_resources: arraySchema\n  },\n  required: [\"manifest_version\", \"name\", \"version\"]\n} as const\n\nexport type MV2Data = FromSchema<typeof MV2Schema>\n\nexport type ManifestData = MV2Data | MV3Data\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/state.ts",
    "content": "import { dirname, resolve } from \"path\"\nimport type { Mapping } from \"@mischnic/json-sourcemap\"\nimport type {\n  MutableAsset,\n  PluginOptions,\n  TransformerResult\n} from \"@parcel/types\"\n\nimport type { ManifestData, MV2Data } from \"./schema\"\n\ntype ExtraAsset = TransformerResult\n\nexport const storeState = (\n  asset: MutableAsset,\n  program: ManifestData,\n  ptrs: Record<string, Mapping>,\n  options: PluginOptions\n) => {\n  const base = {\n    extraAssets: [] as ExtraAsset[],\n    program,\n    hmrOptions: options.hmrOptions,\n    hot: Boolean(options.hmrOptions),\n    fs: asset.fs,\n    filePath: asset.filePath,\n    ptrs,\n    asset,\n    env: options.env,\n    _isMV2: program.manifest_version === 2\n  }\n  const dotPlasmoDir = dirname(asset.filePath)\n  const projectDir = resolve(dotPlasmoDir, \"..\")\n  const assetsDir = resolve(projectDir, \"assets\")\n\n  return {\n    ...base,\n    srcDir: process.env.PLASMO_SRC_DIR,\n    dotPlasmoDir,\n    projectDir,\n    assetsDir,\n    getAssets: () => [...base.extraAssets, asset]\n  }\n}\n\ntype StateParams = Parameters<typeof storeState>\n\ntype State = Partial<Awaited<ReturnType<typeof storeState>>>\n\nlet state: State = {}\n\nexport const getState = () => state\n\nexport const initState = async (...props: StateParams) => {\n  state = storeState(...props)\n}\n\nexport const checkMV2 = (program: ManifestData): program is MV2Data =>\n  state._isMV2\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/utils.ts",
    "content": "import type { DependencyOptions } from \"@parcel/types\"\n\nimport { injectEnv } from \"@plasmo/utils/env\"\nimport { wLog } from \"@plasmo/utils/logging\"\n\nimport { getState } from \"./state\"\n\nexport const addExtraAssets = async (\n  filePath: string,\n  bundlePath: string,\n  type = \"json\",\n  dependencies = [] as DependencyOptions[]\n) => {\n  const { asset, fs, extraAssets } = getState()\n  const rawContent = await fs.readFile(filePath, \"utf8\")\n  const parsedContent = injectEnv(rawContent)\n  extraAssets.push({\n    type,\n    uniqueKey: bundlePath,\n    content: parsedContent,\n    bundleBehavior: \"isolated\",\n    isBundleSplittable: type !== \"json\",\n    env: asset.env,\n    dependencies,\n    meta: {\n      bundlePath,\n      webextEntry: false\n    }\n  })\n}\n\nexport const wLogOnce = (msg: string) => {\n  if (!!process.env.__PLASMO_FRAMEWORK_INTERNAL_WATCHER_STARTED) {\n    return\n  }\n  wLog(msg)\n}\n"
  },
  {
    "path": "core/parcel-transformer-manifest/src/validate-version.ts",
    "content": "const MIN_VERSION = 0\nconst MAX_VERSION = 65535\n\nconst isInvalidVersion = (parts: string[]) =>\n  parts.some((part) => {\n    const num = Number(part)\n    return !isNaN(num) && num < MIN_VERSION && num > MAX_VERSION + 1\n  })\n\nconst validateVersion = (ver: string, maxSize = 3, name = \"Browser\") => {\n  const parts = ver.split(\".\")\n  if (parts.length > maxSize) {\n    return `${name} versions to have at most ${maxSize} dots`\n  }\n\n  if (isInvalidVersion(parts)) {\n    return `${name} versions must be dot-separated integers between ${MIN_VERSION} and ${MAX_VERSION}`\n  }\n\n  return undefined\n}\n\nexport const validateSemanticVersion = (ver: string) =>\n  validateVersion(ver, 3, \"Semantic\")\n\nexport const validateBrowserVersion = (ver: string) =>\n  validateVersion(ver, 4, \"Browser\")\n"
  },
  {
    "path": "core/parcel-transformer-manifest/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/framework\",\n  \"include\": [\"src/**/*.ts\", \"../../cli/plasmo/templates/plasmo.d.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-transformer-svelte/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-svelte/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-svelte\",\n  \"version\": \"0.6.1\",\n  \"description\": \"Plasmo Parcel Transformer for Svelte\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"svelte\": \"4.2.19\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-svelte/src/convert-error.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus\n * Copyright (c) 2023 Christoph Hommelsheim\n * MIT License\n */\nimport type { Diagnostic } from \"@parcel/diagnostic\"\nimport type SourceMap from \"@parcel/source-map\"\nimport type { Warning } from \"svelte/types/compiler/interfaces\"\n\nimport { convertLOC } from \"./convert-loc\"\nimport type { MutableAsset } from \"./types\"\n\nexport function convertError(\n  asset: MutableAsset,\n  originalMap: SourceMap,\n  code: string,\n  diagnostic: Warning\n) {\n  let message = diagnostic.message || \"Unknown error\"\n  if (diagnostic.code) {\n    message = `${message} (${diagnostic.code})`\n  }\n  const res: Diagnostic = {\n    message\n  }\n  if (diagnostic.frame) {\n    res.hints = [diagnostic.frame]\n  }\n  if (diagnostic.start !== undefined && diagnostic.end !== undefined) {\n    const { start, end } = convertLOC(asset, originalMap, diagnostic)\n    res.codeFrames = [\n      {\n        filePath: asset.filePath,\n        code,\n        language: \"svelte\",\n        codeHighlights: [\n          {\n            start,\n            end\n          }\n        ]\n      }\n    ]\n  }\n\n  return res\n}\n"
  },
  {
    "path": "core/parcel-transformer-svelte/src/convert-loc.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus\n * Copyright (c) 2023 Christoph Hommelsheim\n * MIT License\n */\nimport type SourceMap from \"@parcel/source-map\"\nimport { remapSourceLocation } from \"@parcel/utils\"\nimport type { Warning } from \"svelte/types/compiler/interfaces\"\n\nimport type { MutableAsset } from \"./types\"\n\nexport function convertLOC(\n  asset: MutableAsset,\n  originalMap: SourceMap,\n  loc: Warning\n) {\n  let location = {\n    filePath: asset.filePath,\n    start: {\n      line: loc.start.line + Number(asset.meta.startLine || 1) - 1,\n      column: loc.start.column + 1\n    },\n    end: {\n      line: loc.end.line + Number(asset.meta.startLine || 1) - 1,\n      column: loc.end.column + 1\n    }\n  }\n  if (originalMap) {\n    location = remapSourceLocation(location, originalMap)\n  }\n  return location\n}\n"
  },
  {
    "path": "core/parcel-transformer-svelte/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus\n * Copyright (c) 2023 Christoph Hommelsheim\n * MIT License\n */\nimport ThrowableDiagnostic from \"@parcel/diagnostic\"\nimport { Transformer } from \"@parcel/plugin\"\nimport { relativeUrl } from \"@parcel/utils\"\nimport { compile, preprocess, type CompileOptions } from \"svelte/compiler\"\n\nimport { convertError } from \"./convert-error\"\nimport { extendSourceMap } from \"./source-map\"\n\nexport default new Transformer({\n  async loadConfig({ config, options }) {\n    const conf = await config.getConfig(\n      [\n        \".svelterc\",\n        \"svelte.config.js\",\n        \"svelte.config.cjs\",\n        \"svelte.config.mjs\"\n      ],\n      {\n        packageKey: \"svelte\"\n      }\n    )\n\n    let contents = {} as any\n    if (conf && typeof conf.contents === \"object\") {\n      contents = conf.contents\n      if (conf.filePath.endsWith(\".js\") || conf.filePath.endsWith(\".cjs\")) {\n        config.invalidateOnStartup()\n      }\n    }\n\n    const compilerOptions = contents.compilerOptions || contents.compiler || {}\n\n    return {\n      compilerOptions: {\n        dev: options.mode !== \"production\",\n        css: \"injected\",\n        ...compilerOptions\n      } as CompileOptions,\n      preprocess: contents.preprocess,\n      filePath: conf && conf.filePath\n    }\n  },\n\n  async transform({ asset, config, options, logger }) {\n    const [code, originalMap] = await Promise.all([\n      asset.getCode(),\n      asset.getMap()\n    ])\n\n    let finalCode = code\n\n    try {\n      // Retrieve the asset's source code and source map.\n      const filename = relativeUrl(\n        options.projectRoot,\n        asset.filePath\n      ) as string\n\n      const compilerOptions = {\n        filename,\n        ...(config.compilerOptions || {})\n      }\n\n      if (config.preprocess) {\n        const preprocessed = await preprocess(\n          code,\n          config.preprocess,\n          compilerOptions\n        )\n\n        if (preprocessed.map) compilerOptions.sourcemap = preprocessed.map\n        if (preprocessed.dependencies) {\n          for (const dependency of preprocessed.dependencies) {\n            asset.invalidateOnFileChange(dependency)\n          }\n        }\n        finalCode = preprocessed.code\n      }\n\n      const compiled = compile(finalCode, compilerOptions)\n\n      compiled.warnings?.forEach((warning) => {\n        if (compilerOptions.css && warning.code === \"css-unused-selector\")\n          return\n        logger.warn(convertError(asset, originalMap, finalCode, warning))\n      })\n\n      const results = [\n        {\n          type: \"js\",\n          content: compiled.js.code,\n          uniqueKey: `${asset.id}-js`,\n          map: extendSourceMap(\n            options,\n            asset.filePath,\n            originalMap,\n            compiled.js.map\n          )\n        }\n      ]\n      if (compiled.css && compiled.css.code) {\n        results.push({\n          type: \"css\",\n          content: compiled.css.code,\n          uniqueKey: `${asset.id}-css`,\n          map: extendSourceMap(\n            options,\n            asset.filePath,\n            originalMap,\n            compiled.css.map\n          )\n        })\n      }\n      return results\n    } catch (error) {\n      throw new ThrowableDiagnostic({\n        diagnostic: convertError(asset, originalMap, finalCode, error)\n      })\n    }\n  }\n})\n"
  },
  {
    "path": "core/parcel-transformer-svelte/src/source-map.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/HellButcher/parcel-transformer-svelte3-plus\n * Copyright (c) 2023 Christoph Hommelsheim\n * MIT License\n */\nimport { dirname, isAbsolute, join } from \"path\"\nimport SourceMap from \"@parcel/source-map\"\n\nimport type { Options } from \"./types\"\n\nexport function mapSourceMapPath(mapSourceRoot: string, sourcePath: string) {\n  if (sourcePath.startsWith(\"file://\")) {\n    sourcePath = sourcePath.substring(7)\n  }\n  if (isAbsolute(sourcePath)) {\n    return sourcePath\n  } else {\n    return join(mapSourceRoot, sourcePath)\n  }\n}\n\nexport function extendSourceMap(\n  options: Options,\n  filePath: string,\n  originalMap: SourceMap,\n  sourceMap: any\n): SourceMap | null {\n  if (!sourceMap) return originalMap\n  let mapSourceRoot = dirname(filePath)\n  let map = new SourceMap(options.projectRoot)\n  map.addVLQMap({\n    ...sourceMap,\n    sources: sourceMap.sources.map((s) => mapSourceMapPath(mapSourceRoot, s))\n  })\n\n  if (originalMap) {\n    map.extends(originalMap.toBuffer())\n  }\n  return map\n}\n"
  },
  {
    "path": "core/parcel-transformer-svelte/src/types.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n */\nimport type { Transformer } from \"@parcel/plugin\"\n\ntype Transform = ConstructorParameters<typeof Transformer>[0][\"transform\"]\n\nexport type MutableAsset = Parameters<Transform>[0][\"asset\"]\nexport type Options = Parameters<Transform>[0][\"options\"]\n"
  },
  {
    "path": "core/parcel-transformer-svelte/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "core/parcel-transformer-vue/.gitignore",
    "content": "node_modules\n\ndist/"
  },
  {
    "path": "core/parcel-transformer-vue/package.json",
    "content": "{\n  \"name\": \"@plasmohq/parcel-transformer-vue\",\n  \"version\": \"0.5.1\",\n  \"description\": \"Plasmo Parcel Transformer for Vue\",\n  \"files\": [\n    \"dist\"\n  ],\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"prepublishOnly\": \"pnpm build\",\n    \"build\": \"tsup src/index.ts --minify --clean\",\n    \"dev\": \"tsup src/index.ts --sourcemap --watch\"\n  },\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"engines\": {\n    \"parcel\": \">= 2.7.0\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"tsup\": \"8.4.0\"\n  },\n  \"dependencies\": {\n    \"@parcel/core\": \"2.9.3\",\n    \"@parcel/diagnostic\": \"2.9.3\",\n    \"@parcel/plugin\": \"2.9.3\",\n    \"@parcel/source-map\": \"2.1.1\",\n    \"@parcel/types\": \"2.9.3\",\n    \"@parcel/utils\": \"2.9.3\",\n    \"@plasmohq/consolidate\": \"0.17.0\",\n    \"@vue/compiler-sfc\": \"3.5.13\",\n    \"nullthrows\": \"1.1.1\",\n    \"semver\": \"7.7.1\",\n    \"vue\": \"3.5.13\"\n  }\n}\n"
  },
  {
    "path": "core/parcel-transformer-vue/src/index.ts",
    "content": "/**\n * Copyright (c) 2023 Plasmo Corp. <foss@plasmo.com> (https://www.plasmo.com) and contributors\n * MIT License\n *\n * Based on: https://github.com/parcel-bundler/parcel/tree/v2/packages/transformers/vue\n * MIT License\n */\nimport { basename, dirname, extname, relative } from \"path\"\nimport ThrowableDiagnostic, {\n  escapeMarkdown,\n  md,\n  type Diagnostic\n} from \"@parcel/diagnostic\"\nimport { Transformer } from \"@parcel/plugin\"\nimport SourceMap from \"@parcel/source-map\"\nimport type { TransformerResult } from \"@parcel/types\"\nimport { hashObject } from \"@parcel/utils\"\nimport * as compiler from \"@vue/compiler-sfc\"\nimport nullthrows from \"nullthrows\"\nimport semver from \"semver\"\n\nimport consolidate from \"@plasmohq/consolidate\"\n\nconst MODULE_BY_NAME_RE = /\\.module\\./\n\n// TODO: Use language-specific config files during preprocessing\nexport default new Transformer({\n  async loadConfig({ config }) {\n    let conf = await config.getConfig(\n      [\".vuerc\", \".vuerc.json\", \".vuerc.js\", \"vue.config.js\"],\n      { packageKey: \"vue\" }\n    )\n    let contents = {} as any\n    if (conf) {\n      config.invalidateOnStartup()\n      contents = conf.contents\n      if (typeof contents !== \"object\") {\n        // TODO: codeframe\n        throw new ThrowableDiagnostic({\n          diagnostic: {\n            message: \"Vue config should be an object.\",\n            origin: \"@parcel/transformer-vue\"\n          }\n        })\n      }\n    }\n    return {\n      customBlocks: contents.customBlocks || {},\n      filePath: conf && conf.filePath,\n      compilerOptions: contents.compilerOptions || {}\n    }\n  },\n  canReuseAST({ ast }) {\n    return ast.type === \"vue\" && semver.satisfies(ast.version, \"^3.0.0\")\n  },\n  async parse({ asset, options }) {\n    // TODO: This parses the vue component multiple times. Fix?\n    let code = await asset.getCode()\n    let parsed = compiler.parse(code, {\n      sourceMap: true,\n      filename: asset.filePath\n    })\n    if (parsed.errors.length) {\n      throw new ThrowableDiagnostic({\n        diagnostic: parsed.errors.map((err) => {\n          return createDiagnostic(err, asset.filePath)\n        })\n      })\n    }\n\n    const descriptor = parsed.descriptor\n    let id = hashObject({\n      filePath: asset.filePath,\n      source: options.mode === \"production\" ? code : null\n    }).slice(-6)\n\n    return {\n      type: \"vue\",\n      version: \"3.0.0\",\n      program: {\n        ...descriptor,\n        script:\n          descriptor.script != null || descriptor.scriptSetup != null\n            ? compiler.compileScript(descriptor, {\n                id,\n                isProd: options.mode === \"production\"\n              })\n            : null,\n        id\n      }\n    }\n  },\n  async transform({ asset, options, resolve, config }) {\n    let { template, script, styles, customBlocks, id } = nullthrows(\n      await asset.getAST()\n    ).program\n    let scopeId = \"data-v-\" + id\n    let hmrId = id + \"-hmr\"\n    let basePath = basename(asset.filePath)\n    if (asset.pipeline != null) {\n      return processPipeline({\n        asset,\n        template,\n        script,\n        styles,\n        customBlocks,\n        config,\n        basePath,\n        options,\n        resolve,\n        id,\n        hmrId\n      })\n    }\n    return [\n      {\n        type: \"js\",\n        uniqueKey: asset.id + \"-glue\",\n        content: `\n let script;\n let initialize = () => {\n   script = ${\n     script != null\n       ? `require('script:./${basePath}');\n   if (script.__esModule) script = script.default`\n       : \"{}\"\n   };\n   ${\n     template != null\n       ? `script.render = require('template:./${basePath}').render;`\n       : \"\"\n   }\n   ${\n     styles.length !== 0\n       ? `script.__cssModules = require('style:./${basePath}').default;`\n       : \"\"\n   }\n   ${\n     customBlocks != null\n       ? `require('custom:./${basePath}').default(script);`\n       : \"\"\n   }\n   script.__scopeId = '${scopeId}';\n   script.__file = ${JSON.stringify(\n     options.mode === \"production\" ? basePath : asset.filePath\n   )};\n };\n initialize();\n ${\n   options.hmrOptions\n     ? `if (module.hot) {\n   script.__hmrId = '${hmrId}';\n   module.hot.accept(() => {\n     setTimeout(() => {\n       initialize();\n       if (!__VUE_HMR_RUNTIME__.createRecord('${hmrId}', script)) {\n         __VUE_HMR_RUNTIME__.reload('${hmrId}', script);\n       }\n     }, 0);\n   });\n }`\n     : \"\"\n }\n export default script;`\n      }\n    ]\n  }\n})\n\nfunction createDiagnostic(err, filePath) {\n  if (typeof err === \"string\") {\n    return {\n      message: err,\n      origin: \"@parcel/transformer-vue\",\n      filePath\n    }\n  }\n  // TODO: codeframe\n  let diagnostic: Diagnostic = {\n    message: escapeMarkdown(err.message),\n    origin: \"@parcel/transformer-vue\",\n    name: err.name,\n    stack: err.stack\n  }\n  if (err.loc) {\n    diagnostic.codeFrames = [\n      {\n        codeHighlights: [\n          {\n            start: {\n              line: err.loc.start.line + err.loc.start.offset,\n              column: err.loc.start.column\n            },\n            end: {\n              line: err.loc.end.line + err.loc.end.offset,\n              column: err.loc.end.column\n            }\n          }\n        ]\n      }\n    ]\n  }\n  return diagnostic\n}\n\nasync function processPipeline({\n  asset,\n  template,\n  script,\n  styles,\n  customBlocks,\n  config,\n  basePath,\n  options,\n  resolve,\n  id,\n  hmrId\n}) {\n  switch (asset.pipeline) {\n    case \"template\": {\n      if (template.src) {\n        template.content = (\n          await options.inputFS.readFile(\n            await resolve(asset.filePath, template.src)\n          )\n        ).toString()\n        template.lang = extname(template.src).slice(1)\n      }\n      let content = template.content\n      if (template.lang && ![\"htm\", \"html\"].includes(template.lang)) {\n        let options = {} as any\n        let preprocessor = consolidate[template.lang]\n        // Pug doctype fix (fixes #7756)\n        switch (template.lang) {\n          case \"pug\":\n            options.doctype = \"html\"\n            break\n        }\n        if (!preprocessor) {\n          // TODO: codeframe\n          throw new ThrowableDiagnostic({\n            diagnostic: {\n              message: md([`Unknown template language: \"${template.lang}\"`]),\n              origin: \"@parcel/transformer-vue\"\n            }\n          })\n        }\n        content = await preprocessor.render(content, options)\n      }\n      let templateComp = compiler.compileTemplate({\n        filename: asset.filePath,\n        source: content,\n        inMap: template.src ? undefined : template.map,\n        scoped: styles.some((style) => style.scoped),\n        compilerOptions: {\n          ...config.compilerOptions,\n          bindingMetadata: script ? script.bindings : undefined\n        },\n        isProd: options.mode === \"production\",\n        id\n      })\n      if (templateComp.errors.length) {\n        throw new ThrowableDiagnostic({\n          diagnostic: templateComp.errors.map((err) => {\n            return createDiagnostic(err, asset.filePath)\n          })\n        })\n      }\n      let templateAsset: TransformerResult = {\n        type: \"js\",\n        uniqueKey: asset.id + \"-template\",\n        ...(!template.src &&\n          asset.env.sourceMap && {\n            map: createMap(templateComp.map, options.projectRoot)\n          }),\n        content:\n          templateComp.code +\n          `\n ${\n   options.hmrOptions\n     ? `if (module.hot) {\n   module.hot.accept(() => {\n     __VUE_HMR_RUNTIME__.rerender('${hmrId}', render);\n   })\n }`\n     : \"\"\n }`\n      }\n      return [templateAsset]\n    }\n    case \"script\": {\n      if (script.src) {\n        script.content = (\n          await options.inputFS.readFile(\n            await resolve(asset.filePath, script.src)\n          )\n        ).toString()\n        script.lang = extname(script.src).slice(1)\n      }\n      let type\n      switch (script.lang || \"js\") {\n        case \"javascript\":\n        case \"js\":\n          type = \"js\"\n          break\n        case \"jsx\":\n          type = \"jsx\"\n          break\n        case \"typescript\":\n        case \"ts\":\n          type = \"ts\"\n          break\n        case \"tsx\":\n          type = \"tsx\"\n          break\n        case \"coffeescript\":\n        case \"coffee\":\n          type = \"coffee\"\n          break\n        default:\n          // TODO: codeframe\n          throw new ThrowableDiagnostic({\n            diagnostic: {\n              message: md([`Unknown script language: \"${script.lang}\"`]),\n              origin: \"@parcel/transformer-vue\"\n            }\n          })\n      }\n      let scriptAsset = {\n        type,\n        uniqueKey: asset.id + \"-script\",\n        content: script.content,\n        ...(!script.src &&\n          asset.env.sourceMap && {\n            map: createMap(script.map, options.projectRoot)\n          })\n      }\n\n      return [scriptAsset]\n    }\n    case \"style\":\n    case \"style-raw\": {\n      let cssModules = {}\n      let assets = await Promise.all(\n        styles.map(async (style, i) => {\n          if (style.src) {\n            style.content = (\n              await options.inputFS.readFile(\n                await resolve(asset.filePath, style.src)\n              )\n            ).toString()\n            if (!style.module) {\n              style.module = MODULE_BY_NAME_RE.test(style.src)\n            }\n            style.lang = extname(style.src).slice(1)\n          }\n          switch (style.lang) {\n            case \"less\":\n            case \"stylus\":\n            case \"styl\":\n            case \"scss\":\n            case \"sass\":\n            case \"css\":\n            case undefined:\n              break\n            default:\n              // TODO: codeframe\n              throw new ThrowableDiagnostic({\n                diagnostic: {\n                  message: md([`Unknown style language: \"${style.lang}\"`]),\n                  origin: \"@parcel/transformer-vue\"\n                }\n              })\n          }\n          let styleComp = await compiler.compileStyleAsync({\n            filename: asset.filePath,\n            source: style.content,\n            modules: style.module,\n            preprocessLang: style.lang || \"css\",\n            scoped: style.scoped,\n            inMap: style.src ? undefined : style.map,\n            isProd: options.mode === \"production\",\n            id\n          })\n          if (styleComp.errors.length) {\n            throw new ThrowableDiagnostic({\n              diagnostic: styleComp.errors.map((err) => {\n                return createDiagnostic(err, asset.filePath)\n              })\n            })\n          }\n          let styleAsset = {\n            type: \"css\",\n            content: styleComp.code,\n            sideEffects: true,\n            ...(!style.src &&\n              asset.env.sourceMap && {\n                map: createMap(style.map, options.projectRoot)\n              }),\n            uniqueKey: asset.id + \"-style\" + i\n          }\n          if (styleComp.modules) {\n            if (typeof style.module === \"boolean\") style.module = \"$style\"\n            cssModules[style.module] = {\n              ...cssModules[style.module],\n              ...styleComp.modules\n            }\n          }\n          return styleAsset\n        })\n      )\n      if (asset.pipeline == \"style\") {\n        if (Object.keys(cssModules).length !== 0) {\n          assets.push({\n            type: \"js\",\n            uniqueKey: asset.id + \"-cssModules\",\n            content: `\n import {render} from 'template:./${basePath}';\n let cssModules = ${JSON.stringify(cssModules)};\n ${\n   options.hmrOptions\n     ? `if (module.hot) {\n   module.hot.accept(() => {\n     __VUE_HMR_RUNTIME__.rerender('${hmrId}', render);\n   });\n };`\n     : \"\"\n }\n export default cssModules;`\n          })\n        }\n        return assets\n      } else if (asset.pipeline == \"style-raw\") {\n        const styleRawString = assets.map((a) => a.content).join(\"\\n\")\n        return [\n          {\n            type: \"js\",\n            uniqueKey: asset.id + \"-cssRawString\",\n            content: `\nconst styleRawString = \\`${styleRawString}\\`\nexport default styleRawString\n          `\n          }\n        ]\n      }\n    }\n    case \"custom\": {\n      let toCall = []\n      // To satisfy flow\n      if (!config) return []\n      const types = new Set()\n      for (let block of customBlocks) {\n        let { type, src, content, attrs } = block\n        if (!config.customBlocks[type]) {\n          // TODO: codeframe\n          throw new ThrowableDiagnostic({\n            diagnostic: {\n              message: md([`No preprocessor found for block type ${type}`]),\n              origin: \"@parcel/transformer-vue\"\n            }\n          })\n        }\n        if (src) {\n          content = (\n            await options.inputFS.readFile(await resolve(asset.filePath, src))\n          ).toString()\n        }\n        toCall.push([type, content, attrs])\n        types.add(type)\n      }\n      return [\n        {\n          type: \"js\",\n          uniqueKey: asset.id + \"-custom\",\n          content: `\n let NOOP = () => {};\n ${(\n   await Promise.all(\n     [...types].map(\n       async (type: any) =>\n         `import p${type} from './${relative(\n           dirname(asset.filePath),\n           await resolve(nullthrows(config.filePath), config.customBlocks[type])\n         )}';\n if (typeof p${type} !== 'function') {\n   p${type} = NOOP;\n }`\n     )\n   )\n ).join(\"\\n\")}\n export default script => {\n   ${toCall\n     .map(\n       ([type, content, attrs]) =>\n         `  p${type}(script, ${JSON.stringify(content)}, ${JSON.stringify(\n           attrs\n         )});`\n     )\n     .join(\"\\n\")}\n }`\n        }\n      ]\n    }\n    default: {\n      return []\n    }\n  }\n}\n\nfunction createMap(rawMap, projectRoot: string) {\n  let newMap = new SourceMap(projectRoot)\n  newMap.addVLQMap(rawMap)\n  return newMap\n}\n"
  },
  {
    "path": "core/parcel-transformer-vue/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/cli\",\n  \"include\": [\"src/**/*.ts\"],\n  \"exclude\": [\"dist\", \"node_modules\"]\n}\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import globals from \"globals\";\nimport tseslint from \"typescript-eslint\";\n\n\n/** @type {import('eslint').Linter.Config[]} */\nexport default [\n  {files: [\"**/*.{js,mjs,cjs,ts}\"]},\n  {languageOptions: { globals: {...globals.browser, ...globals.node} }},\n  ...tseslint.configs.recommended,\n];"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"p1asm0\",\n  \"private\": true,\n  \"workspaces\": [\n    \"cli/*\",\n    \"api/*\",\n    \"core/*\",\n    \"packages/*\",\n    \"examples/*\"\n  ],\n  \"scripts\": {\n    \"dev:cli\": \"turbo run dev --filter=plasmo\",\n    \"build\": \"turbo run build\",\n    \"build:cli\": \"turbo run build --filter=plasmo\",\n    \"build:packages\": \"turbo run build --filter \\\"./packages/**\\\"\",\n    \"build:api\": \"turbo run build --filter \\\"./api/**\\\"\",\n    \"build:core\": \"turbo run build --filter \\\"./core/**\\\"\",\n    \"build:examples\": \"pnpm --filter \\\"./examples/**\\\" -r build\",\n    \"test:examples\": \"pnpm --filter \\\"./examples/**\\\" -r test\",\n    \"publish:packages\": \"pnpm --filter \\\"./packages/**\\\" publish\",\n    \"publish:api\": \"pnpm --filter \\\"./api/**\\\" publish\",\n    \"publish:core\": \"pnpm --filter \\\"./core/**\\\" publish\",\n    \"publish:cli\": \"pnpm --filter \\\"./cli/*\\\" publish\",\n    \"publish:cli:lab\": \"pnpm --filter \\\"./cli/*\\\" publish --no-git-checks --tag lab\",\n    \"publish:lab\": \"run-s publish:packages publish:cli:lab\",\n    \"format\": \"prettier --write \\\"**/*.{ts,tsx,md,mjs}\\\"\",\n    \"### version script usage example\": \"pnpm v:cli patch\",\n    \"v:packages\": \"pnpm --filter \\\"./packages/**\\\" --parallel -r exec pnpm version --commit-hooks false --git-tag-version false --workspaces-update\",\n    \"v:core\": \"pnpm --filter \\\"./core/**\\\" --parallel -r exec pnpm version --commit-hooks false --git-tag-version false --workspaces-update\",\n    \"v:api\": \"pnpm --filter \\\"./api/**\\\" --parallel -r exec pnpm version --commit-hooks false --git-tag-version false --workspaces-update\",\n    \"v:cli\": \"pnpm --filter \\\"./cli/**\\\" --parallel -r exec pnpm version --commit-hooks false --git-tag-version false --workspaces-update\"\n  },\n  \"pnpm\": {\n    \"overrides\": {\n      \"@parcel/source-map\": \"2.1.1\",\n      \"react-refresh\": \"0.16.0\"\n    },\n    \"onlyBuiltDependencies\": [\n      \"@parcel/watcher\",\n      \"@swc/core\",\n      \"esbuild\",\n      \"svelte-preprocess\",\n      \"canvas\"\n    ]\n  },\n  \"devDependencies\": {\n    \"@ianvs/prettier-plugin-sort-imports\": \"4.4.1\",\n    \"@plasmohq/rps\": \"workspace:*\",\n    \"@types/fs-extra\": \"11.0.4\",\n    \"@types/inquirer\": \"9.0.7\",\n    \"@types/node\": \"22.13.13\",\n    \"@types/node-rsa\": \"1.1.4\",\n    \"@types/react\": \"19.0.12\",\n    \"@types/react-dom\": \"19.0.4\",\n    \"@types/semver\": \"7.7.0\",\n    \"@types/uuid\": \"10.0.0\",\n    \"@types/ws\": \"8.18.0\",\n    \"esbuild\": \"0.25.1\",\n    \"eslint\": \"9.23.0\",\n    \"eslint-config-prettier\": \"10.1.1\",\n    \"eslint-plugin-react\": \"7.37.4\",\n    \"fs-extra\": \"11.3.0\",\n    \"globals\": \"16.0.0\",\n    \"prettier\": \"3.5.3\",\n    \"tsup\": \"8.4.0\",\n    \"turbo\": \"2.4.4\",\n    \"typescript-eslint\": \"8.28.0\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  },\n  \"packageManager\": \"pnpm@10.11.0\"\n}\n"
  },
  {
    "path": "packages/framework-shared/build-socket/event.ts",
    "content": "export enum BuildSocketEvent {\n  BuildReady = \"build_ready\",\n  CsChanged = \"cs_changed\"\n}\n"
  },
  {
    "path": "packages/framework-shared/build-socket/index.ts",
    "content": "import { WebSocket, WebSocketServer } from \"ws\"\n\nimport { BuildSocketEvent } from \"./event\"\n\nexport { BuildSocketEvent }\n\nconst createBuildSocket = (hmrHost: string, hmrPort: number) => {\n  const wss = new WebSocketServer({\n    host: hmrHost,\n    port: hmrPort + 1\n  })\n\n  const broadcast = (type: BuildSocketEvent) => {\n    for (const client of wss.clients) {\n      if (client.readyState === WebSocket.OPEN) {\n        client.send(JSON.stringify({ type }))\n      }\n    }\n  }\n\n  return {\n    broadcast\n  }\n}\n\nlet _buildSocket: Awaited<ReturnType<typeof createBuildSocket>>\n\nexport const getBuildSocket = (hmrHost = \"localhost\", hmrPort?: number) => {\n  if (process.env.NODE_ENV === \"production\") {\n    return null\n  }\n\n  if (!!_buildSocket) {\n    return _buildSocket\n  }\n\n  if (!hmrPort) {\n    throw new Error(\"HMR port is not provided\")\n  }\n\n  _buildSocket = createBuildSocket(hmrHost, hmrPort)\n  return _buildSocket\n}\n\nexport const buildBroadcast = (type: BuildSocketEvent) => {\n  if (process.env.NODE_ENV === \"production\") {\n    return\n  }\n\n  const buildSocket = getBuildSocket()\n  if (buildSocket) {\n    buildSocket.broadcast(type)\n  }\n}\n"
  },
  {
    "path": "packages/framework-shared/package.json",
    "content": "{\n  \"name\": \"@plasmo/framework-shared\",\n  \"version\": \"0.0.2\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"dependencies\": {\n    \"ws\": \"8.18.1\"\n  },\n  \"devDependencies\": {\n    \"@plasmo/config\": \"workspace:*\",\n    \"@plasmo/utils\": \"workspace:*\"\n  }\n}\n"
  },
  {
    "path": "packages/framework-shared/tsconfig.json",
    "content": "{\n  \"extends\": \"@plasmo/config/ts/utils\",\n  \"include\": [\"./**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/init/.gitignore",
    "content": "node_modules"
  },
  {
    "path": "packages/init/bpp.yml",
    "content": "name: \"Submit to Web Store\"\non:\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - name: Cache pnpm modules\n        uses: actions/cache@v3\n        with:\n          path: ~/.pnpm-store\n          key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}\n          restore-keys: |\n            ${{ runner.os }}-\n      - uses: pnpm/action-setup@v2.4.1\n        with:\n          version: latest\n          run_install: true\n      - name: Use Node.js 20.x\n        uses: actions/setup-node@v3.5.1\n        with:\n          node-version: 20.x\n          cache: \"pnpm\"\n      - name: Build the extension\n        run: pnpm build\n      - name: Package the extension into a zip artifact\n        run: pnpm package\n      - name: Browser Platform Publish\n        uses: PlasmoHQ/bpp@v3\n        with:\n          keys: ${{ secrets.SUBMIT_KEYS }}\n          artifact: build/chrome-mv3-prod.zip\n"
  },
  {
    "path": "packages/init/entries/background.ts",
    "content": "export {}\n\nconsole.log(\"Hello from background script!\")\n"
  },
  {
    "path": "packages/init/entries/content.ts",
    "content": "import type { PlasmoCSConfig } from \"plasmo\"\n\nexport const config: PlasmoCSConfig = {\n  matches: [\"https://www.plasmo.com/*\"]\n}\n\nwindow.addEventListener(\"load\", () => {\n  console.log(\"content script loaded\")\n\n  document.body.style.background = \"pink\"\n})\n"
  },
  {
    "path": "packages/init/entries/contents/inline.tsx",
    "content": "import type { PlasmoCSConfig, PlasmoGetInlineAnchor } from \"plasmo\"\n\nexport const config: PlasmoCSConfig = {\n  matches: [\"https://www.plasmo.com/*\"]\n}\n\nexport const getInlineAnchor: PlasmoGetInlineAnchor = () =>\n  document.querySelector(\"#supercharge > h2 > span\")\n\n// Use this to optimize unmount lookups\nexport const getShadowHostId = () => \"plasmo-inline-example-unique-id\"\n\nconst PlasmoInline = () => {\n  return <button>Custom button</button>\n}\n\nexport default PlasmoInline\n"
  },
  {
    "path": "packages/init/entries/contents/overlay.tsx",
    "content": "import type { PlasmoCSConfig, PlasmoGetOverlayAnchor } from \"plasmo\"\n\nexport const config: PlasmoCSConfig = {\n  matches: [\"https://www.plasmo.com/*\"]\n}\n\nexport const getOverlayAnchor: PlasmoGetOverlayAnchor = async () =>\n  document.querySelector<HTMLElement>(\"#pricing\")\n\nconst PlasmoPricingExtra = () => {\n  return (\n    <span\n      style={{\n        background: \"white\",\n        padding: 12\n      }}>\n      HELLO WORLD\n    </span>\n  )\n}\n\nexport default PlasmoPricingExtra\n"
  },
  {
    "path": "packages/init/entries/newtab.tsx",
    "content": "import { useState } from \"react\"\n\nfunction IndexNewtab() {\n  const [data, setData] = useState(\"\")\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        padding: 16\n      }}>\n      <h2>\n        Welcome to your{\" \"}\n        <a href=\"https://www.plasmo.com\" target=\"_blank\">\n          Plasmo\n        </a>{\" \"}\n        Extension!\n      </h2>\n      <input onChange={(e) => setData(e.target.value)} value={data} />\n      <a href=\"https://docs.plasmo.com\" target=\"_blank\">\n        View Docs\n      </a>\n    </div>\n  )\n}\n\nexport default IndexNewtab\n"
  },
  {
    "path": "packages/init/entries/options.tsx",
    "content": "import { useState } from \"react\"\n\nfunction IndexOptions() {\n  const [data, setData] = useState(\"\")\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        padding: 16\n      }}>\n      <h2>\n        Welcome to your{\" \"}\n        <a href=\"https://www.plasmo.com\" target=\"_blank\">\n          Plasmo\n        </a>{\" \"}\n        Extension!\n      </h2>\n      <input onChange={(e) => setData(e.target.value)} value={data} />\n      <a href=\"https://docs.plasmo.com\" target=\"_blank\">\n        View Docs\n      </a>\n    </div>\n  )\n}\n\nexport default IndexOptions\n"
  },
  {
    "path": "packages/init/entries/popup.tsx",
    "content": "import { useState } from \"react\"\n\nfunction IndexPopup() {\n  const [data, setData] = useState(\"\")\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        flexDirection: \"column\",\n        padding: 16\n      }}>\n      <h2>\n        Welcome to your{\" \"}\n        <a href=\"https://www.plasmo.com\" target=\"_blank\">\n          Plasmo\n        </a>{\" \"}\n        Extension!\n      </h2>\n      <input onChange={(e) => setData(e.target.value)} value={data} />\n      <a href=\"https://docs.plasmo.com\" target=\"_blank\">\n        View Docs\n      </a>\n    </div>\n  )\n}\n\nexport default IndexPopup\n"
  },
  {
    "path": "packages/init/index.json",
    "content": "{}\n"
  },
  {
    "path": "packages/init/package.json",
    "content": "{\n  \"name\": \"@plasmohq/init\",\n  \"version\": \"0.7.0\",\n  \"description\": \"Plasmo init template files\",\n  \"files\": [\n    \"entries\",\n    \"templates\",\n    \"bpp.yml\"\n  ],\n  \"main\": \"index.json\",\n  \"author\": \"Plasmo Corp. <foss@plasmo.com>\",\n  \"homepage\": \"https://docs.plasmo.com/\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/PlasmoHQ/plasmo.git\"\n  }\n}\n"
  },
  {
    "path": "packages/init/templates/README.md",
    "content": "This is a [Plasmo extension](https://docs.plasmo.com/) project bootstrapped with [`plasmo init`](https://www.npmjs.com/package/plasmo).\n\n## Getting Started\n\nFirst, run the development server:\n\n```bash\npnpm dev\n# or\nnpm run dev\n```\n\nOpen your browser and load the appropriate development build. For example, if you are developing for the chrome browser, using manifest v3, use: `build/chrome-mv3-dev`.\n\nYou can start editing the popup by modifying `popup.tsx`. It should auto-update as you make changes. To add an options page, simply add a `options.tsx` file to the root of the project, with a react component default exported. Likewise to add a content page, add a `content.ts` file to the root of the project, importing some module and do some logic, then reload the extension on your browser.\n\nFor further guidance, [visit our Documentation](https://docs.plasmo.com/)\n\n## Making production build\n\nRun the following:\n\n```bash\npnpm build\n# or\nnpm run build\n```\n\nThis should create a production bundle for your extension, ready to be zipped and published to the stores.\n\n## Submit to the webstores\n\nThe easiest way to deploy your Plasmo extension is to use the built-in [bpp](https://github.com/marketplace/actions/browser-platform-publisher) GitHub action. Prior to using this action however, make sure to build your extension and upload the first version to the store to establish the basic credentials. Then, simply follow [this setup instruction](https://docs.plasmo.com/framework/workflows/submit) and you should be on your way for automated submission!\n"
  },
  {
    "path": "packages/init/templates/tsconfig.json",
    "content": "{\n  \"extends\": \"plasmo/templates/tsconfig.base\",\n  \"exclude\": [\"node_modules\"],\n  \"include\": [\".plasmo/index.d.ts\", \"./**/*.ts\", \"./**/*.tsx\"],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"~*\": [\"./*\"]\n    },\n    \"baseUrl\": \".\"\n  }\n}\n"
  },
  {
    "path": "packages/init/tsconfig.json",
    "content": "{\n  \"include\": [\"./**/*.ts\", \"./**/*.tsx\"],\n  \"compilerOptions\": {\n    \"strict\": false,\n    \"jsx\": \"react-jsx\",\n    \"paths\": {\n      \"plasmo\": [\"../../cli/plasmo/src/type.ts\"]\n    }\n  }\n}\n"
  },
  {
    "path": "pnpm-workspace.yaml",
    "content": "packages:\n  - \"cli/*\"\n  - \"packages/*\"\n  - \"examples/*\"\n  - \"api/*\"\n  - \"core/*\"\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\",\n    \"group:allNonMajor\"\n  ],\n  \"git-submodules\": {\n    \"enabled\": true\n  },\n  \"cloneSubmodules\": true\n}"
  },
  {
    "path": "scripts/move-prettier-cjs-to-mjs.bash",
    "content": "#!/usr/bin/env bash\n\n# a bash script that traverse the examples directory and mv all prettier cjs files to mjs\n\ndir=\"examples\"\n\n# Use a for loop to traverse the directory\nfor subdir in $(find $dir -type d); do\n    # Check if the file exists\n    if [ -f \"$subdir/.prettierrc.cjs\" ]; then\n        # If the file exists, rename it\n        mv \"$subdir/.prettierrc.cjs\" \"$subdir/.prettierrc.mjs\"\n        echo \"Renamed .prettierrc.cjs to .prettierrc.mjs in directory $subdir\"\n    fi\ndone\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turborepo.org/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\n        \"^build\"\n      ],\n      \"outputs\": [\n        \"dist/**\"\n      ]\n    },\n    \"publish\": {\n      \"dependsOn\": [\n        \"^build\"\n      ],\n      \"outputs\": [\n        \"dist/**\"\n      ]\n    },\n    \"lint\": {\n      \"outputs\": []\n    },\n    \"dev\": {\n      \"dependsOn\": [\n        \"^build\"\n      ],\n      \"cache\": false\n    },\n    \"clean\": {\n      \"dependsOn\": [\n        \"^clean\"\n      ],\n      \"cache\": false\n    }\n  }\n}\n"
  }
]