[
  {
    "path": ".formatter.exs",
    "content": "locals_without_parens = [\n  # Phoenix.Channel\n  intercept: 1,\n\n  # Phoenix.Router\n  connect: 3,\n  connect: 4,\n  delete: 3,\n  delete: 4,\n  forward: 2,\n  forward: 3,\n  forward: 4,\n  get: 3,\n  get: 4,\n  head: 3,\n  head: 4,\n  match: 4,\n  match: 5,\n  options: 3,\n  options: 4,\n  patch: 3,\n  patch: 4,\n  pipeline: 2,\n  pipe_through: 1,\n  post: 3,\n  post: 4,\n  put: 3,\n  put: 4,\n  resources: 2,\n  resources: 3,\n  resources: 4,\n  trace: 4,\n\n  # Phoenix.Controller\n  action_fallback: 1,\n\n  # Phoenix.Endpoint\n  plug: 1,\n  plug: 2,\n  socket: 2,\n  socket: 3,\n\n  # Phoenix.Socket\n  channel: 2,\n  channel: 3,\n\n  # Phoenix.ChannelTest\n  assert_broadcast: 2,\n  assert_broadcast: 3,\n  assert_push: 2,\n  assert_push: 3,\n  assert_reply: 2,\n  assert_reply: 3,\n  assert_reply: 4,\n  refute_broadcast: 2,\n  refute_broadcast: 3,\n  refute_push: 2,\n  refute_push: 3,\n  refute_reply: 2,\n  refute_reply: 3,\n  refute_reply: 4,\n\n  # Phoenix.ConnTest\n  assert_error_sent: 2,\n\n  # Phoenix.Live{Dashboard,View}\n  attr: 2,\n  attr: 3,\n  embed_templates: 1,\n  embed_templates: 2,\n  live: 2,\n  live: 3,\n  live: 4,\n  live_dashboard: 1,\n  live_dashboard: 2,\n  on_mount: 1,\n  slot: 1,\n  slot: 2,\n  slot: 3,\n\n  # Phoenix.LiveViewTest\n  assert_patch: 1,\n  assert_patch: 2,\n  assert_patch: 3,\n  assert_patched: 2,\n  assert_push_event: 3,\n  assert_push_event: 4,\n  assert_redirect: 1,\n  assert_redirect: 2,\n  assert_redirect: 3,\n  assert_redirected: 2,\n  assert_reply: 2,\n  assert_reply: 3,\n  refute_redirected: 1,\n  refute_redirected: 2,\n  refute_patched: 1,\n  refute_patched: 2,\n  refute_push_event: 3,\n  refute_push_event: 4\n]\n\n[\n  locals_without_parens: locals_without_parens,\n  export: [locals_without_parens: locals_without_parens]\n]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n### Environment\n\n* Elixir version (elixir -v):\n* Phoenix version (mix deps):\n* Operating system:\n\n### Actual behavior\n\n<!--\nDescribe the actual behaviour. If you are seeing an error, include the full message and stacktrace.\n\nIf possible, also provide a single file app that reproduces the behaviour, you can start here:\nhttps://github.com/wojtekmach/mix_install_examples/blob/main/phoenix.exs\n-->\n\n### Expected behavior\n\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "---\nblank_issues_enabled: true\n\ncontact_links:\n  - name: Ask questions, support, and general discussions\n    url: https://elixirforum.com/c/phoenix-forum\n    about: Ask questions, provide support, and more on Elixir Forum\n\n  - name: Propose new features\n    url: https://elixirforum.com/c/phoenix-forum\n    about: Propose new features on Elixir Forum\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n  # Maintain dependencies for NPM\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    schedule:\n      interval: \"monthly\"\n    groups:\n      minor-and-patch:\n        applies-to: version-updates\n        update-types:\n          - \"minor\"\n          - \"patch\"\n"
  },
  {
    "path": ".github/workflows/assets.yml",
    "content": "name: Assets\n\non:\n  push:\n    branches:\n      - main\n      - \"v*.*\"\n\npermissions:\n  contents: read\n\njobs:\n  build:\n    runs-on: ubuntu-24.04\n    env:\n      elixir: 1.18.3\n      otp: 27.2\n    permissions:\n      contents: write # for stefanzweifel/git-auto-commit-action to push code in repo\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Set up Elixir\n        uses: erlef/setup-beam@3580539ceec3dc05b0ed51e9e10b08eb7a7c2bb4 # v1.21.0\n        with:\n          elixir-version: ${{ env.elixir }}\n          otp-version: ${{ env.otp }}\n\n      - name: Restore deps and _build cache\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: |\n            deps\n            _build\n          key: ${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-${{ hashFiles('**/mix.lock') }}-dev\n          restore-keys: |\n            ${{ runner.os }}-mix-${{ env.elixir }}-${{ env.otp }}-\n      - name: Install Dependencies\n        run: mix deps.get --only dev\n\n      - name: Set up Node.js 20.x\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0\n        with:\n          node-version: 20.x\n\n      - name: Restore npm cache\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: Install npm dependencies\n        run: npm ci\n\n      - name: Build assets\n        run: mix assets.build\n\n      - name: Push updated assets\n        id: push_assets\n        uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0\n        with:\n          commit_message: Update assets\n          file_pattern: priv/static\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non: [push, pull_request]\npermissions:\n  contents: read\n\njobs:\n  mix_test:\n    name: mix test (OTP ${{matrix.otp}} | Elixir ${{matrix.elixir}})\n\n    env:\n      MIX_ENV: test\n      PHX_CI: true\n\n    strategy:\n      matrix:\n        include:\n          - elixir: \"1.15.8\"\n            otp: \"25.3.2.9\"\n\n          - elixir: \"1.18.4\"\n            otp: \"27.3\"\n\n          - elixir: \"1.19.5\"\n            otp: \"28.3.3\"\n            lint: true\n            installer: true\n\n    runs-on: ubuntu-24.04\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Set up Elixir\n        uses: erlef/setup-beam@3580539ceec3dc05b0ed51e9e10b08eb7a7c2bb4 # v1.21.0\n        with:\n          elixir-version: ${{ matrix.elixir }}\n          otp-version: ${{ matrix.otp }}\n\n      - name: Restore deps and _build cache\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: |\n            deps\n            _build\n          key: deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}\n          restore-keys: |\n            deps-${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}\n\n      - name: Install dependencies\n        run: mix deps.get --only test\n\n      - name: Remove compiled application files\n        run: mix clean\n\n      - name: Compile & lint dependencies\n        run: mix compile --warnings-as-errors\n        if: ${{ matrix.lint }}\n\n      - name: Run tests\n        run: mix test\n\n      - name: Run installer test\n        run: |\n          cd installer\n          mix deps.get\n          mix test\n        if: ${{ matrix.installer }}\n\n  npm_test:\n    name: npm test\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n\n      - name: Restore deps and _build cache\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: |\n            deps\n            _build\n          key: deps-${{ runner.os }}-npm-${{ hashFiles('**/mix.lock') }}\n          restore-keys: |\n            deps-${{ runner.os }}-npm\n\n      - name: Set up Node.js 20.x\n        uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0\n        with:\n          node-version: 20.x\n\n      - name: Restore npm cache\n        uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3\n        with:\n          path: ~/.npm\n          key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}\n          restore-keys: |\n            ${{ runner.os }}-node-\n\n      - name: npm install and test\n        run: |\n          cd assets\n          npm install\n          npm test\n\n  integration-test-elixir:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        include:\n          # look for correct alpine image here: https://hub.docker.com/r/hexpm/elixir/tags\n          - elixir: \"1.15.8\"\n            otp: \"25.3.2.9\"\n            suffix: \"alpine-3.20.3\"\n\n          - elixir: \"1.19.5\"\n            otp: \"28.3.3\"\n            suffix: \"alpine-3.20.9\"\n\n    container:\n      image: hexpm/elixir:${{ matrix.elixir }}-erlang-${{ matrix.otp }}-${{ matrix.suffix }}\n      env:\n        ELIXIR_ASSERT_TIMEOUT: 10000\n        PHX_CI: true\n    services:\n      postgres:\n        image: postgres\n        ports:\n          - 5432:5432\n        env:\n          POSTGRES_PASSWORD: postgres\n      mysql:\n        image: mysql\n        ports:\n          - 3306:3306\n        env:\n          MYSQL_ALLOW_EMPTY_PASSWORD: \"yes\"\n      mssql:\n        image: mcr.microsoft.com/mssql/server:2019-latest\n        env:\n          ACCEPT_EULA: Y\n          SA_PASSWORD: some!Password\n        ports:\n          - 1433:1433\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Run test script\n        run: ./integration_test/test.sh\n"
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "content": "# https://docs.npmjs.com/trusted-publishers\nname: NPM Publish\n\non:\n  push:\n    tags:\n      - \"v*\"\n\npermissions:\n  id-token: write  # Required for OIDC\n  contents: read\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: \"24\"\n          registry-url: \"https://registry.npmjs.org\"\n\n      # Ensure npm 11.5.1 or later is installed\n      - name: Update npm\n        run: npm install -g npm@latest\n\n      - name: Determine npm tag\n        id: npm-tag\n        run: |\n          TAG=${GITHUB_REF#refs/tags/}\n          # Update this condition when bumping the major version!\n          if [[ $TAG == v1.7.* ]]; then\n            echo \"tag=old-version\" >> $GITHUB_OUTPUT\n          elif [[ $TAG == *-rc* ]]; then\n            echo \"tag=rc\" >> $GITHUB_OUTPUT\n          else\n            echo \"tag=latest\" >> $GITHUB_OUTPUT\n          fi\n\n      - name: Publish to npm\n        run: npm publish --tag ${{ steps.npm-tag.outputs.tag }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/_build/\n/deps/\n/doc/\n/node_modules/\n/tmp/\n/cover/\n\n/assets/node_modules/\n\n/installer/_build/\n/installer/assets/\n/installer/deps/\n/installer/doc/\n/installer/phx_new-*.ez\n/installer/tmp/\n\n/integration_test/_build/\n/integration_test/deps/\n\nerl_crash.dump\nphoenix-*.ez\n\n.DS_Store\n\n/priv/templates/phx.gen.live/core_components.ex.eex\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog for v1.8\n\nThis release requires Erlang/OTP 25+.\n\n## Streamlined generators\n\n  * Extend tailwindcss support in new apps with [daisyUI](https://daisyui.com/) for light/dark/system mode support for entire app, including core components\n  * Simplify layout handling for new apps. Now there is only a single `root.html.heex` which wraps the render pipeline. Other dynamic layouts, like `app.html.heex` are called as needed within templates as regular function components\n  * Simplify core components and live generators to more closely match basic `phx.gen.html` crud. This serves as a better base for seasoned devs to start with, and lessens the amount of code newcomers need to get up to speed with on the basics\n  * Introduce magic links (passwordless auth) and \"sudo mode\" to `mix phx.gen.auth` while simplifying the generated structure\n  * Introduce scopes to Phoenix generators, designed to make secure data access the *default*, not something you remember (or forget) to do later\n\n## `put_secure_browser_headers`\n\n`put_secure_browser_headers` has been updated to the latest security practices. In particular, it sets the `content-security-policy` header to `\"base-uri 'self'; frame-ancestors 'self';\"` if none is set, restricting embedding of your application and the use of `<base>` element to same origin respectively. If you expect your application to be embedded by third-parties, you want to consult the documentation.\n\nThe headers `x-download-options` and `x-frame-options` are no longer set as they have been deprecated by standards.\n\n## Deprecations\n\nThis release introduces deprecation warnings for several features that have been soft-deprecated in the past.\n\n  * `use Phoenix.Controller` must now specify the `:formats` option, which may be set to an empty list if the formats are not known upfront\n  * The `:namespace` and `:put_default_views` options on `use Phoenix.Controller` are deprecated and emit a warning on use\n  * Specifying layouts without modules, such as `put_layout(conn, :print)` or `put_layout(conn, html: :print)` is deprecated\n  * The `:trailing_slash` option in `Phoenix.Router` has been deprecated in favor of using `Phoenix.VerifiedRoutes`. The overall usage of helpers will be deprecated in the future\n\n## Potential breaking changes\n\n  * The `config` variable is no longer available in `Phoenix.Endpoint`. In the past, it was possible to read your endpoint configuration at compile-time via an injected variable named `config`, which is no longer supported. Use `Application.compile_env/3` instead, which is tracked by the Elixir compiler and lead to a better developer experience. This may also lead to errors on application boot if you were previously incorrectly setting compile time config at runtime.\n\n## 1.8.5 (2026-03-05)\n\n### JavaScript Client Bug Fixes\n- Fix socket connecting on visibility change when never established\n\n### Enhancements\n- Fix warnings on Elixir 1.20\n\n## 1.8.4 (2026-02-23)\n\n### JavaScript Client Bug Fixes\n- Fix bug reconnecting connections when close was gracefully initiated by server\n- Fix LongPoll transport name in sessionStorage and logs\n\n### Enhancements\n- Adds guards support in `assert_push`, `assert_broadcast`, and `assert_reply`\n- Enable purging in Phoenix code server for Elixir 1.20\n\n## 1.8.3 (2025-12-08)\n\n### Enhancements\n  - Add top-level phoenix config: `sort_verified_routes_query_params` to enable sorting query params in verified routes during tests\n\n### Bug fixes\n  - Fix endpoint port config in an umbrella application. ([#6549](https://github.com/phoenixframework/phoenix/pull/6549))\n  - Drop incoming channel messages with stale join refs\n\n## 1.8.2 (2025-11-26)\n\n### Bug fixes\n  - [phoenix.js] fix issue where LongPoll can cause \"unmatched topic\" errors (observed on iOS only) ([#6538](https://github.com/phoenixframework/phoenix/pull/6538))\n  - [phx.gen.live] fix tests when schema and table names are equal ([#6477](https://github.com/phoenixframework/phoenix/pull/6477))\n  - [Verified Routes] do not add path prefixes for static routes\n  - [Phoenix.Endpoint] fix LongPoll being active by default since 1.8.0 ([#6487](https://github.com/phoenixframework/phoenix/pull/6487))\n\n### Enhancements\n  - [phoenix.js] socket now stops reconnection attempts while the page is hidden ([#6534](https://github.com/phoenixframework/phoenix/pull/6534))\n  - [phx.new] (re-)add `<.input field={@form[:foo]} type=\"hidden\" />` support in core components\n  - [phx.new] set `force_ssl` in `prod.exs` by default ([#6435](https://github.com/phoenixframework/phoenix/pull/6435))\n  - [phx.new] change `--docker` base image to debian trixie ([#6521](https://github.com/phoenixframework/phoenix/pull/6521))\n  - [Phoenix.Socket.assign/2] allow passing a function as second argument `assign(socket, fn _existing_assigns -> %{this_gets: \"merged\"} end)` ([#6530](https://github.com/phoenixframework/phoenix/pull/6530))\n  - [Phoenix.Controller.assign/2] allow passing a function as second argument ([#6542](https://github.com/phoenixframework/phoenix/pull/6542))\n  - [Phoenix.Controller.assign/2] support keyword lists and maps as second argument similar to LiveView ([#6513](https://github.com/phoenixframework/phoenix/pull/6513))\n  - [Presence] support custom dispatcher for `presence_diff` broadcast ([#6500](https://github.com/phoenixframework/phoenix/pull/6500))\n  - [AGENTS.md] add short test guidelines to usage rules\n\n## 1.8.1 (2025-08-28)\n\n### Bug fixes\n  - [phx.new] Fix AGENTS.md failing to include CSS and JavaScript sections\n\n## 1.8.0 (2025-08-05)\n\n### Bug fixes\n  - [phx.new] Don't include node_modules override in generated `tsconfig.json`\n\n### Enhancements\n  - [phx.gen.live|html|json] - Make context argument optional. Defaults to the plural name.\n  - [phx.new] Add `mix precommit` alias\n  - [phx.new] Add `AGENTS.md` generation compatible with [`usage_rules`](https://hexdocs.pm/usage_rules/)\n  - [phx.new] Add `usage_rules` folder to installer, allowing to sync generic Phoenix rules into new projects\n  - [phx.new] Use LiveView 1.1 release in generated code\n  - [phx.new] Ensure theme selector and flash closing works without LiveView\n\n## 1.8.0-rc.4 (2025-07-14)\n\n### Bug Fixes\n  - Fix phx.gen.presence PubSub server name for umbrella apps\n  - Fix `phx.gen.live` subscribing to pubsub in disconnected mounts\n\n### Enhancements\n  - [phx.new] Initialize initial git repo when git is installed\n  - [phx.new] Opt-in to HEEx `:debug_tags_location` in development\n  - [phx.gen.live|html|json|context] Make context name optional and inflect based on schema when missing\n  - [phx.gen.*] Use new Ecto 3.13 `Repo.transact/2` in generators\n  - [phx.gen.auth] Warn when using `phx.gen.auth` without esbuild as features assume `phoenix_html.js` in bundle\n  - Add `security.md` guide for security best practices\n  - [phoenix.js] - Add fetch() support to LongPoll when XMLHTTPRequest is not available\n  - Optimize parameter scrubbing by precompiling patterns\n\n## 1.8.0-rc.3 (2025-05-07)\n\n### Enhancements\n  - [phx.gen.auth] Allow configuring the scope's assign key in phx.gen.auth\n  - [phx.new] Do not override theme in root layout if explicitly set\n\n## 1.8.0-rc.2 (2025-04-29)\n\n### Bug Fixes\n  - [phx.gen.live] Only subscribe to pubsub if connected\n  - [phx.gen.auth] Remove unused current_password field\n  - [phx.gen.auth] Use context_app for scopes to fix generated scopes in umbrella apps\n\n## 1.8.0-rc.1 (2025-04-16)\n\n### Enhancements\n  - [phx.new] Support PORT in dev\n  - [phx.gen.auth] Replace `utc_now/0 + truncate/1` with `utc_now/1`\n  - [phx.gen.auth] Make dev mailbox link more obvious\n\n### Bug Fixes\n  - [phx.new] Fix Tailwind custom variants for loading classes (#6194)\n  - [phx.new] Fix heroicons path for umbrella apps\n  - [phx.gen.auth] Fix missing index for scoped resources (#6186)\n  - [phx.gen.live] Fix crash when an open :show page gets a PubSub broadcast for items (#6197)\n\n## 1.8.0-rc.0 (2025-04-01) 🚀\n\n- First release candidate!\n\n## v1.7\n\nThe CHANGELOG for v1.7 releases can be found in the [v1.7 branch](https://github.com/phoenixframework/phoenix/blob/v1.7/CHANGELOG.md).\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Code of Conduct\n\nAs contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.\n\nWe are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery\n* Personal attacks\n* Trolling or insulting/derogatory comments\n* Public or private harassment\n* Publishing other's private information, such as physical or electronic addresses, without explicit permission\n* Other unethical or unprofessional conduct.\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. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team.\n\nThis code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community.\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.2.0, available at [https://www.contributor-covenant.org/version/1/2/0/code-of-conduct/](https://www.contributor-covenant.org/version/1/2/0/code-of-conduct/)\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Phoenix\n\nPlease take a moment to review this document in order to make the contribution\nprocess easy and effective for everyone involved!\nAlso make sure you read our [Code of Conduct](CODE_OF_CONDUCT.md) that outlines our commitment towards an open and welcoming environment.\n\n## Using the issue tracker\n\nUse the issues tracker for:\n\n* [Bug reports](#bug-reports)\n* [Submitting pull requests](#pull-requests)\n\nFor requesting help and discussing new features:\n\n* [The Phoenix subforum on the Elixir Forum](https://elixirforum.com/c/phoenix-forum)\n* **[#elixir](irc://irc.libera.chat/elixir)** on [Libera](https://libera.chat/) IRC\n\nWe do our best to keep the issue tracker tidy and organized, making it useful\nfor everyone. For example, we classify open issues per perceived difficulty,\nmaking it easier for developers to [contribute to Phoenix](#pull-requests).\n\n## Bug reports\n\nA bug is either a _demonstrable problem_ that is caused by the code in the repository,\nor indicates missing, unclear, or misleading documentation. Good bug reports are extremely\nhelpful - thank you!\n\nGuidelines for bug reports:\n\n1. **Use the GitHub issue search** &mdash; check if the issue has already been\n   reported.\n\n2. **Check if the issue has been fixed** &mdash; try to reproduce it using the\n   `main` branch in the repository.\n\n3. **Isolate and report the problem** &mdash; ideally create a reduced test\n   case.\n\nPlease try to be as detailed as possible in your report. Include information about\nyour Operating System, as well as your Erlang, Elixir and Phoenix versions. Please provide steps to\nreproduce the issue as well as the outcome you were expecting! All these details\nwill help developers to fix any potential bugs.\n\nExample:\n\n> Short and descriptive example bug report title\n>\n> A summary of the issue and the environment in which it occurs. If suitable,\n> include the steps required to reproduce the bug.\n>\n> 1. This is the first step\n> 2. This is the second step\n> 3. Further steps, etc.\n>\n> `<url>` - a link to the reduced test case (e.g. a GitHub Gist)\n>\n> Any other information you want to share that is relevant to the issue being\n> reported. This might include the lines of code that you have identified as\n> causing the bug, and potential solutions (and your opinions on their\n> merits).\n\n## Feature requests\n\nFeature requests are welcome and should be discussed on the [Phoenix subforum](https://elixirforum.com/c/phoenix-forum). But take a moment to find\nout whether your idea fits with the scope and aims of the project. It's up to *you*\nto make a strong case to convince the community of the merits of this feature.\nPlease provide as much detail and context as possible.\n\n## Contributing Documentation\n\nCode documentation (`@doc`, `@moduledoc`, `@typedoc`) has a special convention:\nthe first paragraph is considered to be a short summary.\n\nFor functions, macros, and callbacks say what it will do. For example write\nsomething like:\n\n```elixir\n@doc \"\"\"\nMarks the given value as HTML safe.\n\"\"\"\ndef safe({:safe, value}), do: {:safe, value}\n```\n\nFor modules, protocols, and types say what it is. For example write\nsomething like:\n\n```elixir\ndefmodule Phoenix.HTML do\n  @moduledoc \"\"\"\n  Conveniences for working HTML strings and templates.\n  ...\n  \"\"\"\n```\n\nKeep in mind that the first paragraph might show up in a summary somewhere, long\ntexts in the first paragraph create very ugly summaries. As a rule of thumb\nanything longer than 80 characters is too long.\n\nTry to keep unnecessary details out of the first paragraph, it's only there to\ngive a user a quick idea of what the documented \"thing\" does/is. The rest of the\ndocumentation string can contain the details, for example when a value and when\n`nil` is returned.\n\nIf possible include examples, preferably in a form that works with doctests.\nThis makes it easy to test the examples so that they don't go stale and examples\nare often a great help in explaining what a function does.\n\n## Pull requests\n\nGood pull requests - patches, improvements, new features - are a fantastic\nhelp. They should remain focused in scope and avoid containing unrelated\ncommits.\n\n**IMPORTANT**: By submitting a patch, you agree that your work will be\nlicensed under the license used by the project.\n\nIf you have any large pull request in mind (e.g. implementing features,\nrefactoring code, etc), **please ask first** otherwise you risk spending\na lot of time working on something that the project's developers might\nnot want to merge into the project.\n\nPlease adhere to the coding conventions in the project (indentation,\naccurate comments, etc.) and don't forget to add your own tests and\ndocumentation. When working with git, we recommend the following process\nin order to craft an excellent pull request:\n\n1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork,\n   and configure the remotes:\n\n   ```bash\n   # Clone your fork of the repo into the current directory\n   git clone https://github.com/<your-username>/phoenix\n\n   # Navigate to the newly cloned directory\n   cd phoenix\n\n   # Assign the original repo to a remote called \"upstream\"\n   git remote add upstream https://github.com/phoenixframework/phoenix\n   ```\n\n2. If you cloned a while ago, get the latest changes from upstream, and update your fork:\n\n   ```bash\n   git checkout main\n   git pull upstream main\n   git push\n   ```\n\n3. Create a new topic branch (off of `main`) to contain your feature, change,\n   or fix.\n\n   **IMPORTANT**: Making changes in `main` is discouraged. You should always\n   keep your local `main` in sync with upstream `main` and make your\n   changes in topic branches.\n\n   ```bash\n   git checkout -b <topic-branch-name>\n   ```\n\n4. Commit your changes in logical chunks. Keep your commit messages organized,\n   with a short description in the first line and more detailed information on\n   the following lines. Feel free to use Git's\n   [interactive rebase](https://help.github.com/articles/about-git-rebase/)\n   feature to tidy up your commits before making them public.\n\n5. Make sure all the tests are still passing.\n\n   ```bash\n   mix test\n   ```\n\n6. Push your topic branch up to your fork:\n\n   ```bash\n   git push origin <topic-branch-name>\n   ```\n\n7. [Open a Pull Request](https://help.github.com/articles/about-pull-requests/)\n    with a clear title and description.\n\n8. If you haven't updated your pull request for a while, you should consider\n   rebasing on main and resolving any conflicts.\n\n   **IMPORTANT**: _Never ever_ merge upstream `main` into your branches. You\n   should always `git rebase` on `main` to bring your changes up to date when\n   necessary.\n\n   ```bash\n   git checkout main\n   git pull upstream main\n   git checkout <your-topic-branch>\n   git rebase main\n   ```\n\nThank you for your contributions!\n\n## Guides\n\nThese Guides aim to be inclusive. We use \"we\" and \"our\" instead of \"you\" and\n\"your\" to foster this sense of inclusion.\n\nIdeally there is something for everybody in each guide, from beginner to expert.\nThis is hard, maybe impossible. When we need to compromise, we do so on behalf\nof beginning users because expert users have more tools at their disposal to\nhelp themselves.\n\nThe general pattern we use for presenting information is to first introduce a\nsmall, discrete topic, then write a small amount of code to demonstrate the\nconcept, then verify that the code worked.\n\nIn this way, we build from small, easily digestible concepts into more complex\nones. The shorter this cycle is, as long as the information is still clear and\ncomplete, the better.\n\nFor formatting the guides:\n\n- We use the `elixir` code fence for all module code.\n- We use the `iex` for IEx sessions.\n- We use the `console` code fence for shell commands.\n- We use the `html` code fence for html templates, even if there is elixir code\n  in the template.\n- We use backticks for filenames and directory paths.\n- We use backticks for module names, function names, and variable names.\n- Documentation line length should hard wrapped at around 100 characters if possible.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2014 Chris McCord\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"./priv/static/phoenix-orange.png\" />\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"./priv/static/phoenix.png\" />\n  <img src=\"./priv/static/phoenix.png\" alt=\"Phoenix logo\" />\n</picture>\n\n> Peace of mind from prototype to production.\n\n[![Build Status](https://github.com/phoenixframework/phoenix/workflows/CI/badge.svg)](https://github.com/phoenixframework/phoenix/actions/workflows/ci.yml) [![Hex.pm](https://img.shields.io/hexpm/v/phoenix.svg)](https://hex.pm/packages/phoenix) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/phoenix)\n\n## Getting started\n\nSee the official site at <https://www.phoenixframework.org/>.\n\nInstall the latest version of Phoenix by following the instructions at <https://hexdocs.pm/phoenix/installation.html#phoenix>.\n\n## Documentation\n\nAPI documentation is available at <https://hexdocs.pm/phoenix>.\n\nPhoenix.js documentation is available at <https://hexdocs.pm/phoenix/js>.\n\n## Contributing\n\nWe appreciate any contribution to Phoenix. Check our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) and [CONTRIBUTING.md](CONTRIBUTING.md) guides for more information. We usually keep a list of features and bugs in the [issue tracker][4].\n\n### Generating a Phoenix project from unreleased versions\n\nYou can create a new project using the latest Phoenix source installer (the `phx.new` Mix task) with the following steps:\n\n1. Remove any previously installed `phx_new` archives so that Mix will pick up the local source code. This can be done with `mix archive.uninstall phx_new` or by simply deleting the file, which is usually in `~/.mix/archives/`.\n2. Copy this repo via `git clone https://github.com/phoenixframework/phoenix` or by downloading it\n3. Run the `phx.new` Mix task from within the `installer` directory, for example:\n\n```bash\ncd phoenix/installer\nmix phx.new dev_app --dev\n```\n\nThe `--dev` flag will configure your new project's `:phoenix` dep as a relative path dependency, pointing to your local Phoenix checkout:\n\n```elixir\ndefp deps do\n  [{:phoenix, path: \"../..\", override: true},\n```\n\nTo create projects outside of the `installer/` directory, add the latest archive to your machine by following the instructions in [installer/README.md](https://github.com/phoenixframework/phoenix/blob/main/installer/README.md)\n\n### Building from source\n\nTo build the documentation:\n\n```bash\nnpm install\nMIX_ENV=docs mix docs\n```\n\nTo build Phoenix:\n\n```bash\nmix deps.get\nmix compile\n```\n\nTo build the Phoenix installer:\n\n```bash\nmix deps.get\nmix compile\nmix archive.build\n```\n\nTo build Phoenix.js:\n\n```bash\ncd assets\nnpm install\n```\n\n## Important links\n\n* [#elixir][1] on [Libera][2] IRC\n* [elixir-lang Slack channel][3]\n* [Issues tracker][4]\n* [Phoenix Forum (questions and proposals)][5]\n* Visit Phoenix's sponsor, DockYard, for expert [Phoenix Consulting](https://dockyard.com/phoenix-consulting)\n\n  [1]: https://web.libera.chat/?channels=#elixir\n  [2]: https://libera.chat/\n  [3]: https://elixir-lang.slack.com/\n  [4]: https://github.com/phoenixframework/phoenix/issues\n  [5]: https://elixirforum.com/c/phoenix-forum\n\n## Copyright and License\n\nCopyright (c) 2014, Chris McCord.\n\nPhoenix source code is licensed under the [MIT License](LICENSE.md).\n"
  },
  {
    "path": "RELEASE.md",
    "content": "# Release Instructions\n\n  1. Check related deps for required version bumps and compatibility (`phoenix_ecto`, `phoenix_html`)\n  2. Bump version in related files below\n  3. Bump external dependency version in related external files below\n  4. Run tests:\n      - `mix test` in the root folder\n      - `mix test` in the `installer/` folder\n  5. Commit, push code\n  6. Publish `phx_new` and `phoenix` packages and docs after pruning any extraneous uncommitted files\n  7. Test installer by generating a new app, running `mix deps.get`, and compiling\n  8. Publish to `npm` with `npm publish`\n  9. Update Elixir and Erlang/OTP versions on new.phoenixframework.org\n  10. Start -dev version in related files below\n\n## Files with version\n\n  * `CHANGELOG`\n  * `mix.exs`\n  * `installer/mix.exs`\n  * `package.json`\n  * `assets/package.json`\n\n## Files with external dependency versions\n\n  * `priv/templates/phx.gen.release/Docker.eex` (debian)\n  * `priv/templates/phx.gen.release/Docker.eex` (esbuild)\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported versions\n\nPhoenix applies bug fixes only to the latest minor branch. Security patches are\navailable for the last 4 minor branches:\n\nPhoenix version | Support\n:-------------- | :-----------------------------\n1.7             | Bug fixes and security patches\n1.6             | Security patches only\n1.5             | Security patches only\n1.4             | Security patches only\n\n## Announcements\n\n[Security advisories will be published on GitHub](https://github.com/phoenixframework/phoenix/security).\n\n## Reporting a vulnerability\n\n[Please disclose security vulnerabilities privately via GitHub](https://github.com/phoenixframework/phoenix/security).\n"
  },
  {
    "path": "assets/js/phoenix/ajax.js",
    "content": "import {\n  global,\n  XHR_STATES\n} from \"./constants\"\n\nexport default class Ajax {\n\n  static request(method, endPoint, headers, body, timeout, ontimeout, callback){\n    if(global.XDomainRequest){\n      let req = new global.XDomainRequest() // IE8, IE9\n      return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback)\n    } else if(global.XMLHttpRequest){\n      let req = new global.XMLHttpRequest() // IE7+, Firefox, Chrome, Opera, Safari\n      return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback)\n    } else if(global.fetch && global.AbortController){\n      // Fetch with AbortController for modern browsers\n      return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback)\n    } else {\n      throw new Error(\"No suitable XMLHttpRequest implementation found\")\n    }\n  }\n\n  static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback){\n    let options = {\n      method,\n      headers,\n      body,\n    }\n    let controller = null\n    if(timeout){\n      controller = new AbortController()\n      const _timeoutId = setTimeout(() => controller.abort(), timeout)\n      options.signal = controller.signal\n    }\n    global.fetch(endPoint, options)\n      .then(response => response.text())\n      .then(data => this.parseJSON(data))\n      .then(data => callback && callback(data))\n      .catch(err => {\n        if(err.name === \"AbortError\" && ontimeout){\n          ontimeout()\n        } else {\n          callback && callback(null)\n        }\n      })\n    return controller\n  }\n\n  static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback){\n    req.timeout = timeout\n    req.open(method, endPoint)\n    req.onload = () => {\n      let response = this.parseJSON(req.responseText)\n      callback && callback(response)\n    }\n    if(ontimeout){ req.ontimeout = ontimeout }\n\n    // Work around bug in IE9 that requires an attached onprogress handler\n    req.onprogress = () => { }\n\n    req.send(body)\n    return req\n  }\n\n  static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback){\n    req.open(method, endPoint, true)\n    req.timeout = timeout\n    for(let [key, value] of Object.entries(headers)){\n      req.setRequestHeader(key, value)\n    }\n    req.onerror = () => callback && callback(null)\n    req.onreadystatechange = () => {\n      if(req.readyState === XHR_STATES.complete && callback){\n        let response = this.parseJSON(req.responseText)\n        callback(response)\n      }\n    }\n    if(ontimeout){ req.ontimeout = ontimeout }\n\n    req.send(body)\n    return req\n  }\n\n  static parseJSON(resp){\n    if(!resp || resp === \"\"){ return null }\n\n    try {\n      return JSON.parse(resp)\n    } catch {\n      console && console.log(\"failed to parse JSON response\", resp)\n      return null\n    }\n  }\n\n  static serialize(obj, parentKey){\n    let queryStr = []\n    for(var key in obj){\n      if(!Object.prototype.hasOwnProperty.call(obj, key)){ continue }\n      let paramKey = parentKey ? `${parentKey}[${key}]` : key\n      let paramVal = obj[key]\n      if(typeof paramVal === \"object\"){\n        queryStr.push(this.serialize(paramVal, paramKey))\n      } else {\n        queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal))\n      }\n    }\n    return queryStr.join(\"&\")\n  }\n\n  static appendParams(url, params){\n    if(Object.keys(params).length === 0){ return url }\n\n    let prefix = url.match(/\\?/) ? \"&\" : \"?\"\n    return `${url}${prefix}${this.serialize(params)}`\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/channel.js",
    "content": "import {closure} from \"./utils\"\nimport {\n  CHANNEL_EVENTS,\n  CHANNEL_STATES,\n} from \"./constants\"\n\nimport Push from \"./push\"\nimport Timer from \"./timer\"\n\n/**\n *\n * @param {string} topic\n * @param {(Object|function)} params\n * @param {Socket} socket\n */\nexport default class Channel {\n  constructor(topic, params, socket){\n    this.state = CHANNEL_STATES.closed\n    this.topic = topic\n    this.params = closure(params || {})\n    this.socket = socket\n    this.bindings = []\n    this.bindingRef = 0\n    this.timeout = this.socket.timeout\n    this.joinedOnce = false\n    this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout)\n    this.pushBuffer = []\n    this.stateChangeRefs = []\n\n    this.rejoinTimer = new Timer(() => {\n      if(this.socket.isConnected()){ this.rejoin() }\n    }, this.socket.rejoinAfterMs)\n    this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()))\n    this.stateChangeRefs.push(this.socket.onOpen(() => {\n      this.rejoinTimer.reset()\n      if(this.isErrored()){ this.rejoin() }\n    })\n    )\n    this.joinPush.receive(\"ok\", () => {\n      this.state = CHANNEL_STATES.joined\n      this.rejoinTimer.reset()\n      this.pushBuffer.forEach(pushEvent => pushEvent.send())\n      this.pushBuffer = []\n    })\n    this.joinPush.receive(\"error\", () => {\n      this.state = CHANNEL_STATES.errored\n      if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n    })\n    this.onClose(() => {\n      this.rejoinTimer.reset()\n      if(this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`)\n      this.state = CHANNEL_STATES.closed\n      this.socket.remove(this)\n    })\n    this.onError(reason => {\n      if(this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason)\n      if(this.isJoining()){ this.joinPush.reset() }\n      this.state = CHANNEL_STATES.errored\n      if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n    })\n    this.joinPush.receive(\"timeout\", () => {\n      if(this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout)\n      let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout)\n      leavePush.send()\n      this.state = CHANNEL_STATES.errored\n      this.joinPush.reset()\n      if(this.socket.isConnected()){ this.rejoinTimer.scheduleTimeout() }\n    })\n    this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n      this.trigger(this.replyEventName(ref), payload)\n    })\n  }\n\n  /**\n   * Join the channel\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  join(timeout = this.timeout){\n    if(this.joinedOnce){\n      throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\")\n    } else {\n      this.timeout = timeout\n      this.joinedOnce = true\n      this.rejoin()\n      return this.joinPush\n    }\n  }\n\n  /**\n   * Hook into channel close\n   * @param {Function} callback\n   */\n  onClose(callback){\n    this.on(CHANNEL_EVENTS.close, callback)\n  }\n\n  /**\n   * Hook into channel errors\n   * @param {Function} callback\n   */\n  onError(callback){\n    return this.on(CHANNEL_EVENTS.error, reason => callback(reason))\n  }\n\n  /**\n   * Subscribes on channel events\n   *\n   * Subscription returns a ref counter, which can be used later to\n   * unsubscribe the exact event listener\n   *\n   * @example\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * const ref2 = channel.on(\"event\", do_other_stuff)\n   * channel.off(\"event\", ref1)\n   * // Since unsubscription, do_stuff won't fire,\n   * // while do_other_stuff will keep firing on the \"event\"\n   *\n   * @param {string} event\n   * @param {Function} callback\n   * @returns {integer} ref\n   */\n  on(event, callback){\n    let ref = this.bindingRef++\n    this.bindings.push({event, ref, callback})\n    return ref\n  }\n\n  /**\n   * Unsubscribes off of channel events\n   *\n   * Use the ref returned from a channel.on() to unsubscribe one\n   * handler, or pass nothing for the ref to unsubscribe all\n   * handlers for the given event.\n   *\n   * @example\n   * // Unsubscribe the do_stuff handler\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * channel.off(\"event\", ref1)\n   *\n   * // Unsubscribe all handlers from event\n   * channel.off(\"event\")\n   *\n   * @param {string} event\n   * @param {integer} ref\n   */\n  off(event, ref){\n    this.bindings = this.bindings.filter((bind) => {\n      return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref))\n    })\n  }\n\n  /**\n   * @private\n   */\n  canPush(){ return this.socket.isConnected() && this.isJoined() }\n\n  /**\n   * Sends a message `event` to phoenix with the payload `payload`.\n   * Phoenix receives this in the `handle_in(event, payload, socket)`\n   * function. if phoenix replies or it times out (default 10000ms),\n   * then optionally the reply can be received.\n   *\n   * @example\n   * channel.push(\"event\")\n   *   .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n   *   .receive(\"error\", err => console.log(\"phoenix errored\", err))\n   *   .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n   * @param {string} event\n   * @param {Object} payload\n   * @param {number} [timeout]\n   * @returns {Push}\n   */\n  push(event, payload, timeout = this.timeout){\n    payload = payload || {}\n    if(!this.joinedOnce){\n      throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`)\n    }\n    let pushEvent = new Push(this, event, function (){ return payload }, timeout)\n    if(this.canPush()){\n      pushEvent.send()\n    } else {\n      pushEvent.startTimeout()\n      this.pushBuffer.push(pushEvent)\n    }\n\n    return pushEvent\n  }\n\n  /** Leaves the channel\n   *\n   * Unsubscribes from server events, and\n   * instructs channel to terminate on server\n   *\n   * Triggers onClose() hooks\n   *\n   * To receive leave acknowledgements, use the `receive`\n   * hook to bind to the server ack, ie:\n   *\n   * @example\n   * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n   *\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  leave(timeout = this.timeout){\n    this.rejoinTimer.reset()\n    this.joinPush.cancelTimeout()\n\n    this.state = CHANNEL_STATES.leaving\n    let onClose = () => {\n      if(this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`)\n      this.trigger(CHANNEL_EVENTS.close, \"leave\")\n    }\n    let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout)\n    leavePush.receive(\"ok\", () => onClose())\n      .receive(\"timeout\", () => onClose())\n    leavePush.send()\n    if(!this.canPush()){ leavePush.trigger(\"ok\", {}) }\n\n    return leavePush\n  }\n\n  /**\n   * Overridable message hook\n   *\n   * Receives all events for specialized message handling\n   * before dispatching to the channel callbacks.\n   *\n   * Must return the payload, modified or unmodified\n   * @param {string} event\n   * @param {Object} payload\n   * @param {integer} ref\n   * @returns {Object}\n   */\n  onMessage(_event, payload, _ref){ return payload }\n\n  /**\n   * @private\n   */\n  isMember(topic, event, payload, joinRef){\n    if(this.topic !== topic){ return false }\n\n    if(joinRef && joinRef !== this.joinRef()){\n      if(this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", {topic, event, payload, joinRef})\n      return false\n    } else {\n      return true\n    }\n  }\n\n  /**\n   * @private\n   */\n  joinRef(){ return this.joinPush.ref }\n\n  /**\n   * @private\n   */\n  rejoin(timeout = this.timeout){\n    if(this.isLeaving()){ return }\n    this.socket.leaveOpenTopic(this.topic)\n    this.state = CHANNEL_STATES.joining\n    this.joinPush.resend(timeout)\n  }\n\n  /**\n   * @private\n   */\n  trigger(event, payload, ref, joinRef){\n    let handledPayload = this.onMessage(event, payload, ref, joinRef)\n    if(payload && !handledPayload){ throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\") }\n\n    let eventBindings = this.bindings.filter(bind => bind.event === event)\n\n    for(let i = 0; i < eventBindings.length; i++){\n      let bind = eventBindings[i]\n      bind.callback(handledPayload, ref, joinRef || this.joinRef())\n    }\n  }\n\n  /**\n   * @private\n   */\n  replyEventName(ref){ return `chan_reply_${ref}` }\n\n  /**\n   * @private\n   */\n  isClosed(){ return this.state === CHANNEL_STATES.closed }\n\n  /**\n   * @private\n   */\n  isErrored(){ return this.state === CHANNEL_STATES.errored }\n\n  /**\n   * @private\n   */\n  isJoined(){ return this.state === CHANNEL_STATES.joined }\n\n  /**\n   * @private\n   */\n  isJoining(){ return this.state === CHANNEL_STATES.joining }\n\n  /**\n   * @private\n   */\n  isLeaving(){ return this.state === CHANNEL_STATES.leaving }\n}\n"
  },
  {
    "path": "assets/js/phoenix/constants.js",
    "content": "export const globalSelf = typeof self !== \"undefined\" ? self : null\nexport const phxWindow = typeof window !== \"undefined\" ? window : null\nexport const global = globalSelf || phxWindow || globalThis\nexport const DEFAULT_VSN = \"2.0.0\"\nexport const SOCKET_STATES = {connecting: 0, open: 1, closing: 2, closed: 3}\nexport const DEFAULT_TIMEOUT = 10000\nexport const WS_CLOSE_NORMAL = 1000\nexport const CHANNEL_STATES = {\n  closed: \"closed\",\n  errored: \"errored\",\n  joined: \"joined\",\n  joining: \"joining\",\n  leaving: \"leaving\",\n}\nexport const CHANNEL_EVENTS = {\n  close: \"phx_close\",\n  error: \"phx_error\",\n  join: \"phx_join\",\n  reply: \"phx_reply\",\n  leave: \"phx_leave\"\n}\n\nexport const TRANSPORTS = {\n  longpoll: \"longpoll\",\n  websocket: \"websocket\"\n}\nexport const XHR_STATES = {\n  complete: 4\n}\nexport const AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\"\n"
  },
  {
    "path": "assets/js/phoenix/index.js",
    "content": "/**\n * Phoenix Channels JavaScript client\n *\n * ## Socket Connection\n *\n * A single connection is established to the server and\n * channels are multiplexed over the connection.\n * Connect to the server using the `Socket` class:\n *\n * ```javascript\n * let socket = new Socket(\"/socket\", {params: {userToken: \"123\"}})\n * socket.connect()\n * ```\n *\n * The `Socket` constructor takes the mount point of the socket,\n * the authentication params, as well as options that can be found in\n * the Socket docs, such as configuring the `LongPoll` transport, and\n * heartbeat.\n *\n * ## Channels\n *\n * Channels are isolated, concurrent processes on the server that\n * subscribe to topics and broker events between the client and server.\n * To join a channel, you must provide the topic, and channel params for\n * authorization. Here's an example chat room example where `\"new_msg\"`\n * events are listened for, messages are pushed to the server, and\n * the channel is joined with ok/error/timeout matches:\n *\n * ```\n * let channel = socket.channel(\"room:123\", {token: roomToken})\n * channel.on(\"new_msg\", msg => console.log(\"Got message\", msg) )\n * $input.onEnter( e => {\n *   channel.push(\"new_msg\", {body: e.target.val}, 10000)\n *     .receive(\"ok\", (msg) => console.log(\"created message\", msg) )\n *     .receive(\"error\", (reasons) => console.log(\"create failed\", reasons) )\n *     .receive(\"timeout\", () => console.log(\"Networking issue...\") )\n * })\n *\n * channel.join()\n *   .receive(\"ok\", ({messages}) => console.log(\"catching up\", messages) )\n *   .receive(\"error\", ({reason}) => console.log(\"failed join\", reason) )\n *   .receive(\"timeout\", () => console.log(\"Networking issue. Still waiting...\"))\n *```\n *\n * ## Joining\n *\n * Creating a channel with `socket.channel(topic, params)`, binds the params to\n * `channel.params`, which are sent up on `channel.join()`.\n * Subsequent rejoins will send up the modified params for\n * updating authorization params, or passing up last_message_id information.\n * Successful joins receive an \"ok\" status, while unsuccessful joins\n * receive \"error\".\n *\n * With the default serializers and WebSocket transport, JSON text frames are\n * used for pushing a JSON object literal. If an `ArrayBuffer` instance is provided,\n * binary encoding will be used and the message will be sent with the binary\n * opcode.\n *\n * *Note*: binary messages are only supported on the WebSocket transport.\n *\n * ## Duplicate Join Subscriptions\n *\n * While the client may join any number of topics on any number of channels,\n * the client may only hold a single subscription for each unique topic at any\n * given time. When attempting to create a duplicate subscription,\n * the server will close the existing channel, log a warning, and\n * spawn a new channel for the topic. The client will have their\n * `channel.onClose` callbacks fired for the existing channel, and the new\n * channel join will have its receive hooks processed as normal.\n *\n * ## Pushing Messages\n *\n * From the previous example, we can see that pushing messages to the server\n * can be done with `channel.push(eventName, payload)` and we can optionally\n * receive responses from the push. Additionally, we can use\n * `receive(\"timeout\", callback)` to abort waiting for our other `receive` hooks\n *  and take action after some period of waiting. The default timeout is 10000ms.\n *\n *\n * ## Socket Hooks\n *\n * Lifecycle events of the multiplexed connection can be hooked into via\n * `socket.onError()` and `socket.onClose()` events, ie:\n *\n * ```\n * socket.onError( () => console.log(\"there was an error with the connection!\") )\n * socket.onClose( () => console.log(\"the connection dropped\") )\n * ```\n *\n *\n * ## Channel Hooks\n *\n * For each joined channel, you can bind to `onError` and `onClose` events\n * to monitor the channel lifecycle, ie:\n *\n * ```\n * channel.onError( () => console.log(\"there was an error!\") )\n * channel.onClose( () => console.log(\"the channel has gone away gracefully\") )\n * ```\n *\n * ### onError hooks\n *\n * `onError` hooks are invoked if the socket connection drops, or the channel\n * crashes on the server. In either case, a channel rejoin is attempted\n * automatically in an exponential backoff manner.\n *\n * ### onClose hooks\n *\n * `onClose` hooks are invoked only in two cases. 1) the channel explicitly\n * closed on the server, or 2). The client explicitly closed, by calling\n * `channel.leave()`\n *\n *\n * ## Presence\n *\n * The `Presence` object provides features for syncing presence information\n * from the server with the client and handling presences joining and leaving.\n *\n * ### Syncing state from the server\n *\n * To sync presence state from the server, first instantiate an object and\n * pass your channel in to track lifecycle events:\n *\n * ```\n * let channel = socket.channel(\"some:topic\")\n * let presence = new Presence(channel)\n * ```\n *\n * Next, use the `presence.onSync` callback to react to state changes\n * from the server. For example, to render the list of users every time\n * the list changes, you could write:\n *\n * ```\n * presence.onSync(() => {\n *   myRenderUsersFunction(presence.list())\n * })\n * ```\n *\n * ### Listing Presences\n *\n * `presence.list` is used to return a list of presence information\n * based on the local state of metadata. By default, all presence\n * metadata is returned, but a `listBy` function can be supplied to\n * allow the client to select which metadata to use for a given presence.\n * For example, you may have a user online from different devices with\n * a metadata status of \"online\", but they have set themselves to \"away\"\n * on another device. In this case, the app may choose to use the \"away\"\n * status for what appears on the UI. The example below defines a `listBy`\n * function which prioritizes the first metadata which was registered for\n * each user. This could be the first tab they opened, or the first device\n * they came online from:\n *\n * ```\n * let listBy = (id, {metas: [first, ...rest]}) => {\n *   first.count = rest.length + 1 // count of this user's presences\n *   first.id = id\n *   return first\n * }\n * let onlineUsers = presence.list(listBy)\n * ```\n *\n * ### Handling individual presence join and leave events\n *\n * The `presence.onJoin` and `presence.onLeave` callbacks can be used to\n * react to individual presences joining and leaving the app. For example:\n *\n * ```\n * let presence = new Presence(channel)\n *\n * // detect if user has joined for the 1st time or from another tab/device\n * presence.onJoin((id, current, newPres) => {\n *   if(!current){\n *     console.log(\"user has entered for the first time\", newPres)\n *   } else {\n *     console.log(\"user additional presence\", newPres)\n *   }\n * })\n *\n * // detect if user has left from all tabs/devices, or is still present\n * presence.onLeave((id, current, leftPres) => {\n *   if(current.metas.length === 0){\n *     console.log(\"user has left from all devices\", leftPres)\n *   } else {\n *     console.log(\"user left from a device\", leftPres)\n *   }\n * })\n * // receive presence data from server\n * presence.onSync(() => {\n *   displayUsers(presence.list())\n * })\n * ```\n * @module phoenix\n */\n\nimport Channel from \"./channel\"\nimport LongPoll from \"./longpoll\"\nimport Presence from \"./presence\"\nimport Serializer from \"./serializer\"\nimport Socket from \"./socket\"\n\nexport {\n  Channel,\n  LongPoll,\n  Presence,\n  Serializer,\n  Socket\n}\n"
  },
  {
    "path": "assets/js/phoenix/longpoll.js",
    "content": "import {\n  SOCKET_STATES,\n  TRANSPORTS,\n  AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport Ajax from \"./ajax\"\n\nlet arrayBufferToBase64 = (buffer) => {\n  let binary = \"\"\n  let bytes = new Uint8Array(buffer)\n  let len = bytes.byteLength\n  for(let i = 0; i < len; i++){ binary += String.fromCharCode(bytes[i]) }\n  return btoa(binary)\n}\n\nexport default class LongPoll {\n\n  constructor(endPoint, protocols){\n    // we only support subprotocols for authToken\n    // [\"phoenix\", \"base64url.bearer.phx.BASE64_ENCODED_TOKEN\"]\n    if(protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)){\n      this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length))\n    }\n    this.endPoint = null\n    this.token = null\n    this.skipHeartbeat = true\n    this.reqs = new Set()\n    this.awaitingBatchAck = false\n    this.currentBatch = null\n    this.currentBatchTimer = null\n    this.batchBuffer = []\n    this.onopen = function (){ } // noop\n    this.onerror = function (){ } // noop\n    this.onmessage = function (){ } // noop\n    this.onclose = function (){ } // noop\n    this.pollEndpoint = this.normalizeEndpoint(endPoint)\n    this.readyState = SOCKET_STATES.connecting\n    // we must wait for the caller to finish setting up our callbacks and timeout properties\n    setTimeout(() => this.poll(), 0)\n  }\n\n  normalizeEndpoint(endPoint){\n    return (endPoint\n      .replace(\"ws://\", \"http://\")\n      .replace(\"wss://\", \"https://\")\n      .replace(new RegExp(\"(.*)\\/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll))\n  }\n\n  endpointURL(){\n    return Ajax.appendParams(this.pollEndpoint, {token: this.token})\n  }\n\n  closeAndRetry(code, reason, wasClean){\n    this.close(code, reason, wasClean)\n    this.readyState = SOCKET_STATES.connecting\n  }\n\n  ontimeout(){\n    this.onerror(\"timeout\")\n    this.closeAndRetry(1005, \"timeout\", false)\n  }\n\n  isActive(){ return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting }\n\n  poll(){\n    const headers = {\"Accept\": \"application/json\"}\n    if(this.authToken){\n      headers[\"X-Phoenix-AuthToken\"] = this.authToken\n    }\n    this.ajax(\"GET\", headers, null, () => this.ontimeout(), resp => {\n      if(resp){\n        var {status, token, messages} = resp\n        if(status === 410 && this.token !== null){\n          // In case we already have a token, this means that our existing session\n          // is gone. We fail so that the client rejoins its channels.\n          this.onerror(410)\n          this.closeAndRetry(3410, \"session_gone\", false)\n          return\n        }\n        this.token = token\n      } else {\n        status = 0\n      }\n\n      switch(status){\n        case 200:\n          messages.forEach(msg => {\n            // Tasks are what things like event handlers, setTimeout callbacks,\n            // promise resolves and more are run within.\n            // In modern browsers, there are two different kinds of tasks,\n            // microtasks and macrotasks.\n            // Microtasks are mainly used for Promises, while macrotasks are\n            // used for everything else.\n            // Microtasks always have priority over macrotasks. If the JS engine\n            // is looking for a task to run, it will always try to empty the\n            // microtask queue before attempting to run anything from the\n            // macrotask queue.\n            //\n            // For the WebSocket transport, messages always arrive in their own\n            // event. This means that if any promises are resolved from within,\n            // their callbacks will always finish execution by the time the\n            // next message event handler is run.\n            //\n            // In order to emulate this behaviour, we need to make sure each\n            // onmessage handler is run within its own macrotask.\n            setTimeout(() => this.onmessage({data: msg}), 0)\n          })\n          this.poll()\n          break\n        case 204:\n          this.poll()\n          break\n        case 410:\n          this.readyState = SOCKET_STATES.open\n          this.onopen({})\n          this.poll()\n          break\n        case 403:\n          this.onerror(403)\n          this.close(1008, \"forbidden\", false)\n          break\n        case 0:\n        case 500:\n          this.onerror(500)\n          this.closeAndRetry(1011, \"internal server error\", 500)\n          break\n        default: throw new Error(`unhandled poll status ${status}`)\n      }\n    })\n  }\n\n  // we collect all pushes within the current event loop by\n  // setTimeout 0, which optimizes back-to-back procedural\n  // pushes against an empty buffer\n\n  send(body){\n    if(typeof(body) !== \"string\"){ body = arrayBufferToBase64(body) }\n    if(this.currentBatch){\n      this.currentBatch.push(body)\n    } else if(this.awaitingBatchAck){\n      this.batchBuffer.push(body)\n    } else {\n      this.currentBatch = [body]\n      this.currentBatchTimer = setTimeout(() => {\n        this.batchSend(this.currentBatch)\n        this.currentBatch = null\n      }, 0)\n    }\n  }\n\n  batchSend(messages){\n    this.awaitingBatchAck = true\n    this.ajax(\"POST\", {\"Content-Type\": \"application/x-ndjson\"}, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), resp => {\n      this.awaitingBatchAck = false\n      if(!resp || resp.status !== 200){\n        this.onerror(resp && resp.status)\n        this.closeAndRetry(1011, \"internal server error\", false)\n      } else if(this.batchBuffer.length > 0){\n        this.batchSend(this.batchBuffer)\n        this.batchBuffer = []\n      }\n    })\n  }\n\n  close(code, reason, wasClean){\n    for(let req of this.reqs){ req.abort() }\n    this.readyState = SOCKET_STATES.closed\n    let opts = Object.assign({code: 1000, reason: undefined, wasClean: true}, {code, reason, wasClean})\n    this.batchBuffer = []\n    clearTimeout(this.currentBatchTimer)\n    this.currentBatchTimer = null\n    if(typeof(CloseEvent) !== \"undefined\"){\n      this.onclose(new CloseEvent(\"close\", opts))\n    } else {\n      this.onclose(opts)\n    }\n  }\n\n  ajax(method, headers, body, onCallerTimeout, callback){\n    let req\n    let ontimeout = () => {\n      this.reqs.delete(req)\n      onCallerTimeout()\n    }\n    req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, resp => {\n      this.reqs.delete(req)\n      if(this.isActive()){ callback(resp) }\n    })\n    this.reqs.add(req)\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/presence.js",
    "content": "/**\n * Initializes the Presence\n * @param {Channel} channel - The Channel\n * @param {Object} opts - The options,\n *        for example `{events: {state: \"state\", diff: \"diff\"}}`\n */\nexport default class Presence {\n\n  constructor(channel, opts = {}){\n    let events = opts.events || {state: \"presence_state\", diff: \"presence_diff\"}\n    this.state = {}\n    this.pendingDiffs = []\n    this.channel = channel\n    this.joinRef = null\n    this.caller = {\n      onJoin: function (){ },\n      onLeave: function (){ },\n      onSync: function (){ }\n    }\n\n    this.channel.on(events.state, newState => {\n      let {onJoin, onLeave, onSync} = this.caller\n\n      this.joinRef = this.channel.joinRef()\n      this.state = Presence.syncState(this.state, newState, onJoin, onLeave)\n\n      this.pendingDiffs.forEach(diff => {\n        this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n      })\n      this.pendingDiffs = []\n      onSync()\n    })\n\n    this.channel.on(events.diff, diff => {\n      let {onJoin, onLeave, onSync} = this.caller\n\n      if(this.inPendingSyncState()){\n        this.pendingDiffs.push(diff)\n      } else {\n        this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave)\n        onSync()\n      }\n    })\n  }\n\n  onJoin(callback){ this.caller.onJoin = callback }\n\n  onLeave(callback){ this.caller.onLeave = callback }\n\n  onSync(callback){ this.caller.onSync = callback }\n\n  list(by){ return Presence.list(this.state, by) }\n\n  inPendingSyncState(){\n    return !this.joinRef || (this.joinRef !== this.channel.joinRef())\n  }\n\n  // lower-level public static API\n\n  /**\n   * Used to sync the list of presences on the server\n   * with the client's state. An optional `onJoin` and `onLeave` callback can\n   * be provided to react to changes in the client's local presences across\n   * disconnects and reconnects with the server.\n   *\n   * @returns {Presence}\n   */\n  static syncState(currentState, newState, onJoin, onLeave){\n    let state = this.clone(currentState)\n    let joins = {}\n    let leaves = {}\n\n    this.map(state, (key, presence) => {\n      if(!newState[key]){\n        leaves[key] = presence\n      }\n    })\n    this.map(newState, (key, newPresence) => {\n      let currentPresence = state[key]\n      if(currentPresence){\n        let newRefs = newPresence.metas.map(m => m.phx_ref)\n        let curRefs = currentPresence.metas.map(m => m.phx_ref)\n        let joinedMetas = newPresence.metas.filter(m => curRefs.indexOf(m.phx_ref) < 0)\n        let leftMetas = currentPresence.metas.filter(m => newRefs.indexOf(m.phx_ref) < 0)\n        if(joinedMetas.length > 0){\n          joins[key] = newPresence\n          joins[key].metas = joinedMetas\n        }\n        if(leftMetas.length > 0){\n          leaves[key] = this.clone(currentPresence)\n          leaves[key].metas = leftMetas\n        }\n      } else {\n        joins[key] = newPresence\n      }\n    })\n    return this.syncDiff(state, {joins: joins, leaves: leaves}, onJoin, onLeave)\n  }\n\n  /**\n   *\n   * Used to sync a diff of presence join and leave\n   * events from the server, as they happen. Like `syncState`, `syncDiff`\n   * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n   * joining or leaving from a device.\n   *\n   * @returns {Presence}\n   */\n  static syncDiff(state, diff, onJoin, onLeave){\n    let {joins, leaves} = this.clone(diff)\n    if(!onJoin){ onJoin = function (){ } }\n    if(!onLeave){ onLeave = function (){ } }\n\n    this.map(joins, (key, newPresence) => {\n      let currentPresence = state[key]\n      state[key] = this.clone(newPresence)\n      if(currentPresence){\n        let joinedRefs = state[key].metas.map(m => m.phx_ref)\n        let curMetas = currentPresence.metas.filter(m => joinedRefs.indexOf(m.phx_ref) < 0)\n        state[key].metas.unshift(...curMetas)\n      }\n      onJoin(key, currentPresence, newPresence)\n    })\n    this.map(leaves, (key, leftPresence) => {\n      let currentPresence = state[key]\n      if(!currentPresence){ return }\n      let refsToRemove = leftPresence.metas.map(m => m.phx_ref)\n      currentPresence.metas = currentPresence.metas.filter(p => {\n        return refsToRemove.indexOf(p.phx_ref) < 0\n      })\n      onLeave(key, currentPresence, leftPresence)\n      if(currentPresence.metas.length === 0){\n        delete state[key]\n      }\n    })\n    return state\n  }\n\n  /**\n   * Returns the array of presences, with selected metadata.\n   *\n   * @param {Object} presences\n   * @param {Function} chooser\n   *\n   * @returns {Presence}\n   */\n  static list(presences, chooser){\n    if(!chooser){ chooser = function (key, pres){ return pres } }\n\n    return this.map(presences, (key, presence) => {\n      return chooser(key, presence)\n    })\n  }\n\n  // private\n\n  static map(obj, func){\n    return Object.getOwnPropertyNames(obj).map(key => func(key, obj[key]))\n  }\n\n  static clone(obj){ return JSON.parse(JSON.stringify(obj)) }\n}\n"
  },
  {
    "path": "assets/js/phoenix/push.js",
    "content": "/**\n * Initializes the Push\n * @param {Channel} channel - The Channel\n * @param {string} event - The event, for example `\"phx_join\"`\n * @param {Object} payload - The payload, for example `{user_id: 123}`\n * @param {number} timeout - The push timeout in milliseconds\n */\nexport default class Push {\n  constructor(channel, event, payload, timeout){\n    this.channel = channel\n    this.event = event\n    this.payload = payload || function (){ return {} }\n    this.receivedResp = null\n    this.timeout = timeout\n    this.timeoutTimer = null\n    this.recHooks = []\n    this.sent = false\n  }\n\n  /**\n   *\n   * @param {number} timeout\n   */\n  resend(timeout){\n    this.timeout = timeout\n    this.reset()\n    this.send()\n  }\n\n  /**\n   *\n   */\n  send(){\n    if(this.hasReceived(\"timeout\")){ return }\n    this.startTimeout()\n    this.sent = true\n    this.channel.socket.push({\n      topic: this.channel.topic,\n      event: this.event,\n      payload: this.payload(),\n      ref: this.ref,\n      join_ref: this.channel.joinRef()\n    })\n  }\n\n  /**\n   *\n   * @param {*} status\n   * @param {*} callback\n   */\n  receive(status, callback){\n    if(this.hasReceived(status)){\n      callback(this.receivedResp.response)\n    }\n\n    this.recHooks.push({status, callback})\n    return this\n  }\n\n  /**\n   * @private\n   */\n  reset(){\n    this.cancelRefEvent()\n    this.ref = null\n    this.refEvent = null\n    this.receivedResp = null\n    this.sent = false\n  }\n\n  /**\n   * @private\n   */\n  matchReceive({status, response, _ref}){\n    this.recHooks.filter(h => h.status === status)\n      .forEach(h => h.callback(response))\n  }\n\n  /**\n   * @private\n   */\n  cancelRefEvent(){\n    if(!this.refEvent){ return }\n    this.channel.off(this.refEvent)\n  }\n\n  /**\n   * @private\n   */\n  cancelTimeout(){\n    clearTimeout(this.timeoutTimer)\n    this.timeoutTimer = null\n  }\n\n  /**\n   * @private\n   */\n  startTimeout(){\n    if(this.timeoutTimer){ this.cancelTimeout() }\n    this.ref = this.channel.socket.makeRef()\n    this.refEvent = this.channel.replyEventName(this.ref)\n\n    this.channel.on(this.refEvent, payload => {\n      this.cancelRefEvent()\n      this.cancelTimeout()\n      this.receivedResp = payload\n      this.matchReceive(payload)\n    })\n\n    this.timeoutTimer = setTimeout(() => {\n      this.trigger(\"timeout\", {})\n    }, this.timeout)\n  }\n\n  /**\n   * @private\n   */\n  hasReceived(status){\n    return this.receivedResp && this.receivedResp.status === status\n  }\n\n  /**\n   * @private\n   */\n  trigger(status, response){\n    this.channel.trigger(this.refEvent, {status, response})\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/serializer.js",
    "content": "/* The default serializer for encoding and decoding messages */\nimport {\n  CHANNEL_EVENTS\n} from \"./constants\"\n\nexport default {\n  HEADER_LENGTH: 1,\n  META_LENGTH: 4,\n  KINDS: {push: 0, reply: 1, broadcast: 2},\n\n  encode(msg, callback){\n    if(msg.payload.constructor === ArrayBuffer){\n      return callback(this.binaryEncode(msg))\n    } else {\n      let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]\n      return callback(JSON.stringify(payload))\n    }\n  },\n\n  decode(rawPayload, callback){\n    if(rawPayload.constructor === ArrayBuffer){\n      return callback(this.binaryDecode(rawPayload))\n    } else {\n      let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload)\n      return callback({join_ref, ref, topic, event, payload})\n    }\n  },\n\n  // private\n\n  binaryEncode(message){\n    let {join_ref, ref, event, topic, payload} = message\n    let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length\n    let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength)\n    let view = new DataView(header)\n    let offset = 0\n\n    view.setUint8(offset++, this.KINDS.push) // kind\n    view.setUint8(offset++, join_ref.length)\n    view.setUint8(offset++, ref.length)\n    view.setUint8(offset++, topic.length)\n    view.setUint8(offset++, event.length)\n    Array.from(join_ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n    Array.from(ref, char => view.setUint8(offset++, char.charCodeAt(0)))\n    Array.from(topic, char => view.setUint8(offset++, char.charCodeAt(0)))\n    Array.from(event, char => view.setUint8(offset++, char.charCodeAt(0)))\n\n    var combined = new Uint8Array(header.byteLength + payload.byteLength)\n    combined.set(new Uint8Array(header), 0)\n    combined.set(new Uint8Array(payload), header.byteLength)\n\n    return combined.buffer\n  },\n\n  binaryDecode(buffer){\n    let view = new DataView(buffer)\n    let kind = view.getUint8(0)\n    let decoder = new TextDecoder()\n    switch(kind){\n      case this.KINDS.push: return this.decodePush(buffer, view, decoder)\n      case this.KINDS.reply: return this.decodeReply(buffer, view, decoder)\n      case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder)\n    }\n  },\n\n  decodePush(buffer, view, decoder){\n    let joinRefSize = view.getUint8(1)\n    let topicSize = view.getUint8(2)\n    let eventSize = view.getUint8(3)\n    let offset = this.HEADER_LENGTH + this.META_LENGTH - 1 // pushes have no ref\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n    offset = offset + joinRefSize\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n    offset = offset + topicSize\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n    offset = offset + eventSize\n    let data = buffer.slice(offset, buffer.byteLength)\n    return {join_ref: joinRef, ref: null, topic: topic, event: event, payload: data}\n  },\n\n  decodeReply(buffer, view, decoder){\n    let joinRefSize = view.getUint8(1)\n    let refSize = view.getUint8(2)\n    let topicSize = view.getUint8(3)\n    let eventSize = view.getUint8(4)\n    let offset = this.HEADER_LENGTH + this.META_LENGTH\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize))\n    offset = offset + joinRefSize\n    let ref = decoder.decode(buffer.slice(offset, offset + refSize))\n    offset = offset + refSize\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n    offset = offset + topicSize\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n    offset = offset + eventSize\n    let data = buffer.slice(offset, buffer.byteLength)\n    let payload = {status: event, response: data}\n    return {join_ref: joinRef, ref: ref, topic: topic, event: CHANNEL_EVENTS.reply, payload: payload}\n  },\n\n  decodeBroadcast(buffer, view, decoder){\n    let topicSize = view.getUint8(1)\n    let eventSize = view.getUint8(2)\n    let offset = this.HEADER_LENGTH + 2\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize))\n    offset = offset + topicSize\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize))\n    offset = offset + eventSize\n    let data = buffer.slice(offset, buffer.byteLength)\n\n    return {join_ref: null, ref: null, topic: topic, event: event, payload: data}\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/socket.js",
    "content": "import {\n  global,\n  phxWindow,\n  CHANNEL_EVENTS,\n  DEFAULT_TIMEOUT,\n  DEFAULT_VSN,\n  SOCKET_STATES,\n  TRANSPORTS,\n  WS_CLOSE_NORMAL,\n  AUTH_TOKEN_PREFIX\n} from \"./constants\"\n\nimport {\n  closure\n} from \"./utils\"\n\nimport Ajax from \"./ajax\"\nimport Channel from \"./channel\"\nimport LongPoll from \"./longpoll\"\nimport Serializer from \"./serializer\"\nimport Timer from \"./timer\"\n\n/** Initializes the Socket *\n *\n * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)\n *\n * @param {string} endPoint - The string WebSocket endpoint, ie, `\"ws://example.com/socket\"`,\n *                                               `\"wss://example.com\"`\n *                                               `\"/socket\"` (inherited host & protocol)\n * @param {Object} [opts] - Optional configuration\n * @param {Function} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.\n *\n * Defaults to WebSocket with automatic LongPoll fallback if WebSocket is not defined.\n * To fallback to LongPoll when WebSocket attempts fail, use `longPollFallbackMs: 2500`.\n *\n * @param {number} [opts.longPollFallbackMs] - The millisecond time to attempt the primary transport\n * before falling back to the LongPoll transport. Disabled by default.\n *\n * @param {boolean} [opts.debug] - When true, enables debug logging. Default false.\n *\n * @param {Function} [opts.encode] - The function to encode outgoing messages.\n *\n * Defaults to JSON encoder.\n *\n * @param {Function} [opts.decode] - The function to decode incoming messages.\n *\n * Defaults to JSON:\n *\n * ```javascript\n * (payload, callback) => callback(JSON.parse(payload))\n * ```\n *\n * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.\n *\n * Defaults `DEFAULT_TIMEOUT`\n * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message\n * @param {Function} [opts.reconnectAfterMs] - The optional function that returns the\n * socket reconnect interval, in milliseconds.\n *\n * Defaults to stepped backoff of:\n *\n * ```javascript\n * function(tries){\n *   return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n * }\n * ````\n *\n * @param {Function} [opts.rejoinAfterMs] - The optional function that returns the millisec\n * rejoin interval for individual channels.\n *\n * ```javascript\n * function(tries){\n *   return [1000, 2000, 5000][tries - 1] || 10000\n * }\n * ````\n *\n * @param {Function} [opts.logger] - The optional function for specialized logging, ie:\n *\n * ```javascript\n * function(kind, msg, data) {\n *   console.log(`${kind}: ${msg}`, data)\n * }\n * ```\n *\n * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.\n *\n * Defaults to 20s (double the server long poll timer).\n *\n * @param {(Object|function)} [opts.params] - The optional params to pass when connecting\n * @param {string} [opts.authToken] - the optional authentication token to be exposed on the server\n * under the `:auth_token` connect_info key.\n * @param {string} [opts.binaryType] - The binary type to use for binary WebSocket frames.\n *\n * Defaults to \"arraybuffer\"\n *\n * @param {vsn} [opts.vsn] - The serializer's protocol version to send on connect.\n *\n * Defaults to DEFAULT_VSN.\n *\n * @param {Object} [opts.sessionStorage] - An optional Storage compatible object\n * Phoenix uses sessionStorage for longpoll fallback history. Overriding the store is\n * useful when Phoenix won't have access to `sessionStorage`. For example, This could\n * happen if a site loads a cross-domain channel in an iframe. Example usage:\n *\n *     class InMemoryStorage {\n *       constructor() { this.storage = {} }\n *       getItem(keyName) { return this.storage[keyName] || null }\n *       removeItem(keyName) { delete this.storage[keyName] }\n *       setItem(keyName, keyValue) { this.storage[keyName] = keyValue }\n *     }\n *\n*/\nexport default class Socket {\n  constructor(endPoint, opts = {}){\n    this.stateChangeCallbacks = {open: [], close: [], error: [], message: []}\n    this.channels = []\n    this.sendBuffer = []\n    this.ref = 0\n    this.fallbackRef = null\n    this.timeout = opts.timeout || DEFAULT_TIMEOUT\n    this.transport = opts.transport || global.WebSocket || LongPoll\n    this.primaryPassedHealthCheck = false\n    this.longPollFallbackMs = opts.longPollFallbackMs\n    this.fallbackTimer = null\n    this.sessionStore = opts.sessionStorage || (global && global.sessionStorage)\n    this.establishedConnections = 0\n    this.defaultEncoder = Serializer.encode.bind(Serializer)\n    this.defaultDecoder = Serializer.decode.bind(Serializer)\n    // We start with closeWasClean true to avoid the visibility change\n    // logic from connecting if the socket was never connected in the first place.\n    // transportConnect sets it to false on open.\n    this.closeWasClean = true\n    this.disconnecting = false\n    this.binaryType = opts.binaryType || \"arraybuffer\"\n    this.connectClock = 1\n    this.pageHidden = false\n    if(this.transport !== LongPoll){\n      this.encode = opts.encode || this.defaultEncoder\n      this.decode = opts.decode || this.defaultDecoder\n    } else {\n      this.encode = this.defaultEncoder\n      this.decode = this.defaultDecoder\n    }\n    let awaitingConnectionOnPageShow = null\n    if(phxWindow && phxWindow.addEventListener){\n      phxWindow.addEventListener(\"pagehide\", _e => {\n        if(this.conn){\n          this.disconnect()\n          awaitingConnectionOnPageShow = this.connectClock\n        }\n      })\n      phxWindow.addEventListener(\"pageshow\", _e => {\n        if(awaitingConnectionOnPageShow === this.connectClock){\n          awaitingConnectionOnPageShow = null\n          this.connect()\n        }\n      })\n      phxWindow.addEventListener(\"visibilitychange\", () => {\n        if(document.visibilityState === \"hidden\"){\n          this.pageHidden = true\n        } else {\n          this.pageHidden = false\n          // reconnect immediately\n          if(!this.isConnected() && !this.closeWasClean){\n            this.teardown(() => this.connect())\n          }\n        }\n      })\n    }\n    this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000\n    this.rejoinAfterMs = (tries) => {\n      if(opts.rejoinAfterMs){\n        return opts.rejoinAfterMs(tries)\n      } else {\n        return [1000, 2000, 5000][tries - 1] || 10000\n      }\n    }\n    this.reconnectAfterMs = (tries) => {\n      if(opts.reconnectAfterMs){\n        return opts.reconnectAfterMs(tries)\n      } else {\n        return [10, 50, 100, 150, 200, 250, 500, 1000, 2000][tries - 1] || 5000\n      }\n    }\n    this.logger = opts.logger || null\n    if(!this.logger && opts.debug){\n      this.logger = (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }\n    }\n    this.longpollerTimeout = opts.longpollerTimeout || 20000\n    this.params = closure(opts.params || {})\n    this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`\n    this.vsn = opts.vsn || DEFAULT_VSN\n    this.heartbeatTimeoutTimer = null\n    this.heartbeatTimer = null\n    this.pendingHeartbeatRef = null\n    this.reconnectTimer = new Timer(() => {\n      if(this.pageHidden){\n        this.log(\"Not reconnecting as page is hidden!\")\n        this.teardown()\n        return\n      }\n      this.teardown(() => this.connect())\n    }, this.reconnectAfterMs)\n    this.authToken = opts.authToken\n  }\n\n  /**\n   * Returns the LongPoll transport reference\n   */\n  getLongPollTransport(){ return LongPoll }\n\n  /**\n   * Disconnects and replaces the active transport\n   *\n   * @param {Function} newTransport - The new transport class to instantiate\n   *\n   */\n  replaceTransport(newTransport){\n    this.connectClock++\n    this.closeWasClean = true\n    clearTimeout(this.fallbackTimer)\n    this.reconnectTimer.reset()\n    if(this.conn){\n      this.conn.close()\n      this.conn = null\n    }\n    this.transport = newTransport\n  }\n\n  /**\n   * Returns the socket protocol\n   *\n   * @returns {string}\n   */\n  protocol(){ return location.protocol.match(/^https/) ? \"wss\" : \"ws\" }\n\n  /**\n   * The fully qualified socket url\n   *\n   * @returns {string}\n   */\n  endPointURL(){\n    let uri = Ajax.appendParams(\n      Ajax.appendParams(this.endPoint, this.params()), {vsn: this.vsn})\n    if(uri.charAt(0) !== \"/\"){ return uri }\n    if(uri.charAt(1) === \"/\"){ return `${this.protocol()}:${uri}` }\n\n    return `${this.protocol()}://${location.host}${uri}`\n  }\n\n  /**\n   * Disconnects the socket\n   *\n   * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n   *\n   * @param {Function} callback - Optional callback which is called after socket is disconnected.\n   * @param {integer} code - A status code for disconnection (Optional).\n   * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n   */\n  disconnect(callback, code, reason){\n    this.connectClock++\n    this.disconnecting = true\n    this.closeWasClean = true\n    clearTimeout(this.fallbackTimer)\n    this.reconnectTimer.reset()\n    this.teardown(() => {\n      this.disconnecting = false\n      callback && callback()\n    }, code, reason)\n  }\n\n  /**\n   *\n   * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n   *\n   * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n   * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n   */\n  connect(params){\n    if(params){\n      console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\")\n      this.params = closure(params)\n    }\n    if(this.conn && !this.disconnecting){ return }\n    if(this.longPollFallbackMs && this.transport !== LongPoll){\n      this.connectWithFallback(LongPoll, this.longPollFallbackMs)\n    } else {\n      this.transportConnect()\n    }\n  }\n\n  /**\n   * Logs the message. Override `this.logger` for specialized logging. noops by default\n   * @param {string} kind\n   * @param {string} msg\n   * @param {Object} data\n   */\n  log(kind, msg, data){ this.logger && this.logger(kind, msg, data) }\n\n  /**\n   * Returns true if a logger has been set on this socket.\n   */\n  hasLogger(){ return this.logger !== null }\n\n  /**\n   * Registers callbacks for connection open events\n   *\n   * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n   *\n   * @param {Function} callback\n   */\n  onOpen(callback){\n    let ref = this.makeRef()\n    this.stateChangeCallbacks.open.push([ref, callback])\n    return ref\n  }\n\n  /**\n   * Registers callbacks for connection close events\n   * @param {Function} callback\n   */\n  onClose(callback){\n    let ref = this.makeRef()\n    this.stateChangeCallbacks.close.push([ref, callback])\n    return ref\n  }\n\n  /**\n   * Registers callbacks for connection error events\n   *\n   * @example socket.onError(function(error){ alert(\"An error occurred\") })\n   *\n   * @param {Function} callback\n   */\n  onError(callback){\n    let ref = this.makeRef()\n    this.stateChangeCallbacks.error.push([ref, callback])\n    return ref\n  }\n\n  /**\n   * Registers callbacks for connection message events\n   * @param {Function} callback\n   */\n  onMessage(callback){\n    let ref = this.makeRef()\n    this.stateChangeCallbacks.message.push([ref, callback])\n    return ref\n  }\n\n  /**\n   * Pings the server and invokes the callback with the RTT in milliseconds\n   * @param {Function} callback\n   *\n   * Returns true if the ping was pushed or false if unable to be pushed.\n   */\n  ping(callback){\n    if(!this.isConnected()){ return false }\n    let ref = this.makeRef()\n    let startTime = Date.now()\n    this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: ref})\n    let onMsgRef = this.onMessage(msg => {\n      if(msg.ref === ref){\n        this.off([onMsgRef])\n        callback(Date.now() - startTime)\n      }\n    })\n    return true\n  }\n\n  /**\n   * @private\n   *\n   * @param {Function}\n   */\n  transportName(transport){\n    // JavaScript minification, enabled by default in production in Phoenix\n    // projects, renames symbols to reduce code size.\n    // See https://esbuild.github.io/api/#keep-names.\n    // This helper ensures we return the correct name for the LongPoll transport\n    // even after minification. The other common transport is WebSocket, which\n    // is native to browsers and does not need special handling.\n    switch(transport){\n      case LongPoll: return \"LongPoll\"\n      default: return transport.name\n    }\n  }\n\n  /**\n   * @private\n   */\n  transportConnect(){\n    this.connectClock++\n    this.closeWasClean = false\n    let protocols = undefined\n    // Sec-WebSocket-Protocol based token\n    // (longpoll uses Authorization header instead)\n    if(this.authToken){\n      protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`]\n    }\n    this.conn = new this.transport(this.endPointURL(), protocols)\n    this.conn.binaryType = this.binaryType\n    this.conn.timeout = this.longpollerTimeout\n    this.conn.onopen = () => this.onConnOpen()\n    this.conn.onerror = error => this.onConnError(error)\n    this.conn.onmessage = event => this.onConnMessage(event)\n    this.conn.onclose = event => this.onConnClose(event)\n  }\n\n  getSession(key){ return this.sessionStore && this.sessionStore.getItem(key) }\n\n  storeSession(key, val){ this.sessionStore && this.sessionStore.setItem(key, val) }\n\n  connectWithFallback(fallbackTransport, fallbackThreshold = 2500){\n    clearTimeout(this.fallbackTimer)\n    let established = false\n    let primaryTransport = true\n    let openRef, errorRef\n    let fallbackTransportName = this.transportName(fallbackTransport)\n    let fallback = (reason) => {\n      this.log(\"transport\", `falling back to ${fallbackTransportName}...`, reason)\n      this.off([openRef, errorRef])\n      primaryTransport = false\n      this.replaceTransport(fallbackTransport)\n      this.transportConnect()\n    }\n    if(this.getSession(`phx:fallback:${fallbackTransportName}`)){ return fallback(\"memorized\") }\n\n    this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n\n    errorRef = this.onError(reason => {\n      this.log(\"transport\", \"error\", reason)\n      if(primaryTransport && !established){\n        clearTimeout(this.fallbackTimer)\n        fallback(reason)\n      }\n    })\n    if(this.fallbackRef){\n      this.off([this.fallbackRef])\n    }\n    this.fallbackRef = this.onOpen(() => {\n      established = true\n      if(!primaryTransport){\n        let fallbackTransportName = this.transportName(fallbackTransport)\n        // only memorize LP if we never connected to primary\n        if(!this.primaryPassedHealthCheck){ this.storeSession(`phx:fallback:${fallbackTransportName}`, \"true\") }\n        return this.log(\"transport\", `established ${fallbackTransportName} fallback`)\n      }\n      // if we've established primary, give the fallback a new period to attempt ping\n      clearTimeout(this.fallbackTimer)\n      this.fallbackTimer = setTimeout(fallback, fallbackThreshold)\n      this.ping(rtt => {\n        this.log(\"transport\", \"connected to primary after\", rtt)\n        this.primaryPassedHealthCheck = true\n        clearTimeout(this.fallbackTimer)\n      })\n    })\n    this.transportConnect()\n  }\n\n  clearHeartbeats(){\n    clearTimeout(this.heartbeatTimer)\n    clearTimeout(this.heartbeatTimeoutTimer)\n  }\n\n  onConnOpen(){\n    if(this.hasLogger()) this.log(\"transport\", `${this.transportName(this.transport)} connected to ${this.endPointURL()}`)\n    this.closeWasClean = false\n    this.disconnecting = false\n    this.establishedConnections++\n    this.flushSendBuffer()\n    this.reconnectTimer.reset()\n    this.resetHeartbeat()\n    this.stateChangeCallbacks.open.forEach(([, callback]) => callback())\n  }\n\n  /**\n   * @private\n   */\n\n  heartbeatTimeout(){\n    if(this.pendingHeartbeatRef){\n      this.pendingHeartbeatRef = null\n      if(this.hasLogger()){ this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\") }\n      this.triggerChanError()\n      this.closeWasClean = false\n      this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\")\n    }\n  }\n\n  resetHeartbeat(){\n    if(this.conn && this.conn.skipHeartbeat){ return }\n    this.pendingHeartbeatRef = null\n    this.clearHeartbeats()\n    this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n  }\n\n  teardown(callback, code, reason){\n    if(!this.conn){\n      return callback && callback()\n    }\n\n    // If someone calls connect before we finish tearing down,\n    // we create a new connection, but we still want to finish tearing down the old one.\n    const connToClose = this.conn\n\n    this.waitForBufferDone(connToClose, () => {\n      if(code){ connToClose.close(code, reason || \"\") } else { connToClose.close() }\n\n      this.waitForSocketClosed(connToClose, () => {\n        if(this.conn === connToClose){\n          this.conn.onopen = function (){ } // noop\n          this.conn.onerror = function (){ } // noop\n          this.conn.onmessage = function (){ } // noop\n          this.conn.onclose = function (){ } // noop\n          this.conn = null\n        }\n\n        callback && callback()\n      })\n    })\n  }\n\n  waitForBufferDone(conn, callback, tries = 1){\n    if(tries === 5 || !conn.bufferedAmount){\n      callback()\n      return\n    }\n\n    setTimeout(() => {\n      this.waitForBufferDone(conn, callback, tries + 1)\n    }, 150 * tries)\n  }\n\n  waitForSocketClosed(conn, callback, tries = 1){\n    if(tries === 5 || conn.readyState === SOCKET_STATES.closed){\n      callback()\n      return\n    }\n\n    setTimeout(() => {\n      this.waitForSocketClosed(conn, callback, tries + 1)\n    }, 150 * tries)\n  }\n\n  onConnClose(event){\n    if(this.conn) this.conn.onclose = () => {} // noop to prevent recursive calls in teardown\n    let closeCode = event && event.code\n    if(this.hasLogger()) this.log(\"transport\", \"close\", event)\n    this.triggerChanError()\n    this.clearHeartbeats()\n    if(!this.closeWasClean && closeCode !== 1000){\n      this.reconnectTimer.scheduleTimeout()\n    }\n    this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event))\n  }\n\n  /**\n   * @private\n   */\n  onConnError(error){\n    if(this.hasLogger()) this.log(\"transport\", error)\n    let transportBefore = this.transport\n    let establishedBefore = this.establishedConnections\n    this.stateChangeCallbacks.error.forEach(([, callback]) => {\n      callback(error, transportBefore, establishedBefore)\n    })\n    if(transportBefore === this.transport || establishedBefore > 0){\n      this.triggerChanError()\n    }\n  }\n\n  /**\n   * @private\n   */\n  triggerChanError(){\n    this.channels.forEach(channel => {\n      if(!(channel.isErrored() || channel.isLeaving() || channel.isClosed())){\n        channel.trigger(CHANNEL_EVENTS.error)\n      }\n    })\n  }\n\n  /**\n   * @returns {string}\n   */\n  connectionState(){\n    switch(this.conn && this.conn.readyState){\n      case SOCKET_STATES.connecting: return \"connecting\"\n      case SOCKET_STATES.open: return \"open\"\n      case SOCKET_STATES.closing: return \"closing\"\n      default: return \"closed\"\n    }\n  }\n\n  /**\n   * @returns {boolean}\n   */\n  isConnected(){ return this.connectionState() === \"open\" }\n\n  /**\n   * @private\n   *\n   * @param {Channel}\n   */\n  remove(channel){\n    this.off(channel.stateChangeRefs)\n    this.channels = this.channels.filter(c => c !== channel)\n  }\n\n  /**\n   * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n   *\n   * @param {refs} - list of refs returned by calls to\n   *                 `onOpen`, `onClose`, `onError,` and `onMessage`\n   */\n  off(refs){\n    for(let key in this.stateChangeCallbacks){\n      this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n        return refs.indexOf(ref) === -1\n      })\n    }\n  }\n\n  /**\n   * Initiates a new channel for the given topic\n   *\n   * @param {string} topic\n   * @param {Object} chanParams - Parameters for the channel\n   * @returns {Channel}\n   */\n  channel(topic, chanParams = {}){\n    let chan = new Channel(topic, chanParams, this)\n    this.channels.push(chan)\n    return chan\n  }\n\n  /**\n   * @param {Object} data\n   */\n  push(data){\n    if(this.hasLogger()){\n      let {topic, event, payload, ref, join_ref} = data\n      this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload)\n    }\n\n    if(this.isConnected()){\n      this.encode(data, result => this.conn.send(result))\n    } else {\n      this.sendBuffer.push(() => this.encode(data, result => this.conn.send(result)))\n    }\n  }\n\n  /**\n   * Return the next message ref, accounting for overflows\n   * @returns {string}\n   */\n  makeRef(){\n    let newRef = this.ref + 1\n    if(newRef === this.ref){ this.ref = 0 } else { this.ref = newRef }\n\n    return this.ref.toString()\n  }\n\n  sendHeartbeat(){\n    if(this.pendingHeartbeatRef && !this.isConnected()){ return }\n    this.pendingHeartbeatRef = this.makeRef()\n    this.push({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef})\n    this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs)\n  }\n\n  flushSendBuffer(){\n    if(this.isConnected() && this.sendBuffer.length > 0){\n      this.sendBuffer.forEach(callback => callback())\n      this.sendBuffer = []\n    }\n  }\n\n  onConnMessage(rawMessage){\n    this.decode(rawMessage.data, msg => {\n      let {topic, event, payload, ref, join_ref} = msg\n      if(ref && ref === this.pendingHeartbeatRef){\n        this.clearHeartbeats()\n        this.pendingHeartbeatRef = null\n        this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs)\n      }\n\n      if(this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload)\n\n      for(let i = 0; i < this.channels.length; i++){\n        const channel = this.channels[i]\n        if(!channel.isMember(topic, event, payload, join_ref)){ continue }\n        channel.trigger(event, payload, ref, join_ref)\n      }\n\n      for(let i = 0; i < this.stateChangeCallbacks.message.length; i++){\n        let [, callback] = this.stateChangeCallbacks.message[i]\n        callback(msg)\n      }\n    })\n  }\n\n  leaveOpenTopic(topic){\n    let dupChannel = this.channels.find(c => c.topic === topic && (c.isJoined() || c.isJoining()))\n    if(dupChannel){\n      if(this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`)\n      dupChannel.leave()\n    }\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/timer.js",
    "content": "/**\n *\n * Creates a timer that accepts a `timerCalc` function to perform\n * calculated timeout retries, such as exponential backoff.\n *\n * @example\n * let reconnectTimer = new Timer(() => this.connect(), function(tries){\n *   return [1000, 5000, 10000][tries - 1] || 10000\n * })\n * reconnectTimer.scheduleTimeout() // fires after 1000\n * reconnectTimer.scheduleTimeout() // fires after 5000\n * reconnectTimer.reset()\n * reconnectTimer.scheduleTimeout() // fires after 1000\n *\n * @param {Function} callback\n * @param {Function} timerCalc\n */\nexport default class Timer {\n  constructor(callback, timerCalc){\n    this.callback = callback\n    this.timerCalc = timerCalc\n    this.timer = null\n    this.tries = 0\n  }\n\n  reset(){\n    this.tries = 0\n    clearTimeout(this.timer)\n  }\n\n  /**\n   * Cancels any previous scheduleTimeout and schedules callback\n   */\n  scheduleTimeout(){\n    clearTimeout(this.timer)\n\n    this.timer = setTimeout(() => {\n      this.tries = this.tries + 1\n      this.callback()\n    }, this.timerCalc(this.tries + 1))\n  }\n}\n"
  },
  {
    "path": "assets/js/phoenix/utils.js",
    "content": "// wraps value in closure or returns closure\nexport let closure = (value) => {\n  if(typeof value === \"function\"){\n    return value\n  } else {\n    let closure = function (){ return value }\n    return closure\n  }\n}\n"
  },
  {
    "path": "assets/test/channel_test.js",
    "content": "import {jest} from \"@jest/globals\"\nimport {Channel, Socket} from \"../js/phoenix\"\n\nlet channel, socket\n\nconst defaultRef = 1\nconst defaultTimeout = 10000\n\nclass WSMock {\n  constructor(url, protocols){\n    this.url = url\n    this.protocols = protocols\n  }\n  close(){}\n  send(){}\n}\n\ndescribe(\"with transport\", function (){\n  beforeAll(function (){\n    global.WebSocket = WSMock\n  })\n\n  afterAll(function (){\n    global.WebSocket = null\n  })\n\n  describe(\"constructor\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/\", {timeout: 1234})\n    })\n\n    it(\"sets defaults\", function (){\n      channel = new Channel(\"topic\", {one: \"two\"}, socket)\n\n      expect(channel.state).toBe(\"closed\")\n      expect(channel.topic).toBe(\"topic\")\n      expect(channel.params()).toEqual({one: \"two\"})\n      expect(channel.socket).toBe(socket)\n      expect(channel.timeout).toBe(1234)\n      expect(channel.joinedOnce).toBe(false)\n      expect(channel.joinPush).toBeTruthy()\n      expect(channel.pushBuffer).toEqual([])\n    })\n\n    it(\"sets up joinPush object with literal params\", function (){\n      channel = new Channel(\"topic\", {one: \"two\"}, socket)\n      const joinPush = channel.joinPush\n\n      expect(joinPush.channel).toBe(channel)\n      expect(joinPush.payload()).toEqual({one: \"two\"})\n      expect(joinPush.event).toBe(\"phx_join\")\n      expect(joinPush.timeout).toBe(1234)\n    })\n\n    it(\"sets up joinPush object with closure params\", function (){\n      channel = new Channel(\"topic\", () => ({one: \"two\"}), socket)\n      const joinPush = channel.joinPush\n\n      expect(joinPush.channel).toBe(channel)\n      expect(joinPush.payload()).toEqual({one: \"two\"})\n      expect(joinPush.event).toBe(\"phx_join\")\n      expect(joinPush.timeout).toBe(1234)\n    })\n\n    it(\"sets subprotocols when authToken is provided\", function (){\n      const authToken = \"1234\"\n      const socket = new Socket(\"/socket\", {authToken})\n      \n      socket.connect()\n      expect(socket.conn.protocols).toEqual([\"phoenix\", \"base64url.bearer.phx.MTIzNA\"])\n    })\n  })\n\n  describe(\"updating join params\", function (){\n    it(\"can update the join params\", function (){\n      let counter = 0\n      let params = () => ({value: counter})\n      socket = {timeout: 1234, onError: function (){}, onOpen: function (){}}\n\n      channel = new Channel(\"topic\", params, socket)\n      const joinPush = channel.joinPush\n\n      expect(joinPush.channel).toBe(channel)\n      expect(joinPush.payload()).toEqual({value: 0})\n      expect(joinPush.event).toBe(\"phx_join\")\n      expect(joinPush.timeout).toBe(1234)\n\n      counter++\n\n      expect(joinPush.channel).toBe(channel)\n      expect(joinPush.payload()).toEqual({value: 1})\n      expect(channel.params()).toEqual({value: 1})\n      expect(joinPush.event).toBe(\"phx_join\")\n      expect(joinPush.timeout).toBe(1234)\n    })\n  })\n\n  describe(\"join\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n    })\n\n    it(\"sets state to joining\", function (){\n      channel.join()\n\n      expect(channel.state).toBe(\"joining\")\n    })\n\n    it(\"sets joinedOnce to true\", function (){\n      expect(channel.joinedOnce).toBe(false)\n\n      channel.join()\n\n      expect(channel.joinedOnce).toBe(true)\n    })\n\n    it(\"throws if attempting to join multiple times\", function (){\n      channel.join()\n\n      expect(() => channel.join()).toThrow(/^tried to join multiple times/)\n    })\n\n    it(\"triggers socket push with channel params\", function (){\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n      const spy = jest.spyOn(socket, \"push\")\n\n      channel.join()\n\n      expect(spy).toHaveBeenCalledTimes(1)\n      expect(spy).toHaveBeenCalledWith({\n        topic: \"topic\",\n        event: \"phx_join\",\n        payload: {one: \"two\"},\n        ref: defaultRef,\n        join_ref: channel.joinRef(),\n      })\n    })\n\n    it(\"can set timeout on joinPush\", function (){\n      const newTimeout = 2000\n      const joinPush = channel.joinPush\n\n      expect(joinPush.timeout).toBe(defaultTimeout)\n\n      channel.join(newTimeout)\n\n      expect(joinPush.timeout).toBe(newTimeout)\n    })\n\n    it(\"leaves existing duplicate topic on new join\", function (done){\n      channel.join().receive(\"ok\", () => {\n        let newChannel = socket.channel(\"topic\")\n        expect(channel.isJoined()).toBe(true)\n        newChannel.join()\n        expect(channel.isJoined()).toBe(false)\n        done()\n      })\n\n      channel.joinPush.trigger(\"ok\", {})\n    })\n\n    describe(\"timeout behavior\", function (){\n      let joinPush\n\n      const helpers = {\n        receiveSocketOpen(){\n          jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n          socket.onConnOpen()\n        },\n      }\n\n      beforeEach(function (){\n        jest.useFakeTimers()\n        joinPush = channel.joinPush\n      })\n\n      afterEach(function (){\n        jest.useRealTimers()\n      })\n\n      it(\"succeeds before timeout\", function (){\n        const spy = jest.spyOn(socket, \"push\")\n        const timeout = joinPush.timeout\n\n        socket.connect()\n        helpers.receiveSocketOpen()\n\n        channel.join()\n        expect(spy).toHaveBeenCalledTimes(1)\n\n        expect(channel.timeout).toBe(10000)\n        jest.advanceTimersByTime(100)\n\n        joinPush.trigger(\"ok\", {})\n\n        expect(channel.state).toBe(\"joined\")\n\n        jest.advanceTimersByTime(timeout)\n        expect(spy).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"retries with backoff after timeout\", function (){\n        const spy = jest.spyOn(socket, \"push\")\n        const timeoutSpy = jest.fn()\n        const timeout = joinPush.timeout\n\n        socket.connect()\n        helpers.receiveSocketOpen()\n\n        channel.join().receive(\"timeout\", timeoutSpy)\n\n        expect(spy).toHaveBeenCalledTimes(1)\n        expect(timeoutSpy).toHaveBeenCalledTimes(0)\n\n        jest.advanceTimersByTime(timeout)\n        expect(spy).toHaveBeenCalledTimes(2) // leave pushed to server\n        expect(timeoutSpy).toHaveBeenCalledTimes(1)\n\n        jest.advanceTimersByTime(timeout + 1000)\n        expect(spy).toHaveBeenCalledTimes(4) // leave + rejoin\n        expect(timeoutSpy).toHaveBeenCalledTimes(2)\n\n        jest.advanceTimersByTime(10000)\n        joinPush.trigger(\"ok\", {})\n        expect(spy).toHaveBeenCalledTimes(6)\n        expect(channel.state).toBe(\"joined\")\n      })\n\n      it(\"with socket and join delay\", function (){\n        const spy = jest.spyOn(socket, \"push\")\n        jest.useFakeTimers()\n        const joinPush = channel.joinPush\n\n        channel.join()\n        expect(spy).toHaveBeenCalledTimes(1)\n\n        // open socket after delay\n        jest.advanceTimersByTime(9000)\n\n        expect(spy).toHaveBeenCalledTimes(1)\n\n        // join request returns between timeouts\n        jest.advanceTimersByTime(1000)\n        socket.connect()\n\n        expect(channel.state).toBe(\"errored\")\n\n        helpers.receiveSocketOpen()\n        joinPush.trigger(\"ok\", {})\n\n        // join request succeeds after delay\n        jest.advanceTimersByTime(1000)\n\n        expect(channel.state).toBe(\"joined\")\n\n        expect(spy).toHaveBeenCalledTimes(3) // leave pushed to server\n      })\n\n      it(\"with socket delay only\", function (){\n        jest.useFakeTimers()\n        const joinPush = channel.joinPush\n\n        channel.join()\n\n        expect(channel.state).toBe(\"joining\")\n\n        // connect socket after delay\n        jest.advanceTimersByTime(6000)\n        socket.connect()\n\n        // open socket after delay\n        jest.advanceTimersByTime(5000)\n        helpers.receiveSocketOpen()\n        joinPush.trigger(\"ok\", {})\n\n        joinPush.trigger(\"ok\", {})\n        expect(channel.state).toBe(\"joined\")\n      })\n    })\n  })\n\n  describe(\"joinPush\", function (){\n    let joinPush\n    let response\n\n    const helpers = {\n      receiveOk(){\n        jest.advanceTimersByTime(joinPush.timeout / 2) // before timeout\n        return joinPush.channel.trigger(\"phx_reply\", {status: \"ok\", response: response}, joinPush.ref, joinPush.ref)\n        // return joinPush.trigger(\"ok\", response)\n      },\n\n      receiveTimeout(){\n        jest.advanceTimersByTime(joinPush.timeout * 2) // after timeout\n      },\n\n      receiveError(){\n        jest.advanceTimersByTime(joinPush.timeout / 2) // before timeout\n        return joinPush.trigger(\"error\", response)\n      },\n\n      getBindings(event){\n        return channel.bindings.filter(bind => bind.event === event)\n      },\n    }\n\n    beforeEach(function (){\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      jest.spyOn(socket, \"push\").mockReturnValue(true)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n      joinPush = channel.joinPush\n\n      channel.join()\n    })\n\n    afterEach(function (){\n      jest.useRealTimers()\n    })\n\n    describe(\"receives 'ok'\", function (){\n      beforeEach(function (){\n        response = {chan: \"reply\"}\n      })\n\n      it(\"sets channel state to joined\", function (){\n        expect(channel.state).not.toBe(\"joined\")\n\n        helpers.receiveOk()\n\n        expect(channel.state).toBe(\"joined\")\n      })\n\n      it(\"triggers receive('ok') callback after ok response\", function (){\n        const spyOk = jest.fn()\n\n        joinPush.receive(\"ok\", spyOk)\n\n        helpers.receiveOk()\n\n        expect(spyOk).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"triggers receive('ok') callback if ok response already received\", function (){\n        const spyOk = jest.fn()\n\n        helpers.receiveOk()\n\n        joinPush.receive(\"ok\", spyOk)\n\n        expect(spyOk).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"does not trigger other receive callbacks after ok response\", function (){\n        const spyError = jest.fn()\n        const spyTimeout = jest.fn()\n\n        joinPush.receive(\"error\", spyError).receive(\"timeout\", spyTimeout)\n\n        helpers.receiveOk()\n        jest.advanceTimersByTime(channel.timeout * 2) // attempt timeout\n\n        expect(spyError).not.toHaveBeenCalled()\n        expect(spyTimeout).not.toHaveBeenCalled()\n      })\n\n      it(\"clears timeoutTimer\", function (){\n        expect(joinPush.timeoutTimer).toBeTruthy()\n\n        helpers.receiveOk()\n\n        expect(joinPush.timeoutTimer).toBeNull()\n      })\n\n      it(\"sets receivedResp\", function (){\n        expect(joinPush.receivedResp).toBeNull()\n\n        helpers.receiveOk()\n\n        expect(joinPush.receivedResp).toEqual({status: \"ok\", response})\n      })\n\n      it(\"removes channel bindings\", function (){\n        let bindings = helpers.getBindings(\"chan_reply_3\")\n        expect(bindings.length).toBe(1)\n\n        helpers.receiveOk()\n\n        bindings = helpers.getBindings(\"chan_reply_3\")\n        expect(bindings.length).toBe(0)\n      })\n\n      it(\"resets channel rejoinTimer\", function (){\n        expect(channel.rejoinTimer).toBeTruthy()\n\n        const spy = jest.spyOn(channel.rejoinTimer, \"reset\")\n\n        helpers.receiveOk()\n\n        expect(spy).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"sends and empties channel's buffered pushEvents\", function (done){\n        const pushEvent = {send(){}}\n        const spy = jest.spyOn(pushEvent, \"send\")\n\n        channel.pushBuffer.push(pushEvent)\n\n        expect(channel.state).toBe(\"joining\")\n        joinPush.receive(\"ok\", () => {\n          expect(spy).toHaveBeenCalledTimes(1)\n          expect(channel.pushBuffer.length).toBe(0)\n          done()\n        })\n        helpers.receiveOk()\n      })\n    })\n\n    describe(\"receives 'timeout'\", function (){\n      it(\"sets channel state to errored\", function (done){\n        joinPush.receive(\"timeout\", () => {\n          expect(channel.state).toBe(\"errored\")\n          done()\n        })\n\n        helpers.receiveTimeout()\n      })\n\n      it(\"triggers receive('timeout') callback after ok response\", function (){\n        const spyTimeout = jest.fn()\n\n        joinPush.receive(\"timeout\", spyTimeout)\n\n        helpers.receiveTimeout()\n\n        expect(spyTimeout).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"does not trigger other receive callbacks after timeout response\", function (done){\n        const spyOk = jest.fn()\n        const spyError = jest.fn()\n        jest.spyOn(channel.rejoinTimer, \"scheduleTimeout\").mockReturnValue(true)\n\n        channel.test = true\n        joinPush.receive(\"ok\", spyOk).receive(\"error\", spyError).receive(\"timeout\", () => {\n          expect(spyOk).not.toHaveBeenCalled()\n          expect(spyError).not.toHaveBeenCalled()\n          done()\n        })\n\n        helpers.receiveTimeout()\n        helpers.receiveOk()\n      })\n\n      it(\"schedules rejoinTimer timeout\", function (){\n        expect(channel.rejoinTimer).toBeTruthy()\n\n        const spy = jest.spyOn(channel.rejoinTimer, \"scheduleTimeout\")\n\n        helpers.receiveTimeout()\n\n        expect(spy).toHaveBeenCalled() // TODO why called multiple times?\n      })\n    })\n\n    describe(\"receives 'error'\", function (){\n      beforeEach(function (){\n        response = {chan: \"fail\"}\n      })\n\n      it(\"triggers receive('error') callback after error response\", function (){\n        const spyError = jest.fn()\n\n        expect(channel.state).toBe(\"joining\")\n        joinPush.receive(\"error\", spyError)\n\n        helpers.receiveError()\n        joinPush.trigger(\"error\", {})\n\n        expect(spyError).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"triggers receive('error') callback if error response already received\", function (){\n        const spyError = jest.fn()\n\n        helpers.receiveError()\n\n        joinPush.receive(\"error\", spyError)\n\n        expect(spyError).toHaveBeenCalledTimes(1)\n      })\n\n      it(\"does not trigger other receive callbacks after error response\", function (){\n        const spyOk = jest.fn()\n        const spyError = jest.fn()\n        const spyTimeout = jest.fn()\n\n        joinPush.receive(\"ok\", spyOk).receive(\"error\", () => {\n          spyError()\n          channel.leave()\n        }).receive(\"timeout\", spyTimeout)\n\n        helpers.receiveError()\n        jest.advanceTimersByTime(channel.timeout * 2) // attempt timeout\n\n        expect(spyError).toHaveBeenCalledTimes(1)\n        expect(spyOk).not.toHaveBeenCalled()\n        expect(spyTimeout).not.toHaveBeenCalled()\n      })\n\n      it(\"clears timeoutTimer\", function (){\n        expect(joinPush.timeoutTimer).toBeTruthy()\n\n        helpers.receiveError()\n\n        expect(joinPush.timeoutTimer).toBeNull()\n      })\n\n      it(\"sets receivedResp with error trigger after binding\", function (done){\n        expect(joinPush.receivedResp).toBeNull()\n\n        joinPush.receive(\"error\", resp => {\n          expect(resp).toEqual(response)\n          done()\n        })\n\n        helpers.receiveError()\n      })\n\n      it(\"sets receivedResp with error trigger before binding\", function (done){\n        expect(joinPush.receivedResp).toBeNull()\n\n        helpers.receiveError()\n        joinPush.receive(\"error\", resp => {\n          expect(resp).toEqual(response)\n          done()\n        })\n      })\n\n      it(\"does not set channel state to joined\", function (){\n        helpers.receiveError()\n\n        expect(channel.state).toBe(\"errored\")\n      })\n\n      it(\"does not trigger channel's buffered pushEvents\", function (){\n        const pushEvent = {send: () => {}}\n        const spy = jest.spyOn(pushEvent, \"send\")\n\n        channel.pushBuffer.push(pushEvent)\n\n        helpers.receiveError()\n\n        expect(spy).not.toHaveBeenCalled()\n        expect(channel.pushBuffer.length).toBe(1)\n      })\n    })\n  })\n\n  describe(\"onError\", function (){\n    let joinPush\n\n    beforeEach(function (){\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      jest.spyOn(socket, \"push\").mockReturnValue(true)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n\n      joinPush = channel.joinPush\n\n      channel.join()\n      joinPush.trigger(\"ok\", {})\n    })\n\n    afterEach(function (){\n      jest.useRealTimers()\n    })\n\n    it(\"sets state to 'errored'\", function (){\n      expect(channel.state).not.toBe(\"errored\")\n\n      channel.trigger(\"phx_error\")\n\n      expect(channel.state).toBe(\"errored\")\n    })\n\n    it(\"does not trigger redundant errors during backoff\", function (){\n      const spy = jest.spyOn(joinPush, \"send\").mockImplementation(() => {})\n\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      channel.trigger(\"phx_error\")\n\n      jest.advanceTimersByTime(1000)\n      expect(spy).toHaveBeenCalledTimes(1)\n\n      joinPush.trigger(\"error\", {})\n\n      jest.advanceTimersByTime(10000)\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"does not rejoin if channel leaving\", function (){\n      channel.state = \"leaving\"\n\n      const spy = jest.spyOn(joinPush, \"send\")\n\n      socket.onConnError({})\n\n      jest.advanceTimersByTime(1000)\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      jest.advanceTimersByTime(2000)\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      expect(channel.state).toBe(\"leaving\")\n    })\n\n    it(\"does not rejoin if channel closed\", function (){\n      channel.state = \"closed\"\n\n      const spy = jest.spyOn(joinPush, \"send\")\n\n      socket.onConnError({})\n\n      jest.advanceTimersByTime(1000)\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      jest.advanceTimersByTime(2000)\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      expect(channel.state).toBe(\"closed\")\n    })\n\n    it(\"triggers additional callbacks after join\", function (){\n      const spy = jest.fn()\n      channel.onError(spy)\n      joinPush.trigger(\"ok\", {})\n\n      expect(channel.state).toBe(\"joined\")\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      channel.trigger(\"phx_error\")\n\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe(\"onClose\", function (){\n    let joinPush\n\n    beforeEach(function (){\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      jest.spyOn(socket, \"push\").mockReturnValue(true)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n\n      joinPush = channel.joinPush\n\n      channel.join()\n    })\n\n    afterEach(function (){\n      jest.useRealTimers()\n    })\n\n    it(\"sets state to 'closed'\", function (){\n      expect(channel.state).not.toBe(\"closed\")\n\n      channel.trigger(\"phx_close\")\n\n      expect(channel.state).toBe(\"closed\")\n    })\n\n    it(\"does not rejoin\", function (){\n      const spy = jest.spyOn(joinPush, \"send\")\n\n      channel.trigger(\"phx_close\")\n\n      jest.advanceTimersByTime(1000)\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      jest.advanceTimersByTime(2000)\n      expect(spy).toHaveBeenCalledTimes(0)\n    })\n\n    it(\"triggers additional callbacks\", function (){\n      const spy = jest.fn()\n      channel.onClose(spy)\n\n      expect(spy).toHaveBeenCalledTimes(0)\n\n      channel.trigger(\"phx_close\")\n\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"removes channel from socket\", function (){\n      expect(socket.channels.length).toBe(1)\n      expect(socket.channels[0]).toBe(channel)\n\n      channel.trigger(\"phx_close\")\n\n      expect(socket.channels.length).toBe(0)\n    })\n  })\n\n  describe(\"onMessage\", function (){\n    it(\"returns payload by default\", function (){\n      socket = new Socket(\"/socket\")\n      channel = socket.channel(\"topic\", {one: \"two\"})\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n      const payload = channel.onMessage(\"event\", {one: \"two\"}, defaultRef)\n\n      expect(payload).toEqual({one: \"two\"})\n    })\n  })\n\n  describe(\"canPush\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n    })\n\n    it(\"returns true when socket connected and channel joined\", function (){\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      channel.state = \"joined\"\n\n      expect(channel.canPush()).toBe(true)\n    })\n\n    it(\"otherwise returns false\", function (){\n      const isConnectedStub = jest.spyOn(socket, \"isConnected\")\n\n      isConnectedStub.mockReturnValue(false)\n      channel.state = \"joined\"\n\n      expect(channel.canPush()).toBe(false)\n\n      isConnectedStub.mockReturnValue(true)\n      channel.state = \"joining\"\n\n      expect(channel.canPush()).toBe(false)\n\n      isConnectedStub.mockReturnValue(false)\n      channel.state = \"joining\"\n\n      expect(channel.canPush()).toBe(false)\n    })\n  })\n\n  describe(\"on\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n    })\n\n    it(\"sets up callback for event\", function (){\n      const spy = jest.fn()\n\n      channel.trigger(\"event\", {}, defaultRef)\n      expect(spy).not.toHaveBeenCalled()\n\n      channel.on(\"event\", spy)\n\n      channel.trigger(\"event\", {}, defaultRef)\n\n      expect(spy).toHaveBeenCalled()\n    })\n\n    it(\"other event callbacks are ignored\", function (){\n      const spy = jest.fn()\n      const ignoredSpy = jest.fn()\n\n      channel.trigger(\"event\", {}, defaultRef)\n\n      expect(ignoredSpy).not.toHaveBeenCalled()\n\n      channel.on(\"event\", spy)\n\n      channel.trigger(\"event\", {}, defaultRef)\n\n      expect(ignoredSpy).not.toHaveBeenCalled()\n    })\n\n    it(\"generates unique refs for callbacks\", function (){\n      const ref1 = channel.on(\"event1\", () => 0)\n      const ref2 = channel.on(\"event2\", () => 0)\n      expect(ref1 + 1).toBe(ref2)\n    })\n\n    it(\"calls all callbacks for event if they modified during event processing\", function (){\n      const spy = jest.fn()\n\n      const ref = channel.on(\"event\", () => {\n        channel.off(\"event\", ref)\n      })\n      channel.on(\"event\", spy)\n\n      channel.trigger(\"event\", {}, defaultRef)\n\n      expect(spy).toHaveBeenCalled()\n    })\n  })\n\n  describe(\"off\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n    })\n\n    it(\"removes all callbacks for event\", function (){\n      const spy1 = jest.fn()\n      const spy2 = jest.fn()\n      const spy3 = jest.fn()\n\n      channel.on(\"event\", spy1)\n      channel.on(\"event\", spy2)\n      channel.on(\"other\", spy3)\n\n      channel.off(\"event\")\n\n      channel.trigger(\"event\", {}, defaultRef)\n      channel.trigger(\"other\", {}, defaultRef)\n\n      expect(spy1).not.toHaveBeenCalled()\n      expect(spy2).not.toHaveBeenCalled()\n      expect(spy3).toHaveBeenCalled()\n    })\n\n    it(\"removes callback by its ref\", function (){\n      const spy1 = jest.fn()\n      const spy2 = jest.fn()\n\n      const ref1 = channel.on(\"event\", spy1)\n      const _ref2 = channel.on(\"event\", spy2)\n\n      channel.off(\"event\", ref1)\n      channel.trigger(\"event\", {}, defaultRef)\n\n      expect(spy1).not.toHaveBeenCalled()\n      expect(spy2).toHaveBeenCalled()\n    })\n  })\n\n  describe(\"push\", function (){\n    let joinPush\n    let socketSpy\n\n    const pushParams = (channel) => {\n      return {\n        topic: \"topic\",\n        event: \"event\",\n        payload: {foo: \"bar\"},\n        join_ref: channel.joinRef(),\n        ref: defaultRef,\n      }\n    }\n\n    beforeEach(function (){\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      socketSpy = jest.spyOn(socket, \"push\").mockReturnValue(undefined)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n    })\n\n    afterEach(function (){\n      jest.useRealTimers()\n    })\n\n    it(\"sends push event when successfully joined\", function (){\n      channel.join().trigger(\"ok\", {})\n      channel.push(\"event\", {foo: \"bar\"})\n\n      expect(socketSpy).toHaveBeenCalledWith(pushParams(channel))\n    })\n\n    it(\"enqueues push event to be sent once join has succeeded\", function (){\n      joinPush = channel.join()\n      channel.push(\"event\", {foo: \"bar\"})\n\n      expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel))\n\n      jest.advanceTimersByTime(channel.timeout / 2)\n      joinPush.trigger(\"ok\", {})\n\n      expect(socketSpy).toHaveBeenCalledWith(pushParams(channel))\n    })\n\n    it(\"does not push if channel join times out\", function (){\n      joinPush = channel.join()\n      channel.push(\"event\", {foo: \"bar\"})\n\n      expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel))\n\n      jest.advanceTimersByTime(channel.timeout * 2)\n      joinPush.trigger(\"ok\", {})\n\n      expect(socketSpy).not.toHaveBeenCalledWith(pushParams(channel))\n    })\n\n    it(\"uses channel timeout by default\", function (){\n      const timeoutSpy = jest.fn()\n      channel.join().trigger(\"ok\", {})\n\n      channel.push(\"event\", {foo: \"bar\"}).receive(\"timeout\", timeoutSpy)\n\n      jest.advanceTimersByTime(channel.timeout / 2)\n      expect(timeoutSpy).not.toHaveBeenCalled()\n\n      jest.advanceTimersByTime(channel.timeout)\n      expect(timeoutSpy).toHaveBeenCalled()\n    })\n\n    it(\"accepts timeout arg\", function (){\n      const timeoutSpy = jest.fn()\n      channel.join().trigger(\"ok\", {})\n\n      channel.push(\"event\", {foo: \"bar\"}, channel.timeout * 2).receive(\"timeout\", timeoutSpy)\n\n      jest.advanceTimersByTime(channel.timeout)\n      expect(timeoutSpy).not.toHaveBeenCalled()\n\n      jest.advanceTimersByTime(channel.timeout * 2)\n      expect(timeoutSpy).toHaveBeenCalled()\n    })\n\n    it(\"does not time out after receiving 'ok'\", function (){\n      channel.join().trigger(\"ok\", {})\n      const timeoutSpy = jest.fn()\n      const push = channel.push(\"event\", {foo: \"bar\"})\n      push.receive(\"timeout\", timeoutSpy)\n\n      jest.advanceTimersByTime(push.timeout / 2)\n      expect(timeoutSpy).not.toHaveBeenCalled()\n\n      push.trigger(\"ok\", {})\n\n      jest.advanceTimersByTime(push.timeout)\n      expect(timeoutSpy).not.toHaveBeenCalled()\n    })\n\n    it(\"throws if channel has not been joined\", function (){\n      expect(() => channel.push(\"event\", {})).toThrow(/^tried to push.*before joining/)\n    })\n  })\n\n  describe(\"leave\", function (){\n    let socketSpy\n\n    beforeEach(function (){\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {timeout: defaultTimeout})\n      jest.spyOn(socket, \"isConnected\").mockReturnValue(true)\n      socketSpy = jest.spyOn(socket, \"push\").mockReturnValue(undefined)\n\n      channel = socket.channel(\"topic\", {one: \"two\"})\n      channel.join().trigger(\"ok\", {})\n    })\n\n    afterEach(function (){\n      jest.useRealTimers()\n    })\n\n    it(\"unsubscribes from server events\", function (){\n      jest.spyOn(socket, \"makeRef\").mockReturnValue(defaultRef)\n      const joinRef = channel.joinRef()\n\n      channel.leave()\n\n      expect(socketSpy).toHaveBeenCalledWith({\n        topic: \"topic\",\n        event: \"phx_leave\",\n        payload: {},\n        ref: defaultRef,\n        join_ref: joinRef,\n      })\n    })\n\n    it(\"closes channel on 'ok' from server\", function (){\n      const anotherChannel = socket.channel(\"another\", {three: \"four\"})\n      expect(socket.channels.length).toBe(2)\n\n      channel.leave().trigger(\"ok\", {})\n\n      expect(socket.channels.length).toBe(1)\n      expect(socket.channels[0]).toBe(anotherChannel)\n    })\n\n    it(\"sets state to closed on 'ok' event\", function (){\n      expect(channel.state).not.toBe(\"closed\")\n\n      channel.leave().trigger(\"ok\", {})\n\n      expect(channel.state).toBe(\"closed\")\n    })\n\n    // TODO - the following tests are skipped until Channel.leave\n    // behavior can be fixed; currently, 'ok' is triggered immediately\n    // within Channel.leave so timeout callbacks are never reached\n    //\n    it.skip(\"sets state to leaving initially\", function (){\n      expect(channel.state).not.toBe(\"leaving\")\n\n      channel.leave()\n\n      expect(channel.state).toBe(\"leaving\")\n    })\n\n    it.skip(\"closes channel on 'timeout'\", function (){\n      channel.leave()\n\n      jest.advanceTimersByTime(channel.timeout)\n\n      expect(channel.state).toBe(\"closed\")\n    })\n\n    it.skip(\"accepts timeout arg\", function (){\n      channel.leave(channel.timeout * 2)\n\n      jest.advanceTimersByTime(channel.timeout)\n\n      expect(channel.state).toBe(\"leaving\")\n\n      jest.advanceTimersByTime(channel.timeout * 2)\n\n      expect(channel.state).toBe(\"closed\")\n    })\n  })\n})\n"
  },
  {
    "path": "assets/test/longpoll_test.js",
    "content": "import {jest} from \"@jest/globals\"\nimport {LongPoll} from \"../js/phoenix\"\nimport {Socket} from \"../js/phoenix\"\nimport {AUTH_TOKEN_PREFIX} from \"../js/phoenix/constants\"\nimport Ajax from \"../js/phoenix/ajax\"\n\ndescribe(\"LongPoll\", () => {\n  let originalXHR\n\n  beforeEach(() => {\n    originalXHR = global.XMLHttpRequest\n    \n    // Mock XMLHttpRequest\n    const mockOpen = jest.fn()\n    const mockSend = jest.fn()\n    const mockAbort = jest.fn()\n    const mockSetRequestHeader = jest.fn()\n    \n    global.XMLHttpRequest = jest.fn(() => ({\n      open: mockOpen,\n      send: mockSend,\n      abort: mockAbort,\n      setRequestHeader: mockSetRequestHeader,\n      readyState: 4,\n      status: 200,\n      responseText: JSON.stringify({status: 200, token: \"token123\", messages: []}),\n      onreadystatechange: null,\n    }))\n\n    // Spy on Ajax.request\n    jest.spyOn(Ajax, \"request\").mockImplementation(() => {\n      return {abort: jest.fn()}\n    })\n  })\n\n  afterEach(() => {\n    global.XMLHttpRequest = originalXHR\n    jest.restoreAllMocks()\n  })\n\n  describe(\"constructor\", () => {\n    it(\"should handle undefined protocols\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", undefined)\n      \n      // Verify longpoll was initialized correctly without error\n      expect(longpoll.pollEndpoint).toBe(\"http://localhost/socket/longpoll\")\n      expect(longpoll.authToken).toBeUndefined()\n      expect(longpoll.readyState).toBe(0) // connecting\n    })\n\n    it(\"should handle null protocols\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", null)\n      \n      // Verify longpoll was initialized correctly without error\n      expect(longpoll.pollEndpoint).toBe(\"http://localhost/socket/longpoll\")\n      expect(longpoll.authToken).toBeUndefined()\n      expect(longpoll.readyState).toBe(0) // connecting\n    })\n\n    it(\"should handle empty array protocols\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", [])\n      \n      // Verify longpoll was initialized correctly without error\n      expect(longpoll.pollEndpoint).toBe(\"http://localhost/socket/longpoll\")\n      expect(longpoll.authToken).toBeUndefined()\n      expect(longpoll.readyState).toBe(0) // connecting\n    })\n\n    it(\"should extract authToken when valid protocols are provided\", () => {\n      const authToken = \"my-auth-token\"\n      const encodedToken = btoa(authToken)\n      const protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${encodedToken}`]\n      \n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", protocols)\n      \n      // Verify auth token was extracted correctly\n      expect(longpoll.authToken).toBe(authToken)\n    })\n  })\n\n  describe(\"poll\", () => {\n    it(\"should include auth token in headers when present\", () => {\n      const authToken = \"my-auth-token\"\n      const encodedToken = btoa(authToken)\n      const protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${encodedToken}`]\n\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", protocols)\n      longpoll.timeout = 1000\n      longpoll.poll()\n\n      // Verify Ajax.request was called with the correct headers\n      expect(Ajax.request).toHaveBeenCalledWith(\n        \"GET\",\n        expect.any(String),\n        {\"Accept\": \"application/json\", \"X-Phoenix-AuthToken\": authToken},\n        null,\n        expect.any(Number),\n        expect.any(Function),\n        expect.any(Function)\n      )\n    })\n\n    it(\"should not include auth token in headers when not present\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", undefined)\n      longpoll.timeout = 1000\n      longpoll.poll()\n\n      // Verify Ajax.request was called without auth token header\n      expect(Ajax.request).toHaveBeenCalledWith(\n        \"GET\",\n        expect.any(String),\n        {\"Accept\": \"application/json\"},\n        null,\n        expect.any(Number),\n        expect.any(Function),\n        expect.any(Function)\n      )\n    })\n\n    it(\"should treat 410 as error when token already exists\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", undefined)\n      longpoll.timeout = 1000\n      longpoll.token = \"existing-token\"\n\n      const mockOnerror = jest.fn()\n      const mockCloseAndRetry = jest.fn()\n      longpoll.onerror = mockOnerror\n      longpoll.closeAndRetry = mockCloseAndRetry\n\n      Ajax.request.mockImplementation((method, url, headers, body, timeout, ontimeout, callback) => {\n        callback({status: 410, token: \"new-token\", messages: []})\n        return {abort: jest.fn()}\n      })\n\n      longpoll.poll()\n\n      expect(mockOnerror).toHaveBeenCalledWith(410)\n      expect(mockCloseAndRetry).toHaveBeenCalledWith(3410, \"session_gone\", false)\n    })\n  })\n\n  describe(\"batchSend\", () => {\n    it(\"should send with correct content-type header format\", () => {\n      const longpoll = new LongPoll(\"http://localhost/socket/longpoll\", undefined)\n      longpoll.timeout = 1000\n      const messages = [\"message1\", \"message2\"]\n      \n      longpoll.batchSend(messages)\n      \n      // Verify Ajax.request was called with correct headers format\n      expect(Ajax.request).toHaveBeenCalledWith(\n        \"POST\",\n        expect.any(String),\n        {\"Content-Type\": \"application/x-ndjson\"},\n        \"message1\\nmessage2\",\n        expect.any(Number),\n        expect.any(Function),\n        expect.any(Function)\n      )\n    })\n  })\n})\n\ndescribe(\"Socket with LongPoll\", () => {\n  describe(\"transportConnect\", () => {\n    it(\"should initialize with undefined protocols when no auth token\", () => {\n      const socket = new Socket(\"/socket\", {transport: LongPoll})\n      \n      // Mock the transport to capture the protocols argument\n      socket.transport = jest.fn(() => ({\n        onopen: jest.fn(),\n        onerror: jest.fn(),\n        onmessage: jest.fn(),\n        onclose: jest.fn()\n      }))\n      \n      socket.transportConnect()\n      \n      // Verify that the transport was called with undefined protocols\n      expect(socket.transport).toHaveBeenCalledWith(\n        expect.any(String),\n        undefined\n      )\n    })\n    \n    it(\"should only set protocols array when auth token is present\", () => {\n      const authToken = \"my-auth-token\"\n      const socket = new Socket(\"/socket\", {\n        transport: LongPoll,\n        params: {token: authToken}\n      })\n      \n      // Set auth token\n      socket.authToken = authToken\n      \n      // Mock the transport to capture the protocols argument\n      socket.transport = jest.fn(() => ({\n        onopen: jest.fn(),\n        onerror: jest.fn(),\n        onmessage: jest.fn(),\n        onclose: jest.fn()\n      }))\n      \n      socket.transportConnect()\n      \n      // Verify that the transport was called with correct protocols array\n      expect(socket.transport).toHaveBeenCalledWith(\n        expect.any(String),\n        [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(authToken).replace(/=/g, \"\")}`]\n      )\n    })\n  })\n})\n\ndescribe(\"Ajax.request\", () => {\n  let originalXMLHttpRequest, originalFetch, originalAbortController\n\n  beforeEach(() => {\n    originalXMLHttpRequest = global.XMLHttpRequest\n    originalFetch = global.fetch\n    originalAbortController = global.AbortController\n\n    // Mock AbortController\n    global.AbortController = jest.fn(() => ({\n      abort: jest.fn(),\n      signal: {}\n    }))\n\n    // Mock XMLHttpRequest\n    global.XMLHttpRequest = jest.fn(() => ({\n      open: jest.fn(),\n      send: jest.fn(),\n      setRequestHeader: jest.fn(),\n      onreadystatechange: null,\n      readyState: 4,\n      status: 200,\n      responseText: JSON.stringify({success: true})\n    }))\n\n    // Mock fetch\n    global.fetch = jest.fn(() =>\n      Promise.resolve({\n        text: () => Promise.resolve(JSON.stringify({success: true}))\n      })\n    )\n  })\n\n  afterEach(() => {\n    global.XMLHttpRequest = originalXMLHttpRequest\n    global.fetch = originalFetch\n    global.AbortController = originalAbortController\n    jest.restoreAllMocks()\n  })\n\n  it(\"should use XMLHttpRequest by default\", () => {\n    Ajax.request(\"GET\", \"/test-endpoint\", {}, null, 0, null, (response) => {\n      expect(response).toEqual({success: true})\n    })\n\n    expect(global.XMLHttpRequest).toHaveBeenCalled()\n  })\n\n  it(\"should use fetch when XMLHttpRequest is not available\", () => {\n    global.XMLHttpRequest = undefined // Simulate it being unavailable\n    Ajax.request(\"GET\", \"/test-endpoint\", {}, null, 0, null, (response) => {\n      expect(response).toEqual({success: true})\n    })\n\n    expect(global.fetch).toHaveBeenCalledWith(\n      \"/test-endpoint\",\n      expect.objectContaining({\n        method: \"GET\",\n      })\n    )\n  })\n})\n\n"
  },
  {
    "path": "assets/test/presence_test.js",
    "content": "import {Presence} from \"../js/phoenix\"\n\nconst clone = (obj) => {\n  let cloned = JSON.parse(JSON.stringify(obj))\n  Object.entries(obj).forEach(([key, val]) => {\n    if(val === undefined){\n      cloned[key] = undefined\n    }\n  })\n  return cloned\n}\n\nconst fixtures = {\n  joins(){\n    return {u1: {metas: [{id: 1, phx_ref: \"1.2\"}]}}\n  },\n  leaves(){\n    return {u2: {metas: [{id: 2, phx_ref: \"2\"}]}}\n  },\n  state(){\n    return {\n      u1: {metas: [{id: 1, phx_ref: \"1\"}]},\n      u2: {metas: [{id: 2, phx_ref: \"2\"}]},\n      u3: {metas: [{id: 3, phx_ref: \"3\"}]},\n    }\n  },\n}\n\nconst channelStub = {\n  ref: 1,\n  events: {},\n\n  on(event, callback){\n    this.events[event] = callback\n  },\n\n  trigger(event, data){\n    this.events[event](data)\n  },\n\n  joinRef(){\n    return `${this.ref}`\n  },\n\n  simulateDisconnectAndReconnect(){\n    this.ref++\n  },\n}\n\nconst listByFirst = (id, {metas: [first, ..._rest]}) => first\n\ndescribe(\"syncState\", () => {\n  it(\"syncs empty state\", () => {\n    let newState = {u1: {metas: [{id: 1, phx_ref: \"1\"}]}}\n    let state = {}\n    let stateBefore = clone(state)\n    Presence.syncState(state, newState)\n    expect(state).toEqual(stateBefore)\n\n    state = Presence.syncState(state, newState)\n    expect(state).toEqual(newState)\n  })\n\n  it(\"onJoins new presences and onLeave's left presences\", () => {\n    let newState = fixtures.state()\n    let state = {u4: {metas: [{id: 4, phx_ref: \"4\"}]}}\n    let joined = {}\n    let left = {}\n    const onJoin = (key, current, newPres) => {\n      joined[key] = {current, newPres}\n    }\n    const onLeave = (key, current, leftPres) => {\n      left[key] = {current, leftPres}\n    }\n\n    state = Presence.syncState(state, newState, onJoin, onLeave)\n    expect(state).toEqual(newState)\n    expect(joined).toEqual({\n      u1: {current: undefined, newPres: {metas: [{id: 1, phx_ref: \"1\"}]}},\n      u2: {current: undefined, newPres: {metas: [{id: 2, phx_ref: \"2\"}]}},\n      u3: {current: undefined, newPres: {metas: [{id: 3, phx_ref: \"3\"}]}},\n    })\n    expect(left).toEqual({\n      u4: {current: {metas: []}, leftPres: {metas: [{id: 4, phx_ref: \"4\"}]}},\n    })\n  })\n\n  it(\"onJoins only newly added metas\", () => {\n    let newState = {u3: {metas: [{id: 3, phx_ref: \"3\"}, {id: 3, phx_ref: \"3.new\"}]}}\n    let state = {u3: {metas: [{id: 3, phx_ref: \"3\"}]}}\n    let joined = []\n    let left = []\n    const onJoin = (key, current, newPres) => {\n      joined.push([key, clone({current, newPres})])\n    }\n    const onLeave = (key, current, leftPres) => {\n      left.push([key, clone({current, leftPres})])\n    }\n    state = Presence.syncState(state, clone(newState), onJoin, onLeave)\n    expect(state).toEqual(newState)\n    expect(joined).toEqual([\n      [\"u3\", {current: {metas: [{id: 3, phx_ref: \"3\"}]}, newPres: {metas: [{id: 3, phx_ref: \"3.new\"}]}}],\n    ])\n    expect(left).toEqual([])\n  })\n})\n\ndescribe(\"syncDiff\", () => {\n  it(\"syncs empty state\", () => {\n    let joins = {u1: {metas: [{id: 1, phx_ref: \"1\"}]}}\n    let state = Presence.syncDiff({}, {joins, leaves: {}})\n    expect(state).toEqual(joins)\n  })\n\n  it(\"removes presence when meta is empty and adds additional meta\", () => {\n    let state = fixtures.state()\n    state = Presence.syncDiff(state, {joins: fixtures.joins(), leaves: fixtures.leaves()})\n\n    expect(state).toEqual({\n      u1: {metas: [{id: 1, phx_ref: \"1\"}, {id: 1, phx_ref: \"1.2\"}]},\n      u3: {metas: [{id: 3, phx_ref: \"3\"}]},\n    })\n  })\n\n  it(\"removes meta while leaving key if other metas exist\", () => {\n    let state = {u1: {metas: [{id: 1, phx_ref: \"1\"}, {id: 1, phx_ref: \"1.2\"}]}}\n    state = Presence.syncDiff(state, {joins: {}, leaves: {u1: {metas: [{id: 1, phx_ref: \"1\"}]}}})\n\n    expect(state).toEqual({\n      u1: {metas: [{id: 1, phx_ref: \"1.2\"}]},\n    })\n  })\n})\n\ndescribe(\"list\", () => {\n  it(\"lists full presence by default\", () => {\n    let state = fixtures.state()\n    expect(Presence.list(state)).toEqual([\n      {metas: [{id: 1, phx_ref: \"1\"}]},\n      {metas: [{id: 2, phx_ref: \"2\"}]},\n      {metas: [{id: 3, phx_ref: \"3\"}]},\n    ])\n  })\n\n  it(\"lists with custom function\", () => {\n    let state = {u1: {metas: [{id: 1, phx_ref: \"1.first\"}, {id: 1, phx_ref: \"1.second\"}]}}\n\n    const listBy = (key, {metas: [first, ..._rest]}) => first\n\n    expect(Presence.list(state, listBy)).toEqual([{id: 1, phx_ref: \"1.first\"}])\n  })\n})\n\ndescribe(\"instance\", () => {\n  it(\"syncs state and diffs\", () => {\n    let presence = new Presence(channelStub)\n    let user1 = {metas: [{id: 1, phx_ref: \"1\"}]}\n    let user2 = {metas: [{id: 2, phx_ref: \"2\"}]}\n    let newState = {u1: user1, u2: user2}\n\n    channelStub.trigger(\"presence_state\", newState)\n    expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: \"1\"}, {id: 2, phx_ref: \"2\"}])\n\n    channelStub.trigger(\"presence_diff\", {joins: {}, leaves: {u1: user1}})\n    expect(presence.list(listByFirst)).toEqual([{id: 2, phx_ref: \"2\"}])\n  })\n\n  it(\"applies pending diff if state is not yet synced\", () => {\n    let presence = new Presence(channelStub)\n    let onJoins = []\n    let onLeaves = []\n\n    presence.onJoin((id, current, newPres) => {\n      onJoins.push(clone({id, current, newPres}))\n    })\n    presence.onLeave((id, current, leftPres) => {\n      onLeaves.push(clone({id, current, leftPres}))\n    })\n\n    let user1 = {metas: [{id: 1, phx_ref: \"1\"}]}\n    let user2 = {metas: [{id: 2, phx_ref: \"2\"}]}\n    let user3 = {metas: [{id: 3, phx_ref: \"3\"}]}\n    let newState = {u1: user1, u2: user2}\n    let leaves = {u2: user2}\n\n    channelStub.trigger(\"presence_diff\", {joins: {}, leaves: leaves})\n\n    expect(presence.list(listByFirst)).toEqual([])\n    expect(presence.pendingDiffs).toEqual([{joins: {}, leaves: leaves}])\n\n    channelStub.trigger(\"presence_state\", newState)\n    expect(onLeaves).toEqual([{id: \"u2\", current: {metas: []}, leftPres: {metas: [{id: 2, phx_ref: \"2\"}]}}])\n\n    expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: \"1\"}])\n    expect(presence.pendingDiffs).toEqual([])\n    expect(onJoins).toEqual([\n      {id: \"u1\", current: undefined, newPres: {metas: [{id: 1, phx_ref: \"1\"}]}},\n      {id: \"u2\", current: undefined, newPres: {metas: [{id: 2, phx_ref: \"2\"}]}},\n    ])\n\n    channelStub.simulateDisconnectAndReconnect()\n    expect(presence.inPendingSyncState()).toBe(true)\n\n    channelStub.trigger(\"presence_diff\", {joins: {}, leaves: {u1: user1}})\n    expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: \"1\"}])\n\n    channelStub.trigger(\"presence_state\", {u1: user1, u3: user3})\n    expect(presence.list(listByFirst)).toEqual([{id: 3, phx_ref: \"3\"}])\n  })\n\n  it(\"allows custom channel events\", () => {\n    let presence = new Presence(channelStub, {\n      events: {\n        state: \"the_state\",\n        diff: \"the_diff\",\n      },\n    })\n\n    let user1 = {metas: [{id: 1, phx_ref: \"1\"}]}\n    channelStub.trigger(\"the_state\", {user1})\n    expect(presence.list(listByFirst)).toEqual([{id: 1, phx_ref: \"1\"}])\n    channelStub.trigger(\"the_diff\", {joins: {}, leaves: {user1}})\n    expect(presence.list(listByFirst)).toEqual([])\n  })\n\n  it(\"updates existing meta for a presence update (leave + join)\", () => {\n    let presence = new Presence(channelStub)\n    let onJoins = []\n    let onLeaves = []\n\n    let user1 = {metas: [{id: 1, phx_ref: \"1\"}]}\n    let user2 = {metas: [{id: 2, name: \"chris\", phx_ref: \"2\"}]}\n    let newState = {u1: user1, u2: user2}\n\n    channelStub.trigger(\"presence_state\", clone(newState))\n\n    presence.onJoin((id, current, newPres) => {\n      onJoins.push(clone({id, current, newPres}))\n    })\n    presence.onLeave((id, current, leftPres) => {\n      onLeaves.push(clone({id, current, leftPres}))\n    })\n\n    expect(presence.list((id, {metas: metas}) => metas)).toEqual([\n      [{id: 1, phx_ref: \"1\"}],\n      [{id: 2, name: \"chris\", phx_ref: \"2\"}],\n    ])\n\n    let leaves = {u2: user2}\n    let joins = {u2: {metas: [{id: 2, name: \"chris.2\", phx_ref: \"2.2\", phx_ref_prev: \"2\"}]}}\n    channelStub.trigger(\"presence_diff\", {joins, leaves})\n\n    expect(presence.list((id, {metas: metas}) => metas)).toEqual([\n      [{id: 1, phx_ref: \"1\"}],\n      [{id: 2, name: \"chris.2\", phx_ref: \"2.2\", phx_ref_prev: \"2\"}],\n    ])\n\n    expect(onJoins).toEqual([\n      {\n        id: \"u2\",\n        current: {metas: [{id: 2, name: \"chris\", phx_ref: \"2\"}]},\n        newPres: {metas: [{id: 2, name: \"chris.2\", phx_ref: \"2.2\", phx_ref_prev: \"2\"}]},\n      },\n    ])\n  })\n})\n"
  },
  {
    "path": "assets/test/serializer.js",
    "content": "\nexport const encode = (msg) => {\n  let payload = [\n    msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload\n  ]\n  return JSON.stringify(payload)\n}\n\nexport const decode = (rawPayload) => {\n  let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload)\n\n  return {join_ref, ref, topic, event, payload}\n}\n"
  },
  {
    "path": "assets/test/serializer_test.js",
    "content": "/**\n * @jest-environment node\n */\n\nimport {TextEncoder, TextDecoder} from \"util\"\nimport {Serializer} from \"../js/phoenix\"\n\nlet exampleMsg = {join_ref: \"0\", ref: \"1\", topic: \"t\", event: \"e\", payload: {foo: 1}}\n\nlet binPayload = () => {\n  let buffer = new ArrayBuffer(1)\n  new DataView(buffer).setUint8(0, 1)\n  return buffer\n}\n\ndescribe(\"JSON\", () => {\n  it(\"encodes general pushes\", (done) => {\n    Serializer.encode(exampleMsg, (result) => {\n      expect(result).toBe(\"[\\\"0\\\",\\\"1\\\",\\\"t\\\",\\\"e\\\",{\\\"foo\\\":1}]\")\n      done()\n    })\n  })\n\n  it(\"decodes\", (done) => {\n    Serializer.decode(\"[\\\"0\\\",\\\"1\\\",\\\"t\\\",\\\"e\\\",{\\\"foo\\\":1}]\", (result) => {\n      expect(result).toEqual(exampleMsg)\n      done()\n    })\n  })\n})\n\ndescribe(\"binary\", () => {\n  it(\"encodes\", (done) => {\n    let buffer = binPayload()\n    let bin = \"\\0\\x01\\x01\\x01\\x0101te\\x01\"\n    let decoder = new TextDecoder()\n    Serializer.encode({join_ref: \"0\", ref: \"1\", topic: \"t\", event: \"e\", payload: buffer}, (result) => {\n      expect(decoder.decode(result)).toBe(bin)\n      done()\n    })\n  })\n\n  it(\"encodes variable length segments\", (done) => {\n    let buffer = binPayload()\n    let bin = \"\\0\\x02\\x01\\x03\\x02101topev\\x01\"\n    let decoder = new TextDecoder()\n    Serializer.encode({join_ref: \"10\", ref: \"1\", topic: \"top\", event: \"ev\", payload: buffer}, (result) => {\n      expect(decoder.decode(result)).toBe(bin)\n      done()\n    })\n  })\n\n  it(\"decodes push\", (done) => {\n    let bin = \"\\0\\x03\\x03\\n123topsome-event\\x01\\x01\"\n    let buffer = new TextEncoder().encode(bin).buffer\n    let decoder = new TextDecoder()\n    Serializer.decode(buffer, (result) => {\n      expect(result.join_ref).toBe(\"123\")\n      expect(result.ref).toBeNull()\n      expect(result.topic).toBe(\"top\")\n      expect(result.event).toBe(\"some-event\")\n      expect(result.payload.constructor).toBe(ArrayBuffer)\n      expect(decoder.decode(result.payload)).toBe(\"\\x01\\x01\")\n      done()\n    })\n  })\n\n  it(\"decodes reply\", (done) => {\n    let bin = \"\\x01\\x03\\x02\\x03\\x0210012topok\\x01\\x01\"\n    let buffer = new TextEncoder().encode(bin).buffer\n    let decoder = new TextDecoder()\n    Serializer.decode(buffer, (result) => {\n      expect(result.join_ref).toBe(\"100\")\n      expect(result.ref).toBe(\"12\")\n      expect(result.topic).toBe(\"top\")\n      expect(result.event).toBe(\"phx_reply\")\n      expect(result.payload.status).toBe(\"ok\")\n      expect(result.payload.response.constructor).toBe(ArrayBuffer)\n      expect(decoder.decode(result.payload.response)).toBe(\"\\x01\\x01\")\n      done()\n    })\n  })\n\n  it(\"decodes broadcast\", (done) => {\n    let bin = \"\\x02\\x03\\ntopsome-event\\x01\\x01\"\n    let buffer = new TextEncoder().encode(bin).buffer\n    let decoder = new TextDecoder()\n    Serializer.decode(buffer, (result) => {\n      expect(result.join_ref).toBeNull()\n      expect(result.ref).toBeNull()\n      expect(result.topic).toBe(\"top\")\n      expect(result.event).toBe(\"some-event\")\n      expect(result.payload.constructor).toBe(ArrayBuffer)\n      expect(decoder.decode(result.payload)).toBe(\"\\x01\\x01\")\n      done()\n    })\n  })\n})\n"
  },
  {
    "path": "assets/test/socket_http_test.js",
    "content": "/**\n * @jest-environment jsdom\n * @jest-environment-options {\"url\": \"http://example.com/\"}\n */\nimport {Socket} from \"../js/phoenix\"\n\n// sadly, jsdom can only be configured globally for a file\n\ndescribe(\"protocol\", function (){\n  it(\"returns ws when location.protocol is http\", function (){\n    const socket = new Socket(\"/socket\")\n    expect(socket.protocol()).toBe(\"ws\")\n  })\n})\n\ndescribe(\"endpointURL\", function (){\n  it(\"returns endpoint for given path on http host\", function (){\n    const socket = new Socket(\"/socket\")\n    expect(socket.endPointURL()).toBe(\n      \"ws://example.com/socket/websocket?vsn=2.0.0\",\n    )\n  })\n})\n"
  },
  {
    "path": "assets/test/socket_test.js",
    "content": "import {jest} from \"@jest/globals\"\nimport {WebSocket, Server as WebSocketServer} from \"mock-socket\"\nimport {encode} from \"./serializer\"\nimport {Socket, LongPoll} from \"../js/phoenix\"\nimport {SOCKET_STATES} from \"../js/phoenix/constants\"\n\nlet socket\n\ndescribe(\"with transports\", function (){\n  beforeAll(() => {\n    window.WebSocket = WebSocket\n    const mockOpen = jest.fn()\n    const mockSend = jest.fn()\n    const mockAbort = jest.fn()\n    const mockSetRequestHeader = jest.fn()\n    \n    global.XMLHttpRequest = jest.fn(() => ({\n      open: mockOpen,\n      send: mockSend,\n      abort: mockAbort,\n      setRequestHeader: mockSetRequestHeader,\n      readyState: 4,\n      status: 200,\n      responseText: JSON.stringify({}),\n      onreadystatechange: null,\n    }))\n  })\n\n  describe(\"constructor\", function (){\n    it(\"sets defaults\", function (){\n      socket = new Socket(\"/socket\")\n\n      expect(socket.channels.length).toBe(0)\n      expect(socket.sendBuffer.length).toBe(0)\n      expect(socket.ref).toBe(0)\n      expect(socket.endPoint).toBe(\"/socket/websocket\")\n      expect(socket.stateChangeCallbacks).toEqual({open: [], close: [], error: [], message: []})\n      expect(socket.transport).toBe(WebSocket)\n      expect(socket.timeout).toBe(10000)\n      expect(socket.longpollerTimeout).toBe(20000)\n      expect(socket.heartbeatIntervalMs).toBe(30000)\n      expect(socket.logger).toBeNull()\n      expect(socket.binaryType).toBe(\"arraybuffer\")\n      expect(typeof socket.reconnectAfterMs).toBe(\"function\")\n    })\n\n    it(\"supports closure or literal params\", function (){\n      socket = new Socket(\"/socket\", {params: {one: \"two\"}})\n      expect(socket.params()).toEqual({one: \"two\"})\n\n      socket = new Socket(\"/socket\", {params: function (){ return ({three: \"four\"}) }})\n      expect(socket.params()).toEqual({three: \"four\"})\n    })\n\n    it(\"overrides some defaults with options\", function (){\n      const customTransport = function transport(){ }\n      const customLogger = function logger(){ }\n      const customReconnect = function reconnect(){ }\n\n      socket = new Socket(\"/socket\", {\n        timeout: 40000,\n        longpollerTimeout: 50000,\n        heartbeatIntervalMs: 60000,\n        transport: customTransport,\n        logger: customLogger,\n        reconnectAfterMs: customReconnect,\n        params: {one: \"two\"},\n      })\n\n      expect(socket.timeout).toBe(40000)\n      expect(socket.longpollerTimeout).toBe(50000)\n      expect(socket.heartbeatIntervalMs).toBe(60000)\n      expect(socket.transport).toBe(customTransport)\n      expect(socket.logger).toBe(customLogger)\n      expect(socket.params()).toEqual({one: \"two\"})\n    })\n\n    describe(\"with Websocket\", function (){\n      it(\"defaults to Websocket transport if available\", function (done){\n        let mockServer = new WebSocketServer(\"wss://example.com/\")\n        socket = new Socket(\"/socket\")\n        expect(socket.transport).toBe(WebSocket)\n        mockServer.stop(() => done())\n      })\n    })\n\n    describe(\"longPollFallbackMs\", function (){\n      it(\"falls back to longpoll when set after primary transport failure\", function (done){\n        let mockServer\n        socket = new Socket(\"/socket\", {longPollFallbackMs: 20})\n        const replaceSpy = jest.spyOn(socket, \"replaceTransport\")\n        mockServer = new WebSocketServer(\"wss://example.test/\")\n        mockServer.stop(() => {\n          expect(socket.transport).toBe(WebSocket)\n          socket.onError((_reason) => {\n            setTimeout(() => {\n              expect(replaceSpy).toHaveBeenCalledWith(LongPoll)\n              done()\n            }, 100)\n          })\n          socket.connect()\n        })\n      })\n    })\n  })\n\n  describe(\"visibilitychange\", function (){\n    it(\"does not connect a socket that was never connected\", function (){\n      socket = new Socket(\"/socket\")\n      const teardownSpy = jest.spyOn(socket, \"teardown\")\n\n      Object.defineProperty(document, \"visibilityState\", {value: \"hidden\", writable: true})\n      window.dispatchEvent(new Event(\"visibilitychange\"))\n\n      Object.defineProperty(document, \"visibilityState\", {value: \"visible\", writable: true})\n      window.dispatchEvent(new Event(\"visibilitychange\"))\n\n      expect(teardownSpy).not.toHaveBeenCalled()\n    })\n\n    it(\"reconnects on visibility change after unclean close\", function (){\n      socket = new Socket(\"/socket\")\n      socket.closeWasClean = false\n      const teardownSpy = jest.spyOn(socket, \"teardown\")\n\n      Object.defineProperty(document, \"visibilityState\", {value: \"visible\", writable: true})\n      window.dispatchEvent(new Event(\"visibilitychange\"))\n\n      expect(teardownSpy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"does not reconnect on visibility change after clean close\", function (){\n      socket = new Socket(\"/socket\")\n      socket.closeWasClean = true\n      const teardownSpy = jest.spyOn(socket, \"teardown\")\n\n      Object.defineProperty(document, \"visibilityState\", {value: \"visible\", writable: true})\n      window.dispatchEvent(new Event(\"visibilitychange\"))\n\n      expect(teardownSpy).not.toHaveBeenCalled()\n    })\n  })\n\n  describe(\"protocol\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"returns wss when location.protocol is https\", function (){\n      expect(socket.protocol()).toBe(\"wss\")\n    })\n  })\n\n  describe(\"endpointURL\", function (){\n    it(\"returns endpoint for given full url\", function (){\n      socket = new Socket(\"wss://example.org/chat\")\n      expect(socket.endPointURL()).toBe(\"wss://example.org/chat/websocket?vsn=2.0.0\")\n    })\n\n    it(\"returns endpoint for given protocol-relative url\", function (){\n      socket = new Socket(\"//example.org/chat\")\n      expect(socket.endPointURL()).toBe(\"wss://example.org/chat/websocket?vsn=2.0.0\")\n    })\n\n    it(\"returns endpoint for given path on https host\", function (){\n      socket = new Socket(\"/socket\")\n      expect(socket.endPointURL()).toBe(\"wss://example.com/socket/websocket?vsn=2.0.0\")\n    })\n  })\n\n  describe(\"connect with WebSocket\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"establishes websocket connection with endpoint\", function (){\n      socket.connect()\n      const conn = socket.conn\n      expect(conn instanceof WebSocket).toBeTruthy()\n      expect(conn.url).toBe(socket.endPointURL())\n    })\n\n    it(\"sets callbacks for connection\", function (){\n      let opens = 0\n      socket.onOpen(() => ++opens)\n      let closes = 0\n      socket.onClose(() => ++closes)\n      let lastError\n      socket.onError((error) => lastError = error)\n      let lastMessage\n      socket.onMessage((message) => lastMessage = message.payload)\n\n      socket.connect()\n\n      socket.conn.onopen()\n      expect(opens).toBe(1)\n\n      socket.conn.onclose()\n      expect(closes).toBe(1)\n\n      socket.conn.onerror(\"error\")\n      expect(lastError).toBe(\"error\")\n\n      const data = {\"topic\": \"topic\", \"event\": \"event\", \"payload\": \"payload\", \"status\": \"ok\"}\n      socket.conn.onmessage({data: encode(data)})\n      expect(lastMessage).toBe(\"payload\")\n    })\n\n    it(\"is idempotent\", function (){\n      socket.connect()\n      const conn = socket.conn\n      socket.connect()\n      expect(conn).toBe(socket.conn)\n    })\n  })\n\n  describe(\"connect with long poll\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {transport: LongPoll})\n    })\n\n    it(\"establishes long poll connection with endpoint\", function (){\n      socket.connect()\n      const conn = socket.conn\n      expect(conn instanceof LongPoll).toBeTruthy()\n      expect(conn.pollEndpoint).toBe(\"https://example.com/socket/longpoll?vsn=2.0.0\")\n      expect(conn.timeout).toBe(20000)\n    })\n\n    it(\"sets callbacks for connection\", function (){\n      let opens = 0\n      socket.onOpen(() => ++opens)\n      let closes = 0\n      socket.onClose(() => ++closes)\n      let lastError\n      socket.onError((error) => lastError = error)\n      let lastMessage\n      socket.onMessage((message) => lastMessage = message.payload)\n\n      socket.connect()\n\n      socket.conn.onopen()\n      expect(opens).toBe(1)\n\n      socket.conn.onclose()\n      expect(closes).toBe(1)\n\n      socket.conn.onerror(\"error\")\n      expect(lastError).toBe(\"error\")\n\n      socket.connect()\n\n      const data = {\"topic\": \"topic\", \"event\": \"event\", \"payload\": \"payload\", \"status\": \"ok\"}\n\n      socket.conn.onmessage({data: encode(data)})\n      expect(lastMessage).toBe(\"payload\")\n    })\n\n    it(\"is idempotent\", function (){\n      socket.connect()\n      const conn = socket.conn\n      socket.connect()\n      expect(conn).toBe(socket.conn)\n    })\n  })\n\n  describe(\"disconnect\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"removes existing connection\", function (done){\n      socket.connect()\n      socket.disconnect()\n      socket.disconnect(() => {\n        expect(socket.conn).toBeNull()\n        done()\n      })\n    })\n\n    it(\"calls callback\", function (done){\n      let count = 0\n      socket.connect()\n      socket.disconnect(() => {\n        count++\n        expect(count).toBe(1)\n        done()\n      })\n    })\n\n    it(\"calls connection close callback\", function (done){\n      socket.connect()\n      const closeSpy = jest.spyOn(socket.conn, \"close\")\n\n      socket.disconnect(() => {\n        expect(closeSpy).toHaveBeenCalledWith(1000, \"reason\")\n        done()\n      }, 1000, \"reason\")\n    })\n\n    it(\"does not throw when no connection\", function (){\n      expect(() => {\n        socket.disconnect()\n      }).not.toThrow()\n    })\n\n    it(\"properly tears down old connection when immediately reconnecting\", function (){\n      const connections = []\n      const mockWebSocket = function StubWebSocketNoAutoClose(_url){\n        const conn = {\n          readyState: SOCKET_STATES.open,\n          get bufferedAmount(){ return 1 },\n          binaryType: \"arraybuffer\",\n          timeout: 20000,\n          onopen: null,\n          onerror: null,\n          onmessage: null,\n          onclose: null,\n          close(_code, _reason){\n            this.readyState = SOCKET_STATES.closing\n            setTimeout(() => {\n              this.readyState = SOCKET_STATES.closed\n            }, 1000)\n          },\n          send(){},\n        }\n        connections.push(conn)\n        return conn\n      }\n\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {\n        heartbeatIntervalMs: 30000,\n        heartbeatTimeoutMs: 30000,\n        reconnectAfterMs: () => 10,\n        transport: mockWebSocket\n      })\n      socket.connect()\n      const originalConn = socket.conn\n\n      // Disconnect triggers teardown, which waits for bufferedAmount to be zero or 2250ms,\n      // then awaits SOCKET_STATES.closed before calling the callback.\n      const disconnected = jest.fn()\n      socket.disconnect(disconnected)\n\n      // For now, the conn is still set.\n      expect(socket.conn).toBeTruthy()\n\n      // Advance time by > 2250ms, which means we are waiting for socket to transition to closed\n      jest.advanceTimersByTime(3000)\n\n      // Now we call connect, while the teardown is still running\n      socket.connect()\n      // By now, waitForSocketClosed should be done, but now there's a new conn!\n      jest.advanceTimersByTime(3000)\n      expect(socket.conn).not.toBe(originalConn)\n\n      const openConns = connections.filter(c => c.readyState === SOCKET_STATES.open)\n      expect(openConns.length).toBe(1)\n\n      // Late teardown must not overwrite this.conn with null when it is already connB\n      expect(socket.conn).not.toBeNull()\n\n      // the original disconnected should have been called\n      expect(disconnected).toHaveBeenCalled()\n\n      jest.useRealTimers()\n    })\n\n    it(\"properly tears down old connection when disconnecting twice\", function (){\n      const connections = []\n      const mockWebSocket = function StubWebSocketNoAutoClose(_url){\n        const conn = {\n          readyState: SOCKET_STATES.open,\n          get bufferedAmount(){ return 1 },\n          binaryType: \"arraybuffer\",\n          timeout: 20000,\n          onopen: null,\n          onerror: null,\n          onmessage: null,\n          onclose: null,\n          close(_code, _reason){\n            this.readyState = SOCKET_STATES.closing\n            setTimeout(() => {\n              this.readyState = SOCKET_STATES.closed\n            }, 1000)\n          },\n          send(){},\n        }\n        connections.push(conn)\n        return conn\n      }\n\n      jest.useFakeTimers()\n\n      socket = new Socket(\"/socket\", {\n        heartbeatIntervalMs: 30000,\n        heartbeatTimeoutMs: 30000,\n        reconnectAfterMs: () => 10,\n        transport: mockWebSocket\n      })\n      socket.connect()\n\n      const disconnected = jest.fn()\n      socket.disconnect(disconnected)\n\n      // For now, the conn is still set.\n      expect(socket.conn).toBeTruthy()\n\n      // Advance time by > 2250ms, which means we are waiting for socket to transition to closed\n      jest.advanceTimersByTime(3000)\n\n      // Now we call disconnect again, while the teardown is still running\n      const disconnected2 = jest.fn()\n      socket.disconnect(disconnected2)\n\n      jest.advanceTimersByTime(10000)\n\n      const openConns = connections.filter(c => c.readyState === SOCKET_STATES.open)\n      expect(openConns.length).toBe(0)\n      expect(socket.conn).toBeNull()\n\n      // both disconnected functions should have been called\n      expect(disconnected).toHaveBeenCalled()\n      expect(disconnected2).toHaveBeenCalled()\n\n      jest.useRealTimers()\n    })\n  })\n\n  describe(\"connectionState\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"defaults to closed\", function (){\n      expect(socket.connectionState()).toBe(\"closed\")\n    })\n\n    it(\"returns closed if readyState unrecognized\", function (){\n      socket.connect()\n      socket.conn.readyState = 5678\n      expect(socket.connectionState()).toBe(\"closed\")\n    })\n\n    it(\"returns connecting\", function (){\n      socket.connect()\n      socket.conn.readyState = 0\n      expect(socket.connectionState()).toBe(\"connecting\")\n      expect(socket.isConnected()).toBe(false)\n    })\n\n    it(\"returns open\", function (){\n      socket.connect()\n      socket.conn.readyState = 1\n      expect(socket.connectionState()).toBe(\"open\")\n      expect(socket.isConnected()).toBe(true)\n    })\n\n    it(\"returns closing\", function (){\n      socket.connect()\n      socket.conn.readyState = 2\n      expect(socket.connectionState()).toBe(\"closing\")\n      expect(socket.isConnected()).toBe(false)\n    })\n\n    it(\"returns closed\", function (){\n      socket.connect()\n      socket.conn.readyState = 3\n      expect(socket.connectionState()).toBe(\"closed\")\n      expect(socket.isConnected()).toBe(false)\n    })\n  })\n\n  describe(\"channel\", function (){\n    let channel\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"returns channel with given topic and params\", function (){\n      channel = socket.channel(\"topic\", {one: \"two\"})\n      expect(channel.socket).toBe(socket)\n      expect(channel.topic).toBe(\"topic\")\n      expect(channel.params()).toEqual({one: \"two\"})\n    })\n\n    it(\"adds channel to sockets channels list\", function (){\n      expect(socket.channels.length).toBe(0)\n      channel = socket.channel(\"topic\", {one: \"two\"})\n      expect(socket.channels.length).toBe(1)\n      const [foundChannel] = socket.channels\n      expect(foundChannel).toBe(channel)\n    })\n  })\n\n  describe(\"remove\", function (){\n    it(\"removes given channel from channels\", function (){\n      socket = new Socket(\"/socket\")\n      const channel1 = socket.channel(\"topic-1\")\n      const channel2 = socket.channel(\"topic-2\")\n\n      jest.spyOn(channel1, \"joinRef\").mockReturnValue(1)\n      jest.spyOn(channel2, \"joinRef\").mockReturnValue(2)\n\n      expect(socket.stateChangeCallbacks.open.length).toBe(2)\n\n      socket.remove(channel1)\n\n      expect(socket.stateChangeCallbacks.open.length).toBe(1)\n      expect(socket.channels.length).toBe(1)\n\n      const [foundChannel] = socket.channels\n      expect(foundChannel).toBe(channel2)\n    })\n  })\n\n  describe(\"push\", function (){\n    let data, json\n\n    beforeEach(function (){\n      data = {topic: \"topic\", event: \"event\", payload: \"payload\", ref: \"ref\"}\n      json = encode(data)\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"sends data to connection when connected\", function (){\n      socket.connect()\n      socket.conn.readyState = 1 // open\n\n      const sendSpy = jest.spyOn(socket.conn, \"send\")\n\n      socket.push(data)\n\n      expect(sendSpy).toHaveBeenCalledWith(json)\n    })\n\n    it(\"buffers data when not connected\", function (){\n      socket.connect()\n      socket.conn.readyState = 0 // connecting\n\n      const sendSpy = jest.spyOn(socket.conn, \"send\").mockImplementation(() => {})\n\n      expect(socket.sendBuffer.length).toBe(0)\n\n      socket.push(data)\n\n      expect(sendSpy).not.toHaveBeenCalledWith(json)\n      expect(socket.sendBuffer.length).toBe(1)\n\n      const [callback] = socket.sendBuffer\n      callback()\n      expect(sendSpy).toHaveBeenCalledWith(json)\n    })\n  })\n\n  describe(\"makeRef\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n    })\n\n    it(\"returns next message ref\", function (){\n      expect(socket.ref).toBe(0)\n      expect(socket.makeRef()).toBe(\"1\")\n      expect(socket.ref).toBe(1)\n      expect(socket.makeRef()).toBe(\"2\")\n      expect(socket.ref).toBe(2)\n    })\n\n    it(\"restarts for overflow\", function (){\n      socket.ref = Number.MAX_SAFE_INTEGER + 1\n      expect(socket.makeRef()).toBe(\"0\")\n      expect(socket.ref).toBe(0)\n    })\n  })\n\n  describe(\"sendHeartbeat\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n      socket.connect()\n    })\n\n    it(\"closes socket when heartbeat is not ack'd within heartbeat window\", function (done){\n      jest.useFakeTimers()\n      let closed = false\n      socket.conn.readyState = 1 // open\n      socket.conn.close = () => closed = true\n      socket.sendHeartbeat()\n      expect(closed).toBe(false)\n\n      jest.advanceTimersByTime(10000)\n      expect(closed).toBe(false)\n\n      jest.advanceTimersByTime(20010)\n      expect(closed).toBe(true)\n\n      jest.useRealTimers()\n      done()\n    })\n\n    it(\"pushes heartbeat data when connected\", function (){\n      socket.conn.readyState = 1 // open\n\n      const sendSpy = jest.spyOn(socket.conn, \"send\")\n      const data = \"[null,\\\"1\\\",\\\"phoenix\\\",\\\"heartbeat\\\",{}]\"\n\n      socket.sendHeartbeat()\n      expect(sendSpy).toHaveBeenCalledWith(data)\n    })\n\n    it(\"no ops when not connected\", function (){\n      socket.conn.readyState = 0 // connecting\n\n      const sendSpy = jest.spyOn(socket.conn, \"send\")\n      const data = encode({topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: \"1\"})\n\n      socket.sendHeartbeat()\n      expect(sendSpy).not.toHaveBeenCalledWith(data)\n    })\n  })\n\n  describe(\"flushSendBuffer\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n      socket.connect()\n    })\n\n    it(\"calls callbacks in buffer when connected\", function (){\n      socket.conn.readyState = 1 // open\n      const spy1 = jest.fn()\n      const spy2 = jest.fn()\n      socket.sendBuffer.push(spy1)\n      socket.sendBuffer.push(spy2)\n\n      socket.flushSendBuffer()\n\n      expect(spy1).toHaveBeenCalledTimes(1)\n      expect(spy2).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"empties sendBuffer\", function (){\n      socket.conn.readyState = 1 // open\n      socket.sendBuffer.push(() => { })\n\n      socket.flushSendBuffer()\n\n      expect(socket.sendBuffer.length).toBe(0)\n    })\n  })\n\n  describe(\"onConnOpen\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {\n        reconnectAfterMs: () => 100000\n      })\n      socket.connect()\n    })\n\n    it(\"flushes the send buffer\", function (){\n      socket.conn.readyState = 1 // open\n      const spy = jest.fn()\n      socket.sendBuffer.push(spy)\n\n      socket.onConnOpen()\n\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"resets reconnectTimer\", function (){\n      const resetSpy = jest.spyOn(socket.reconnectTimer, \"reset\")\n      socket.onConnOpen()\n      expect(resetSpy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"triggers onOpen callback\", function (){\n      const spy = jest.fn()\n      socket.onOpen(spy)\n      socket.onConnOpen()\n      expect(spy).toHaveBeenCalledTimes(1)\n    })\n  })\n\n  describe(\"onConnClose\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {\n        reconnectAfterMs: () => 100000\n      })\n      socket.connect()\n    })\n\n    it(\"does not schedule reconnectTimer if normal close\", function (){\n      const scheduleSpy = jest.spyOn(socket.reconnectTimer, \"scheduleTimeout\")\n      const event = {code: 1000}\n      socket.onConnClose(event)\n      expect(scheduleSpy).not.toHaveBeenCalled()\n    })\n\n    it(\"schedules reconnectTimer timeout if abnormal close\", function (){\n      const scheduleSpy = jest.spyOn(socket.reconnectTimer, \"scheduleTimeout\")\n      const event = {code: 1006}\n      socket.onConnClose(event)\n      expect(scheduleSpy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"does not schedule reconnectTimer timeout if normal close after explicit disconnect\", function (){\n      const scheduleSpy = jest.spyOn(socket.reconnectTimer, \"scheduleTimeout\")\n      socket.disconnect()\n      expect(scheduleSpy).not.toHaveBeenCalled()\n    })\n\n    it(\"schedules reconnectTimer timeout if not normal close\", function (){\n      const scheduleSpy = jest.spyOn(socket.reconnectTimer, \"scheduleTimeout\")\n      const event = {code: 1001}\n      socket.onConnClose(event)\n      expect(scheduleSpy).toHaveBeenCalledTimes(1)\n    })\n\n    it(\"schedules reconnectTimer timeout if connection cannot be made after a previous clean disconnect\", function (done){\n      const scheduleSpy = jest.spyOn(socket.reconnectTimer, \"scheduleTimeout\")\n      socket.disconnect(() => {\n        socket.connect()\n        const event = {code: 1001}\n        socket.onConnClose(event)\n        expect(scheduleSpy).toHaveBeenCalledTimes(1)\n        done()\n      })\n    })\n\n    it(\"triggers onClose callback\", function (){\n      const spy = jest.fn()\n      socket.onClose(spy)\n      socket.onConnClose(\"event\")\n      expect(spy).toHaveBeenCalledWith(\"event\")\n    })\n\n    it(\"triggers channel error if joining\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join()\n      expect(channel.state).toBe(\"joining\")\n      socket.onConnClose()\n      expect(triggerSpy).toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"triggers channel error if joined\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join().trigger(\"ok\", {})\n      expect(channel.state).toBe(\"joined\")\n      socket.onConnClose()\n      expect(triggerSpy).toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"does not trigger channel error after leave\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join().trigger(\"ok\", {})\n      channel.leave()\n      expect(channel.state).toBe(\"closed\")\n      socket.onConnClose()\n      expect(triggerSpy).not.toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"does not send heartbeat after explicit disconnect\", function (done){\n      jest.useFakeTimers()\n      const sendHeartbeatSpy = jest.spyOn(socket, \"sendHeartbeat\")\n      socket.onConnOpen()\n      socket.disconnect()\n      jest.advanceTimersByTime(30000)\n      expect(sendHeartbeatSpy).not.toHaveBeenCalled()\n      jest.useRealTimers()\n      done()\n    })\n\n    it(\"does not timeout the heartbeat after explicit disconnect\", function (done){\n      jest.useFakeTimers()\n      const heartbeatTimeoutSpy = jest.spyOn(socket, \"heartbeatTimeout\")\n      socket.onConnOpen()\n      socket.disconnect()\n      jest.advanceTimersByTime(60000)\n      expect(heartbeatTimeoutSpy).not.toHaveBeenCalled()\n      jest.useRealTimers()\n      done()\n    })\n  })\n\n  describe(\"onConnError\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {\n        reconnectAfterMs: () => 100000\n      })\n      socket.connect()\n    })\n\n    it(\"triggers onClose callback\", function (){\n      const spy = jest.fn()\n      socket.onError(spy)\n      socket.onConnError(\"error\")\n      expect(spy).toHaveBeenCalledWith(\"error\", expect.any(Function), 0)\n    })\n\n    it(\"triggers channel error if joining with open connection\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join()\n      socket.onConnOpen()\n      expect(channel.state).toBe(\"joining\")\n      socket.onConnError(\"error\")\n      expect(triggerSpy).toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"triggers channel error if joining with no connection\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join()\n      expect(channel.state).toBe(\"joining\")\n      socket.onConnError(\"error\")\n      expect(triggerSpy).toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"triggers channel error if joined\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join().trigger(\"ok\", {})\n      socket.onConnOpen()\n      expect(channel.state).toBe(\"joined\")\n\n      let connectionsCount = null\n      let transport = null\n      socket.onError((error, erroredTransport, conns) => {\n        transport = erroredTransport\n        connectionsCount = conns\n      })\n\n      socket.onConnError(\"error\")\n\n      expect(transport).toBe(WebSocket)\n      expect(connectionsCount).toBe(1)\n      expect(triggerSpy).toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"does not trigger channel error after leave\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join().trigger(\"ok\", {})\n      channel.leave()\n      expect(channel.state).toBe(\"closed\")\n      socket.onConnError(\"error\")\n      expect(triggerSpy).not.toHaveBeenCalledWith(\"phx_error\")\n    })\n\n    it(\"does not trigger channel error if transport replaced with no previous connection\", function (){\n      const channel = socket.channel(\"topic\")\n      const triggerSpy = jest.spyOn(channel, \"trigger\")\n      channel.join()\n      expect(channel.state).toBe(\"joining\")\n\n      let connectionsCount = null\n      class FakeTransport { }\n\n      socket.onError((error, transport, conns) => {\n        socket.replaceTransport(FakeTransport)\n        connectionsCount = conns\n      })\n      socket.onConnError(\"error\")\n\n      expect(connectionsCount).toBe(0)\n      expect(socket.transport).toBe(FakeTransport)\n      expect(triggerSpy).not.toHaveBeenCalledWith(\"phx_error\")\n    })\n  })\n\n  describe(\"onConnMessage\", function (){\n    let mockServer\n\n    beforeAll(function (){\n      mockServer = new WebSocketServer(\"wss://example.com/\")\n    })\n\n    afterAll(function (done){\n      mockServer.stop(() => done())\n    })\n\n    beforeEach(function (){\n      socket = new Socket(\"/socket\", {\n        reconnectAfterMs: () => 100000\n      })\n      socket.connect()\n    })\n\n    it(\"parses raw message and triggers channel event\", function (){\n      const message = encode({topic: \"topic\", event: \"event\", payload: \"payload\", ref: \"ref\"})\n      const data = {data: message}\n\n      const targetChannel = socket.channel(\"topic\")\n      const otherChannel = socket.channel(\"off-topic\")\n\n      const targetSpy = jest.spyOn(targetChannel, \"trigger\")\n      const otherSpy = jest.spyOn(otherChannel, \"trigger\")\n\n      socket.onConnMessage(data)\n\n      expect(targetSpy).toHaveBeenCalledWith(\"event\", \"payload\", \"ref\", null)\n      expect(targetSpy).toHaveBeenCalledTimes(1)\n      expect(otherSpy).toHaveBeenCalledTimes(0)\n    })\n\n    it(\"triggers onMessage callback\", function (){\n      const message = {\"topic\": \"topic\", \"event\": \"event\", \"payload\": \"payload\", \"ref\": \"ref\"}\n      const spy = jest.fn()\n      socket.onMessage(spy)\n      socket.onConnMessage({data: encode(message)})\n\n      expect(spy).toHaveBeenCalledWith({\n        \"topic\": \"topic\",\n        \"event\": \"event\",\n        \"payload\": \"payload\",\n        \"ref\": \"ref\",\n        \"join_ref\": null\n      })\n    })\n  })\n\n  describe(\"ping\", function (){\n    beforeEach(function (){\n      socket = new Socket(\"/socket\")\n      socket.connect()\n    })\n\n    it(\"pushes when connected\", function (done){\n      let latency = 100\n      socket.conn.readyState = 1 // open\n      expect(socket.isConnected()).toBe(true)\n      socket.push = (msg) => {\n        setTimeout(() => {\n          socket.onConnMessage({data: encode({topic: \"phoenix\", event: \"phx_reply\", ref: msg.ref})})\n        }, latency)\n      }\n\n      const result = socket.ping(rtt => {\n        // if we're unlucky we could also receive 99 as rtt, so let's be generous\n        expect(rtt >= (latency - 10)).toBe(true)\n        done()\n      })\n      expect(result).toBe(true)\n    })\n\n    it(\"returns false when disconnected\", function (){\n      socket.conn.readyState = 0\n      expect(socket.isConnected()).toBe(false)\n      const result = socket.ping(_rtt => true)\n      expect(result).toBe(false)\n    })\n  })\n\n  describe(\"custom encoder and decoder\", function (){\n\n    it(\"encodes to JSON array by default\", function (){\n      socket = new Socket(\"/socket\")\n      const payload = {topic: \"topic\", ref: \"2\", join_ref: \"1\", event: \"join\", payload: {foo: \"bar\"}}\n\n      socket.encode(payload, encoded => {\n        expect(encoded).toBe(\"[\\\"1\\\",\\\"2\\\",\\\"topic\\\",\\\"join\\\",{\\\"foo\\\":\\\"bar\\\"}]\")\n      })\n    })\n\n    it(\"allows custom encoding when using WebSocket transport\", function (){\n      const encoder = (payload, callback) => callback(\"encode works\")\n      socket = new Socket(\"/socket\", {transport: WebSocket, encode: encoder})\n\n      socket.encode({foo: \"bar\"}, encoded => {\n        expect(encoded).toBe(\"encode works\")\n      })\n    })\n\n    it(\"forces JSON encoding when using LongPoll transport\", function (){\n      const encoder = (payload, callback) => callback(\"encode works\")\n      socket = new Socket(\"/socket\", {transport: LongPoll, encode: encoder})\n      const payload = {topic: \"topic\", ref: \"2\", join_ref: \"1\", event: \"join\", payload: {foo: \"bar\"}}\n\n      socket.encode(payload, encoded => {\n        expect(encoded).toBe(\"[\\\"1\\\",\\\"2\\\",\\\"topic\\\",\\\"join\\\",{\\\"foo\\\":\\\"bar\\\"}]\")\n      })\n    })\n\n    it(\"decodes JSON by default\", function (){\n      socket = new Socket(\"/socket\")\n      const encoded = \"[\\\"1\\\",\\\"2\\\",\\\"topic\\\",\\\"join\\\",{\\\"foo\\\":\\\"bar\\\"}]\"\n\n      socket.decode(encoded, decoded => {\n        expect(decoded).toEqual({topic: \"topic\", ref: \"2\", join_ref: \"1\", event: \"join\", payload: {foo: \"bar\"}})\n      })\n    })\n\n    it(\"allows custom decoding when using WebSocket transport\", function (){\n      const decoder = (payload, callback) => callback(\"decode works\")\n      socket = new Socket(\"/socket\", {transport: WebSocket, decode: decoder})\n\n      socket.decode(\"...esoteric format...\", decoded => {\n        expect(decoded).toBe(\"decode works\")\n      })\n    })\n\n    it(\"forces JSON decoding when using LongPoll transport\", function (){\n      const decoder = (payload, callback) => callback(\"decode works\")\n      socket = new Socket(\"/socket\", {transport: LongPoll, decode: decoder})\n      const payload = {topic: \"topic\", ref: \"2\", join_ref: \"1\", event: \"join\", payload: {foo: \"bar\"}}\n\n      socket.decode(\"[\\\"1\\\",\\\"2\\\",\\\"topic\\\",\\\"join\\\",{\\\"foo\\\":\\\"bar\\\"}]\", decoded => {\n        expect(decoded).toEqual(payload)\n      })\n    })\n  })\n})\n\nwindow.XMLHttpRequest = jest.fn()\nwindow.WebSocket = WebSocket\n"
  },
  {
    "path": "babel.config.json",
    "content": "{\n    \"presets\": [\n        \"@babel/preset-env\"\n    ]\n}\n"
  },
  {
    "path": "config/config.exs",
    "content": "import Config\n\nconfig :logger, :console,\n  colors: [enabled: false],\n  format: \"\\n$time $metadata[$level] $message\\n\"\n\nconfig :phoenix,\n  json_library: Jason,\n  stacktrace_depth: 20,\n  trim_on_html_eex_engine: false,\n  sort_verified_routes_query_params: true\n\nif Mix.env() == :dev do\n  esbuild = fn args ->\n    [\n      args: ~w(./js/phoenix --bundle) ++ args,\n      cd: Path.expand(\"../assets\", __DIR__),\n      env: %{\"NODE_PATH\" => Path.expand(\"../deps\", __DIR__)}\n    ]\n  end\n\n  config :esbuild,\n    version: \"0.25.4\",\n    module: esbuild.(~w(--format=esm --sourcemap --outfile=../priv/static/phoenix.mjs)),\n    main: esbuild.(~w(--format=cjs --sourcemap --outfile=../priv/static/phoenix.cjs.js)),\n    cdn:\n      esbuild.(\n        ~w(--target=es2016 --format=iife --global-name=Phoenix --outfile=../priv/static/phoenix.js)\n      ),\n    cdn_min:\n      esbuild.(\n        ~w(--target=es2016 --format=iife --global-name=Phoenix --minify --outfile=../priv/static/phoenix.min.js)\n      )\nend\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import jest from \"eslint-plugin-jest\"\nimport js from \"@eslint/js\"\nimport stylistic from \"@stylistic/eslint-plugin\"\n\nexport default [\n  {\n    // eslint config is very unintuitive; they will match an js file in any\n    // directory by default and you can only expand this;\n    // moreover, to have a global ignore, it must be specified without\n    // any other key as a separate object...\n    ignores: [\n      \"integration_test/\",\n      \"installer/\",\n      \"doc/\",\n      \"deps/\",\n      \"coverage/\",\n      \"priv/\",\n      \"tmp/\",\n      \"test/\"\n    ],\n  },\n  {\n    ...js.configs.recommended,\n\n    plugins: {\n      jest,\n      \"@stylistic\": stylistic\n    },\n\n    languageOptions: {\n      globals: {\n        ...jest.environments.globals.globals,\n        global: \"writable\",\n      },\n\n      ecmaVersion: 12,\n      sourceType: \"module\",\n    },\n\n    rules: {\n      \"@stylistic/indent\": [\"error\", 2, {\n        SwitchCase: 1,\n      }],\n      \n      \"@stylistic/linebreak-style\": [\"error\", \"unix\"],\n      \"@stylistic/quotes\": [\"error\", \"double\"],\n      \"@stylistic/semi\": [\"error\", \"never\"],\n      \n      \"@stylistic/object-curly-spacing\": [\"error\", \"never\", {\n        objectsInObjects: false,\n        arraysInObjects: false,\n      }],\n      \n      \"@stylistic/array-bracket-spacing\": [\"error\", \"never\"],\n      \n      \"@stylistic/comma-spacing\": [\"error\", {\n        before: false,\n        after: true,\n      }],\n      \n      \"@stylistic/computed-property-spacing\": [\"error\", \"never\"],\n      \n      \"@stylistic/space-before-blocks\": [\"error\", {\n        functions: \"never\",\n        keywords: \"never\",\n        classes: \"always\",\n      }],\n      \n      \"@stylistic/keyword-spacing\": [\"error\", {\n        overrides: {\n          if: {\n            after: false,\n          },\n      \n          for: {\n            after: false,\n          },\n      \n          while: {\n            after: false,\n          },\n      \n          switch: {\n            after: false,\n          },\n        },\n      }],\n      \n      \"@stylistic/eol-last\": [\"error\", \"always\"],\n      \n      \"no-unused-vars\": [\"error\", {\n        argsIgnorePattern: \"^_\",\n        varsIgnorePattern: \"^_\",\n      }],\n      \n      \"no-useless-escape\": \"off\",\n      \"no-cond-assign\": \"off\",\n      \"no-case-declarations\": \"off\",\n    },\n  }]\n"
  },
  {
    "path": "guides/asset_management.md",
    "content": "# Asset Management\n\nBeside producing HTML, most web applications have various assets (JavaScript, CSS, images, fonts and so on).\n\nFrom Phoenix v1.7, new applications use [esbuild](https://esbuild.github.io/) to prepare assets via the [Elixir esbuild wrapper](https://github.com/phoenixframework/esbuild), and [tailwindcss](https://tailwindcss.com) via the [Elixir tailwindcss wrapper](https://github.com/phoenixframework/tailwind) for CSS. The direct integration with `esbuild` and `tailwind` means that newly generated applications do not have dependencies on Node.js or an external build system (e.g. Webpack).\n\nYour JavaScript is typically placed at \"assets/js/app.js\" and `esbuild` will extract it to \"priv/static/assets/js/app.js\". In development, this is done automatically via the `esbuild` watcher. In production, this is done by running `mix assets.deploy`.\n\n`esbuild` can also handle your CSS files, but by default `tailwind` handles all CSS building.\n\nFinally, all other assets, that usually don't have to be preprocessed, go directly to \"priv/static\".\n\n## Third-party JS packages\n\nIf you want to import JavaScript dependencies, you have at least three options to add them to your application:\n\n1. Vendor those dependencies inside your project and import them in your \"assets/js/app.js\" using a relative path:\n\n   ```javascript\n   import topbar from \"../vendor/topbar\"\n   ```\n\n2. Call `npm install topbar --prefix assets`, which will create `package.json` and `package-lock.json` inside your assets directory, and `esbuild` will be able to automatically pick them up:\n\n   ```javascript\n   import topbar from \"topbar\"\n   ```\n\n   To ensure that `npm install` is being run when checking out your project, or when building a release, add a `\"cmd --cd assets npm ci\"` step in `mix.exs` to the `assets.deploy` and  `assets.build` steps:\n\n```elixir   \n      \"assets.build\": [\"cmd --cd assets npm ci\", \"tailwind your_app\", \"esbuild your_app\"],\n      \"assets.deploy\": [\n        \"cmd --cd assets npm ci\",\n        \"tailwind your_app --minify\",\n        \"esbuild your_app --minify\",\n        \"phx.digest\"\n      ]\n```\n\n3. Use Mix to track the dependency from a source repository:\n\n   ```elixir\n   # mix.exs\n   {:topbar, github: \"buunguyen/topbar\", app: false, compile: false}\n   ```\n\n   Run `mix deps.get` to fetch the dependency and then import it:\n\n   ```javascript\n   import topbar from \"topbar\"\n   ```\n\n   New applications use this third approach to import icons, such as Heroicons,\n   to avoid vendoring a copy of all icons and to avoid additional system\n   dependencies such as `npm`, while you can still track explicit versions\n   thanks to Mix. It is important to note that git dependencies cannot be used\n   by Hex packages, so if you intend to publish your project to Hex, consider\n   alternatives approaches.\n\nNote that if you use third party JS package managers, you might need to adjust your\ndeployment steps to properly include the packages. If you're using\n`mix phx.gen.release --docker`, have a look at the\n[documentation](Mix.Tasks.Phx.Gen.Release.html#module-docker) for further details.\n\n## Images, fonts, and external files\n\nIf you reference an external file in your CSS or JavaScript files, `esbuild` will attempt to validate and manage them, unless told otherwise.\n\nFor example, imagine you want to reference `priv/static/images/bg.png`, served at `/images/bg.png`, from your CSS file:\n\n```css\nbody {\n  background-image: url(/images/bg.png);\n}\n```\n\nThe above may fail with the following message:\n\n```text\nerror: Could not resolve \"/images/bg.png\" (mark it as external to exclude it from the bundle)\n```\n\nGiven the images are already managed by Phoenix, you need to mark all resources from `/images` (and also `/fonts`) as external, as the error message says. This is what Phoenix does by default for new apps since v1.6.1+. In your `config/config.exs`, you will find:\n\n```elixir\nargs: ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/*),\n```\n\nIf you need to reference other directories, you need to update the arguments above accordingly. Note running `mix phx.digest` will create digested files for all of the assets in `priv/static`, so your images and fonts are still cache-busted.\n\n### Ensuring fonts and images from third-party libraries are loaded\n\nIf you import a Node package that depends on additional fonts or images, you might find them to fail to load. This is because they are referenced in the JS or CSS but by default Esbuild will not touch or process referenced files. You can add arguments to esbuild in `config/config.exs` to ensure that the referenced resources are copied to the output folder. The following example would copy all referenced font files to the output folder and prefix the paths with `/assets/`:\n\n```elixir\nargs: ~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/* --public-path=/assets/ --loader:.woff=copy  --loader:.ttf=copy --loader:.eot=copy --loader:.woff2=copy),\n```\nFor more information, see [the esbuild documentation](https://esbuild.github.io/content-types/#copy).\n\n## Esbuild plugins\n\nPhoenix's default configuration of `esbuild` (via the Elixir wrapper) does not allow you to use [esbuild plugins](https://esbuild.github.io/plugins/). If you want to use an esbuild plugin, for example to compile SASS files to CSS, you can replace the default build system with a custom build script.\n\nThe following is an example of a custom build using esbuild via Node.js. First of all, you'll need to install Node.js in development and make it available for your production build step.\n\nThen you'll need to add `esbuild` to your Node.js packages and the Phoenix packages. Inside the `assets` directory, run:\n\n```console\n$ npm install esbuild --save-dev\n$ npm install ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view --save\n```\n\nor, for Yarn:\n\n```console\n$ yarn add --dev esbuild\n$ yarn add ../deps/phoenix ../deps/phoenix_html ../deps/phoenix_live_view\n```\n\nNext, add a custom JavaScript build script. We'll call the example `assets/build.js`:\n\n```javascript\nconst esbuild = require(\"esbuild\");\n\nconst args = process.argv.slice(2);\nconst watch = args.includes('--watch');\nconst deploy = args.includes('--deploy');\n\nconst loader = {\n  // Add loaders for images/fonts/etc, e.g. { '.svg': 'file' }\n};\n\nconst plugins = [\n  // Add and configure plugins here\n];\n\n// Define esbuild options\nlet opts = {\n  entryPoints: [\"js/app.js\"],\n  bundle: true,\n  logLevel: \"info\",\n  target: \"es2022\",\n  outdir: \"../priv/static/assets\",\n  external: [\"*.css\", \"fonts/*\", \"images/*\"],\n  nodePaths: [\"../deps\"],\n  loader: loader,\n  plugins: plugins,\n};\n\nif (deploy) {\n  opts = {\n    ...opts,\n    minify: true,\n  };\n}\n\nif (watch) {\n  opts = {\n    ...opts,\n    sourcemap: \"inline\",\n  };\n  esbuild\n    .context(opts)\n    .then((ctx) => {\n      ctx.watch();\n    })\n    .catch((_error) => {\n      process.exit(1);\n    });\n} else {\n  esbuild.build(opts);\n}\n```\n\nThis script covers following use cases:\n\n- `node build.js`: builds for development & testing (useful on CI)\n- `node build.js --watch`: like above, but watches for changes continuously\n- `node build.js --deploy`: builds minified assets for production\n\nModify `config/dev.exs` so that the script runs whenever you change files, replacing the existing `:esbuild` configuration under `watchers`:\n\n```elixir\nconfig :hello, HelloWeb.Endpoint,\n  ...\n  watchers: [\n    node: [\"build.js\", \"--watch\", cd: Path.expand(\"../assets\", __DIR__)]\n  ],\n  ...\n```\n\nModify the `aliases` task in `mix.exs` to install `npm` packages during `mix setup` and use the new `esbuild` on `mix assets.deploy`:\n\n```elixir\n  defp aliases do\n    [\n      setup: [\"deps.get\", \"ecto.setup\", \"cmd --cd assets npm install\"],\n      ...,\n      \"assets.deploy\": [\"cmd --cd assets node build.js --deploy\", \"phx.digest\"]\n    ]\n  end\n```\n\nFinally, remove the `esbuild` configuration from `config/config.exs` and remove the dependency from the `deps` function in your `mix.exs`, and you are done!\n\n## Alternative JS build tools\n\nIf you are writing an API or you want to use another asset build tool, you may want to remove the `esbuild` Hex package (see steps below). Then you must follow the additional steps required by the third-party tool.\n\n### Remove esbuild\n\n1. Remove the `esbuild` configuration in `config/config.exs` and `config/dev.exs`,\n2. Remove the `assets.deploy` task defined in `mix.exs`,\n3. Remove the `esbuild` dependency from `mix.exs`,\n4. Unlock the `esbuild` dependency:\n\n```console\n$ mix deps.unlock esbuild\n```\n\n## Alternative CSS frameworks\n\nBy default, Phoenix generates CSS with the `tailwind` library and its default plugins.\n\nIf you want to use external `tailwind` plugins or another CSS framework, you should replace the `tailwind` Hex package (see steps below). Then you can use an `esbuild` plugin (as outlined above) or even bring a separate framework altogether.\n\n### Remove tailwind\n\n1. Remove the `tailwind` configuration in `config/config.exs` and `config/dev.exs`,\n2. Remove the `assets.deploy` task defined in `mix.exs`,\n3. Remove the `tailwind` dependency from `mix.exs`,\n4. Unlock the `tailwind` dependency:\n\n```console\n$ mix deps.unlock tailwind\n```\n\nYou may optionally remove and delete the `heroicons` dependency as well.\n\n## Alternative icon libraries\n\nPhoenix ships with the [Heroicons](https://heroicons.com/) library for icons support.\nThis is done by embedding icons as CSS classes, which guarantees only the icons actually\nused by your application are sent to the client, thanks to Tailwind.\n\nIf you prefer to use an alternative icon set, it should be possible to adapt the\ncode that embeds Heroicons to use another library. Let's see exactly how to do that\nusing [Remix Icon](https://remixicon.com/) as an example:\n\nFirst replace the `heroicon` repository in your `mix.exs` by `remixicons`:\n\n```elixir\n{:remixicons,\n  github: \"Remix-Design/RemixIcon\",\n  sparse: \"icons\",\n  tag: \"v4.6.0\",\n  app: false,\n  compile: false,\n  depth: 1},\n```\n\nThen replace `assets/vendor/heroicons.js`, which traverses the heroicons dependency, by `assets/vendor/remixicons.js`, which traverses remix icons instead:\n\n```js\nconst plugin = require(\"tailwindcss/plugin\")\nconst fs = require(\"fs\")\nconst path = require(\"path\")\n\nmodule.exports = plugin(function({matchComponents, theme}) {\n  let baseDir = path.join(__dirname, \"../../deps/remixicons/icons\");\n  let values = {};\n  let icons = fs\n    .readdirSync(baseDir, { withFileTypes: true })\n    .filter((dirent) => dirent.isDirectory())\n    .map((dirent) => dirent.name);\n\n  icons.forEach((dir) => {\n    fs.readdirSync(path.join(baseDir, dir)).map((file) => {\n      let name = path.basename(file, \".svg\");\n      values[name] = { name, fullPath: path.join(baseDir, dir, file) };\n    });\n  });\n\n  matchComponents(\n    {\n      ri: ({ name, fullPath }) => {\n        let content = fs\n          .readFileSync(fullPath)\n          .toString()\n          .replace(/\\r?\\n|\\r/g, \"\");\n\n        return {\n          [`--ri-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,\n          \"-webkit-mask\": `var(--ri-${name})`,\n          mask: `var(--ri-${name})`,\n          \"background-color\": \"currentColor\",\n          \"vertical-align\": \"middle\",\n          display: \"inline-block\",\n          width: theme(\"spacing.10\"),\n          height: theme(\"spacing.10\"),\n        };\n      },\n    },\n    { values },\n  );\n})\n```\n\nAnd then change `assets/css/app.css` to import your new plugin instead.\n\nFinally, update the `icon` function in `lib/my_app_web/components/core_components.ex`\nto match on `ri-` prefixes instead:\n\n```\n@doc \"\"\"\nRenders a [Remix Icon](https://remixicon.com).\n\nYou can customize the size and colors of the icons by\nsetting width, height, and background color classes.\n\n## Examples\n\n    <.icon name=\"ri-github-fill\" />\n    <.icon name=\"ri-github\" class=\"ml-1 w-3 h-3 animate-spin\" />\n\"\"\"\nattr :name, :string, required: true\nattr :class, :any, default: \"size-5\"\n\ndef icon(%{name: \"ri-\" <> _} = assigns) do\n  ~H\"\"\"\n  <i class={[@name, @class]} aria-hidden=\"true\"></i>\n  \"\"\"\nend\n```\n\nNow replace the Heroicons in your application by Remix ones and you are good to go!\n\nThe approach above may also work with other libraries, it is a matter of adapting\nthe Tailwind plugin to traverse these libraries and generate the proper classes.\nSome iconsets may also be available as regular Hex packages too.\n"
  },
  {
    "path": "guides/authn_authz/api_authentication.md",
    "content": "# API Authentication\n\n> **Requirement**: This guide expects that you have gone through the [`mix phx.gen.auth`](mix_phx_gen_auth.html) guide.\n\nThis guide shows how to add API authentication on top of `mix phx.gen.auth`. Since the authentication generator already includes a token table, we use it to store API tokens too, following the best security practices.\n\nWe will break this guide in two parts: augmenting the context and the plug implementation. We will assume that the following `mix phx.gen.auth` command was executed:\n\n```\n$ mix phx.gen.auth Accounts User users\n```\n\nIf you ran something else, it should be trivial to adapt the names.\n\n## Adding API functions to the context\n\nOur authentication system will require two functions. One to create the API token and another to verify it. Open up `lib/my_app/accounts.ex` and add these two new functions:\n\n```elixir\n  ## API\n\n  @doc \"\"\"\n  Creates a new api token for a user.\n\n  The token returned must be saved somewhere safe.\n  This token cannot be recovered from the database.\n  \"\"\"\n  def create_user_api_token(user) do\n    {encoded_token, user_token} = UserToken.build_email_token(user, \"api-token\")\n    Repo.insert!(user_token)\n    encoded_token\n  end\n\n  @doc \"\"\"\n  Fetches the user by API token.\n  \"\"\"\n  def fetch_user_by_api_token(token) do\n    with {:ok, query} <- UserToken.verify_api_token_query(token),\n         %User{} = user <- Repo.one(query) do\n      {:ok, user}\n    else\n      _ -> :error\n    end\n  end\n```\n\nThe new functions use the existing `UserToken` functionality to store a new type of token called \"api-token\". Because this is an email token, if the user changes their email, the tokens will be expired.\n\nAlso notice we called the second function `fetch_user_by_api_token`, instead of `get_user_by_api_token`. Because we want to render different status codes in our API, depending if a user was found or not, we return `{:ok, user}` or `:error`. Elixir's convention is to call these functions `fetch_*`, instead of `get_*` which would usually return `nil` instead of tuples.\n\nTo make sure our new functions work, let's write tests. Open up `test/my_app/accounts_test.exs` and add this new describe block:\n\n```elixir\n  describe \"create_user_api_token/1 and fetch_user_by_api_token/1\" do\n    test \"creates and fetches by token\" do\n      user = user_fixture()\n      token = Accounts.create_user_api_token(user)\n      assert Accounts.fetch_user_by_api_token(token) == {:ok, user}\n      assert Accounts.fetch_user_by_api_token(\"invalid\") == :error\n    end\n  end\n```\n\nIf you run the tests, they will actually fail. Something similar to this:\n\n```console\n1) test create_user_api_token/1 and fetch_user_by_api_token/1 creates and fetches by token (Demo.AccountsTest)\n    test/demo/accounts_test.exs:380\n    ** (UndefinedFunctionError) function Demo.Accounts.UserToken.verify_api_token_query/1 is undefined or private. Did you mean:\n\n          * verify_change_email_token_query/2\n          * verify_magic_link_token_query/1\n          * verify_session_token_query/1\n    \n    code: assert Accounts.fetch_user_by_api_token(token) == {:ok, user}\n    stacktrace:\n      (demo 0.1.0) Demo.Accounts.UserToken.verify_api_token_query(\"sTpJg7rt-KQ9gZ7xLMtn2keusGk9N2JpPwkXDx7LmHU\")\n      (demo 0.1.0) lib/demo/accounts.ex:325: Demo.Accounts.fetch_user_by_api_token/1\n      test/demo/accounts_test.exs:383: (test)\n```\n\nIf you prefer, try looking at the error and fixing it yourself. The explanation will come next.\n\nThe `UserToken` module contains functions for verifying different tokens. Right now, there is no `verify_api_token_query/1`, but we can implement it similar to the existing functions. How long the API token should be valid is going to depend on your application and how sensitive it is in terms of security. For this example, let's say the token is valid for 365 days.\n\nOpen up `lib/my_app/accounts/user_token.ex`, and add a new function, like this:\n\n```elixir\n  @doc \"\"\"\n  Checks if the API token is valid and returns its underlying lookup query.\n\n  The query returns the user found by the token, if any.\n\n  The given token is valid if it matches its hashed counterpart in the\n  database and the user email has not changed. This function also checks\n  if the token is being used within 365 days.\n  \"\"\"\n  def verify_api_token_query(token) do\n    case Base.url_decode64(token, padding: false) do\n      {:ok, decoded_token} ->\n        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)\n\n        query =\n          from token in by_token_and_context_query(hashed_token, \"api-token\"),\n            join: user in assoc(token, :user),\n            where:\n              token.inserted_at > ago(^@api_token_validity_in_days, \"day\") and\n                token.sent_to == user.email,\n            select: user\n\n        {:ok, query}\n\n      :error ->\n        :error\n    end\n  end\n```\n\nNote that we also added a `@api_token_validity_in_days` module attribute at the top of the file:\n\n```diff\n   @magic_link_validity_in_minutes 15\n   @change_email_validity_in_days 7\n   @session_validity_in_days 60\n+  @api_token_validity_in_days 365\n```\n\nNow tests should pass and we are ready to move forward!\n\n## API authentication plug\n\nThe last part is to add authentication to our API.\n\nWhen we ran `mix phx.gen.auth`, it generated a `MyAppWeb.UserAuth` module with several plugs, which are small functions that receive the `conn` and customize our request/response life-cycle. Open up `lib/my_app_web/user_auth.ex` and add this new function:\n\n```elixir\ndef fetch_current_scope_for_api_user(conn, _opts) do\n  with [<<bearer::binary-size(6), \" \", token::binary>>] <-\n         get_req_header(conn, \"authorization\"),\n       true <- String.downcase(bearer) == \"bearer\",\n       {:ok, user} <- Accounts.fetch_user_by_api_token(token) do\n    assign(conn, :current_scope, Scope.for_user(user))\n  else\n    _ ->\n      conn\n      |> send_resp(:unauthorized, \"No access for you\")\n      |> halt()\n  end\nend\n```\n\nOur function receives the connection and checks if the \"authorization\" header has been set with \"Bearer TOKEN\", where \"TOKEN\" is the value returned by `Accounts.create_user_api_token/1`. In case the token is not valid or there is no such user, we abort the request.\n\nFinally, we need to add this `plug` to our pipeline. Open up `lib/my_app_web/router.ex` and you will find a pipeline for API. Let's add our new plug under it, like this:\n\n```elixir\n  pipeline :api do\n    plug :accepts, [\"json\"]\n    plug :fetch_current_scope_for_api_user\n  end\n```\n\nNow you are ready to receive and validate API requests. Feel free to open up `test/my_app_web/user_auth_test.exs` and write your own test. You can use the tests for other plugs as templates!\n\n## Your turn\n\nThe overall API authentication flow will depend on your application.\n\nIf you want to use this token in a JavaScript client, you will need to slightly alter the `UserSessionController` to invoke `Accounts.create_user_api_token/1` and return a JSON response including the token.\n\nIf you want to provide APIs for 3rd-party users, you will need to allow them to create tokens, and show the result of `Accounts.create_user_api_token/1` to them. They must save these tokens somewhere safe and include them as part of their requests using the \"authorization\" header.\n"
  },
  {
    "path": "guides/authn_authz/authn_authz.md",
    "content": "# Introduction to Auth\n\nAuthentication (authn) and authorization (authz) are two important concepts in security. Authentication is the process of verifying the identity of a user or system, while authorization is the process of granting or denying access to resources based on the user's identity and permissions.\n\nPhoenix comes with built-in support for both. Generally speaking, developers use the `mix phx.gen.auth` generator to scaffold their authn and authz. Third-party libraries such as [Ueberauth](https://github.com/ueberauth/ueberauth) can be used either as complementary systems or by itself.\n\nOverall we have the following guides:\n\n  * [mix phx.gen.auth](mix_phx_gen_auth.md) - An introduction to the `mix phx.gen.auth` generator and its security considerations.\n\n  * [Scopes](scopes.md) - Scopes are the mechanism Phoenix v1.8 introduced to manage access to resources based on the user's identity and permissions.\n\n  * [API Authentication](api_authentication.md) - An additional guide that shows how to expand `mix phx.gen.auth` code to support token-based API authentication.\n"
  },
  {
    "path": "guides/authn_authz/mix_phx_gen_auth.md",
    "content": "# mix phx.gen.auth\n\nThe `mix phx.gen.auth` command generates a flexible, pre-built authentication system into your Phoenix app. This generator allows you to quickly move past the task of adding authentication to your codebase and stay focused on the real-world problem your application is trying to solve. It supports the following features:\n\n- User registration with account confirmation by email\n- Log in with magic links\n- Opt-in password authentication\n- \"Sudo mode\", also known as privileged authentication, where the user must confirm their identity before performing sensitive actions\n\n## Getting started\n\n> Before running this command, consider committing your work as it generates multiple files.\n\nLet's start by running the following command from the root of our app:\n\n```console\n$ mix phx.gen.auth Accounts User users\n\nAn authentication system can be created in two different ways:\n- Using Phoenix.LiveView (default)\n- Using Phoenix.Controller only\n\nDo you want to create a LiveView based authentication system? [Y/n] Y\n```\n\nThe first argument is the context module followed by the schema module\nand its plural name (used as the schema table name). The example above\nwill generate an `Accounts` context module with two schemas inside:\n`User` and `UserToken`. The context module helps us group all of the\ndifferent schemas related to authentication. You may name the context\nand schema according to your preferences.\n\nThe authentication generators support Phoenix LiveView, for enhanced UX,\nso you should answer `Y` here. You may also answer `n` for a controller\nbased authentication system. Either approach will create the same context\nand schemas, using the same table names and route paths.\n\nSince this generator installed additional dependencies in `mix.exs`,\nlet's fetch those:\n\n```console\n$ mix deps.get\n```\n\nNow run the pending repository migrations:\n\n```console\n$ mix ecto.migrate\n```\n\nLet's run the tests to make sure our new authentication system works as expected.\n\n```console\n$ mix test\n```\n\nAnd finally, let's start our Phoenix server and try it out (note the new `Register` and `Log in` links at the top right of the default page).\n\n```console\n$ mix phx.server\n```\n\n## Developer responsibilities\n\nSince Phoenix generates this code into your application instead of building these modules into Phoenix itself, you now have complete freedom to modify the authentication system, so it works best with your use case. The one caveat with using a generated authentication system is it will not be updated after it's been generated. Therefore, as improvements are made to the output of `mix phx.gen.auth`, it becomes your responsibility to determine if these changes need to be ported into your application. Security-related and other important improvements will be explicitly and clearly marked in the `CHANGELOG.md` file and upgrade notes.\n\n## Generated code\n\nThe following are notes about the generated authentication system.\n\n### Forbidding access\n\nThe generated code ships with an authentication module with a handful of plugs that fetch the current user, require authentication and so on. For instance, in an app named MyApp which had `mix phx.gen.auth Accounts User users` run on it, you will find a module named `MyAppWeb.UserAuth` with plugs such as:\n\n  * `fetch_current_scope_for_user` - fetches the current user information if available and stores it as `:current_scope` assign\n  * `require_authenticated_user` - must be invoked after `fetch_current_scope_for_user` and requires that a current user exists and is authenticated\n  * `redirect_if_user_is_authenticated` - used for the few pages that must not be available to authenticated users (only generated for controller based authentication)\n  * `require_sudo_mode` - used for pages that contain sensitive operations and enforces recent authentication\n\n### Scopes\n\nThe generated code includes a scope module. For an app named MyApp which had `mix phx.gen.auth Accounts User users` run on it, you will find the following module at `lib/my_app/accounts/scope.ex`:\n\n```elixir\ndefmodule MyApp.Accounts.Scope do\n  # ...\n  alias MyApp.Accounts.User\n\n  defstruct user: nil\n\n  @doc \"\"\"\n  Creates a scope for the given user.\n\n  Returns nil if no user is given.\n  \"\"\"\n  def for_user(%User{} = user) do\n    %__MODULE__{user: user}\n  end\n\n  def for_user(nil), do: nil\nend\n```\n\nThe scope data structure is stored in the assigns and available to your Controllers and LiveViews. As your application grows in complexity, this data structure can store important metadata such as the teams, companies, or organizations the user belongs to, permissions, telemetry information such as IP address and so forth.\n\nFurthermore, future Phoenix generator invocations will automatically pass this data structure from your Controllers and LiveViews to most of [your context operations](contexts.md), making sure that future data is scoped to the current user/team/company/organization. Scopes are essential to enforce the user can only access data they own. You can learn more about them in the [Scopes](scopes.md) guide.\n\n### Password hashing\n\nThe password hashing mechanism defaults to `bcrypt` for Unix systems and `pbkdf2` for Windows systems. Both systems use the [Comeonin interface](https://hexdocs.pm/comeonin/).\n\nThe password hashing mechanism can be overridden with the `--hashing-lib` option. The following values are supported:\n\n  * `bcrypt` - [bcrypt_elixir](https://hex.pm/packages/bcrypt_elixir)\n  * `pbkdf2` - [pbkdf2_elixir](https://hex.pm/packages/pbkdf2_elixir)\n  * `argon2` - [argon2_elixir](https://hex.pm/packages/argon2_elixir)\n\nWe recommend developers to consider using `argon2`, which is the most robust of all 3. The downside is that `argon2` is quite CPU and memory intensive, and you will need more powerful instances to run your applications on.\n\nFor more information about choosing these libraries, see the [Comeonin project](https://github.com/riverrun/comeonin).\n\nThere are similar `:on_mount` hooks for LiveView based authentication.\n\n### Notifiers\n\nThe generated code is not integrated with any system to send SMSes or emails for confirming accounts, resetting passwords, etc. Instead, it simply logs a message to the terminal. It is your responsibility to integrate with the proper system after generation.\n\nNote that if you generated your Phoenix project with `mix phx.new`, your project is configured to use [Swoosh](https://hexdocs.pm/swoosh/Swoosh.html) mailer by default. To view notifier emails during development with Swoosh, navigate to `/dev/mailbox`.\n\n### Concurrent tests\n\nThe generated tests run concurrently if you are using a database that supports concurrent tests, which is the case of PostgreSQL.\n\n### More about `mix phx.gen.auth`\n\nCheck out `mix phx.gen.auth` for more details, such as using a different password hashing library, customizing the web module namespace, generating binary id type, configuring the default options, and using custom table names.\n\n## Security considerations\n\n### Tracking sessions\n\nAll sessions and tokens are tracked in a separate table. This allows you to track how many sessions are active for each account. You could even expose this information to users if desired.\n\nNote that whenever the password changes (either via reset password or directly), all tokens are deleted, and the user has to log in again on all devices.\n\n### User Enumeration attacks\n\nA user enumeration attack allows someone to check if an email is registered in the application. The generated authentication code does not attempt to protect from such attacks. For instance, when you register an account, if the email is already registered, the code will notify the user the email is already registered.\n\nIf your application is sensitive to enumeration attacks, you need to implement your own workflows, which tends to be very different from most applications, as you need to carefully balance security and user experience.\n\nFurthermore, if you are concerned about enumeration attacks, beware of timing attacks too. For example, registering a new account typically involves additional work (such as writing to the database, sending emails, etc) compared to when an account already exists. Someone could measure the time taken to execute those additional tasks to enumerate emails. This applies to all endpoints (registration, login, etc.) that may send email, in-app notifications, etc.\n\n### Confirmation and credential pre-stuffing attacks\n\nThe generated functionality ships with an account confirmation mechanism, where users have to confirm their account, typically by email. Furthermore, to prevent security issues, the generated code does forbid users from using the application if their accounts have not yet been confirmed. If you want to change this behavior, please refer to the [\"Mixing magic link and password registration\" section](Mix.Tasks.Phx.Gen.Auth.html#module-mixing-magic-link-and-password-registration) of `mix phx.gen.auth`.\n\n### Case sensitiveness\n\nThe email lookup is made to be case-insensitive. Case-insensitive lookups are the default in MySQL and MSSQL. In SQLite3 we use [`COLLATE NOCASE`](https://www.sqlite.org/datatype3.html#collating_sequences) in the column definition to support it. In PostgreSQL, we use the [`citext` extension](https://www.postgresql.org/docs/current/citext.html).\n\nNote `citext` is part of PostgreSQL itself and is bundled with it in most operating systems and package managers. `mix phx.gen.auth` takes care of creating the extension and no extra work is necessary in the majority of cases. If by any chance your package manager splits `citext` into a separate package, you will get an error while migrating, and you can most likely solve it by installing the `postgres-contrib` package.\n\n## Additional resources\n\n### Migrating to Phoenix v1.8 magic links and sudo mode\n\nPhoenix v1.8 added new features and simplified the authentication code. Developers are not required to migrate to the new generators, although we recommend setting up your own scope, as defined in the [Scopes](scopes.md) guide.\n\nIf you generated your authentication code with `mix phx.gen.auth` in Phoenix v1.7 or earlier and you want to migrate to the new generators, you can use the following pull requests as reference:\n\n  * [Pull request for migrating LiveView based Phoenix 1.7 `phx.gen.auth` to magic links](https://github.com/SteffenDE/phoenix_gen_auth_magic_link/pull/1)\n  * [Pull request for migrating controller based Phoenix 1.7 `phx.gen.auth` to magic links](https://github.com/SteffenDE/phoenix_gen_auth_magic_link/pull/2)\n\nKeep in mind that the new authentication system fully removes registering an account with password, which simplifies both the user experience and the generated code. Therefore, when migrating, you should not change your existing migration files, instead, you must make the `hashed_password` column optional by setting `null: true`. Also, when migrating to the new system and removing features like \"Forgot your password?\", you must set the `hashed_password` of all accounts that have not been confirmed to `nil`, after making the column nullable, to avoid credential stuffing attacks. For this reason, we recommend deploying the migrated authentication system during low-traffic periods, where ideally no user who has just registered an account would have their password nullified. If those trade-offs are not acceptable, [you can add magic links on top of your existing authentication system without a complete migration, as discussed here](https://github.com/srcrip/phoenix-magic-links).\n\n### Initial implementation\n\nThe following links describe the original implementation of the authentication system, the default up to Phoenix v1.7:\n\n  * José Valim's blog post - [An upcoming authentication solution for Phoenix](https://dashbit.co/blog/a-new-authentication-solution-for-phoenix)\n  * Berenice Medel's blog post on generating LiveViews for authentication (rather than conventional Controllers & Views) - [Bringing Phoenix Authentication to Life](https://fly.io/phoenix-files/phx-gen-auth/)\n  * [Original design spec](https://github.com/dashbitco/mix_phx_gen_auth_demo/blob/auth/README.md)\n  * [Pull request on bare Phoenix app](https://github.com/dashbitco/mix_phx_gen_auth_demo/pull/1)\n"
  },
  {
    "path": "guides/authn_authz/scopes.md",
    "content": "# Scopes\n\nA scope is a data structure used to keep information about the current request or session, such as the current user, the user's organization/company, permissions, and so on. Think of a scope as a container with information required by the majority of pages in your application. A scope can also hold request metadata, such as IP addresses.\n\nScopes play an important role in security. [OWASP](https://owasp.org/) lists \"Broken access control\" as a [top-10 security risk](https://owasp.org/Top10/). Most application data is private for a user, a team, or an organization. Your database CRUD operations must be properly scoped to the current user/team/organization. Phoenix generators such as `mix phx.gen.html`, `mix phx.gen.json`, and `mix phx.gen.live` automatically use your custom scopes.\n\nScopes are flexible. You can have more than one scope in your application and choose the specific scope when invoking a generator. When you run `mix phx.gen.auth`, it will automatically generate a scope for you, but you may also add your own.\n\nThis guide will:\n\n* Show how `mix phx.gen.auth` generates a scope for you\n* Discuss how generators, such as `mix phx.gen.context`, rely on scopes for security\n* How to define your own scope from scratch and all valid options\n* Augment the built-in scope with additional scopes\n\n## phx.gen.auth\n\nThe task `mix phx.gen.auth` will generate a default scope. This scope ties the generated resources to the currently authenticated user. Let's see it in action:\n\n```console\n$ mix phx.gen.auth Accounts User users\n```\n\nThe scope code is the same for the `--live` and `--no-live` variants of the generator.\n\nLooking at the generated scope file `lib/my_app/accounts/scope.ex`, we can see that it defines a struct with a single `user` field, and a function `for_user/1` that, if given a `User` struct, returns a new `%Scope{}` for that user.\n\n```elixir\ndefmodule MyApp.Accounts.Scope do\n  alias MyApp.Accounts.User\n\n  defstruct user: nil\n\n  def for_user(%User{} = user) do\n    %__MODULE__{user: user}\n  end\n\n  def for_user(nil), do: nil\nend\n```\n\nThe scope is automatically fetched by the `fetch_current_scope_for_user` plug that is injected into the `:browser` pipeline:\n\n```elixir\n# router.ex\n...\npipeline :browser do\n  ...\n  plug :fetch_current_scope_for_user\nend\n```\n\n```elixir\n# user_auth.ex\ndef fetch_current_scope_for_user(conn, _opts) do\n  {user_token, conn} = ensure_user_token(conn)\n  user = user_token && Accounts.get_user_by_session_token(user_token)\n  assign(conn, :current_scope, Scope.for_user(user))\nend\n```\n\nSimilarly, for LiveViews, there is a pre-defined `mount_current_scope` hook that ensures\nthe scope is available:\n\n```elixir\n# user_auth.ex\ndef on_mount(:mount_current_scope, _params, session, socket) do\n  {:cont, mount_current_scope(socket, session)}\nend\n\ndefp mount_current_scope(socket, session) do\n  Phoenix.Component.assign_new(socket, :current_scope, fn ->\n    user =\n      if user_token = session[\"user_token\"] do\n        Accounts.get_user_by_session_token(user_token)\n      end\n\n    Scope.for_user(user)\n  end)\nend\n```\n\n## Integration of scopes in the Phoenix generators\n\nIf a default scope is defined in your application's config, the generators will build scoped resources by default. The generated LiveViews / Controllers will automatically pass the scope to the context functions. `mix phx.gen.auth` automatically sets its scope as default, if there is not already a default scope defined:\n\n```elixir\n# config/config.exs\nconfig :my_app, :scopes,\n  user: [\n    default: true,\n    ...\n  ]\n```\n\nWe will look at the individual options in the next section.\n\nNow let's look at the code generated once a default scope is set. We will use `mix phx.gen.live` as an example, but the ideas and the overall code will be similar to `mix phx.gen.html` and `mix phx.gen.json` too:\n\n```console\n$ mix phx.gen.live Blog Post posts title:string body:text\n```\n\nThis creates a new `Blog` context, with a `Post` resource. To ensure the scope is available, for LiveViews the routes in your `router.ex` must be added to a `live_session` that ensures the user is authenticated. In this case, within the aptly named `required_authenticated_user` section:\n\n```diff\n   scope \"/\", MyAppWeb do\n     pipe_through [:browser, :require_authenticated_user]\n\n     live_session :require_authenticated_user,\n       on_mount: [{MyAppWeb.UserAuth, :ensure_authenticated}] do\n       live \"/users/settings\", UserLive.Settings, :edit\n       live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n\n+      live \"/posts\", PostLive.Index, :index\n+      live \"/posts/new\", PostLive.Form, :new\n+      live \"/posts/:id\", PostLive.Show, :show\n+      live \"/posts/:id/edit\", PostLive.Form, :edit\n     end\n\n     post \"/users/update-password\", UserSessionController, :update_password\n   end\n```\n\n> Although the router has a `scope` macro, the router `scope` and `current_scope` are ultimately distinct features which have similar purposes: to narrow down access to parts of our application, each acting at distinct layers (one at the router, the other at the data layer).\n\nNow, let's look at the generated LiveView (`lib/my_app_web/live/post_live/index.ex`):\n\n```elixir\ndefmodule MyAppWeb.PostLive.Index do\n  use MyAppWeb, :live_view\n\n  alias MyApp.Blog\n\n  ...\n\n  @impl true\n  def mount(_params, _session, socket) do\n    Blog.subscribe_posts(socket.assigns.current_scope)\n\n    {:ok,\n     socket\n     |> assign(:page_title, \"Listing Posts\")\n     |> stream(:posts, Blog.list_posts(socket.assigns.current_scope))}\n  end\n\n  @impl true\n  def handle_event(\"delete\", %{\"id\" => id}, socket) do\n    post = Blog.get_post!(socket.assigns.current_scope, id)\n    {:ok, _} = Blog.delete_post(socket.assigns.current_scope, post)\n\n    {:noreply, stream_delete(socket, :posts, post)}\n  end\n\n  @impl true\n  def handle_info({type, %MyApp.Blog.Post{}}, socket)\n      when type in [:created, :updated, :deleted] do\n    {:noreply, stream(socket, :posts, Blog.list_posts(socket.assigns.current_scope), reset: true)}\n  end\nend\n```\n\nNote that every function from the `Blog` context that we call gets the `current_scope` assign passed in as the first argument. The `list_posts/1` function then uses that information to properly filter posts:\n\n```elixir\n# lib/my_app/blog.ex\ndef list_posts(%Scope{} = scope) do\n  Repo.all(from post in Post, where: post.user_id == ^scope.user.id)\nend\n```\n\nThe LiveView even subscribes to scoped PubSub messages and automatically updates the rendered list whenever a new post is created or an existing post is updated or deleted, while ensuring that only messages for the current scope are processed.\n\n## Defining scopes\n\nThe Phoenix generators use your application's config to discover the available scopes. A scope is defined by the following options:\n\n```elixir\nconfig :my_app, :scopes,\n  user: [\n    default: true,\n    module: MyApp.Accounts.Scope,\n    assign_key: :current_scope,\n    access_path: [:user, :id],\n    schema_key: :user_id,\n    schema_type: :id,\n    schema_table: :users,\n    test_data_fixture: MyApp.AccountsFixtures,\n    test_setup_helper: :register_and_log_in_user\n  ]\n```\n\nIn this example, the scope is called `user` and it is the `default` scope that is automatically used when running `mix phx.gen.schema`, `mix phx.gen.context`, `mix phx.gen.live`, `mix phx.gen.html` and `mix phx.gen.json`. A scope needs a module that defines a struct, in this case `MyApp.Accounts.Scope`. Those structs are used as first argument to the generated context functions, like `list_posts/1`.\n\n* `default` - a boolean that indicates if this scope is the default scope. There can only be one default scope defined.\n\n* `module` - the module that defines the struct for this scope.\n\n* `assign_key` - the key where the scope struct is assigned to the [socket](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.Socket.html#t:t/0) or [conn](https://hexdocs.pm/plug/Plug.Conn.html).\n\n* `access_path` - a list of keys that define the path to the identifying field in the scope struct. The generators generate code like `where: schema_key == ^scope.user.id`.\n\n* `route_prefix` - (optional) a path template string for how resources should be nested. For example, `/organizations/:org` would generate routes like `/organizations/:org/posts`. The parameter segment (`:org`) will be replaced with the appropriate scope access value in templates and LiveViews.\n\n* `route_access_path` - (optional) list of keys that define the path to the field used in route generation (if `route_prefix` is set). This is particularly useful for user-friendly URLs where you might want to use a slug instead of an ID. If not specified, it defaults to `Enum.drop(scope.access_path, -1)` or `access_path` if the former is empty. For example, if the `access_path` is `[:organization, :id]`, it defaults to `[:organization]`, assuming that the value at `scope.organization` implements the `Phoenix.Param` protocol.\n\n* `schema_key` - the foreign key that ties the resource to the scope. New scoped schemas are created with a foreign key field named `schema_key` of type `schema_type` to the `schema_table` table.\n\n* `schema_type` - the type of the foreign key field in the schema. Typically `:id` or `:binary_id`.\n\n* `schema_migration_type` (optional) - the type of the foreign key column in the database. Used for the generated migration. It defaults to the default migration foreign keytype.\n\n* `schema_table` - the name of the table where the foreign key points to.\n\n* `test_data_fixture` - a module that is automatically imported into the context test file. It must have a `NAME_scope_fixture/0` function that returns a unique scope struct for context tests, in this case `user_scope_fixture/0`.\n\n* `test_setup_helper` - the name of a function that is registered as [`setup` callback](https://hexdocs.pm/ex_unit/ExUnit.Callbacks.html#setup/1) in LiveView / Controller tests. The function is expected to be imported in the test file. Usually, this is ensured by putting it into the `MyAppWeb.ConnCase` module.\n\nWhile the `mix phx.gen.auth` automatically generates a scope, scopes can also be defined manually. This can be useful, for example, to retrofit an existing application with scopes or to define scopes that are not tied to a user.\n\nFor this example, we will implement a custom scope that gives each session its own scope. While this might not be useful in most real-world applications as created resources would be inaccessible as soon as the session ends, it is a good example to understand how scopes work. See the following section for an example on how to augment an existing scope with organizations (teams, companies, or similar).\n\nFirst, let's define our scope module `lib/my_app/scope.ex`:\n\n```elixir\ndefmodule MyApp.Scope do\n  defstruct id: nil\n\n  def for_id(id) do\n    %MyApp.Scope{id: id}\n  end\nend\n```\n\nNext, we define a plug in our router that assigns a scope to each request:\n\n```diff\n   pipeline :browser do\n     plug :accepts, [\"html\"]\n     plug :fetch_session\n     plug :fetch_live_flash\n     plug :put_root_layout, html: {MyAppWeb.Layouts, :root}\n     plug :protect_from_forgery\n     plug :put_secure_browser_headers\n+    plug :assign_scope\n   end\n+\n+  defp assign_scope(conn, _opts) do\n+    if id = get_session(conn, :scope_id) do\n+      assign(conn, :current_scope, MyApp.Scope.for_id(id))\n+    else\n+      id = System.unique_integer()\n+\n+      conn\n+      |> put_session(:scope_id, id)\n+      |> assign(:current_scope, MyApp.Scope.for_id(id))\n+    end\n+  end\n```\n\nFor tests, we'll also define a fixture module `test/support/fixtures/scope_fixtures.ex`:\n\n```elixir\ndefmodule MyApp.ScopeFixtures do\n  alias MyApp.Scope\n\n  def session_scope_fixture(id \\\\ System.unique_integer()) do\n    %Scope{id: id}\n  end\nend\n```\n\nAnd then add a `setup` helper to our `test/support/conn_case.ex`:\n\n```elixir\ndefmodule MyAppWeb.ConnCase do\n  ...\n\n  def put_scope_in_session(%{conn: conn}) do\n    id = System.unique_integer()\n    scope = MyApp.ScopeFixtures.session_scope_fixture(id)\n\n    conn =\n      conn\n      |> Phoenix.ConnTest.init_test_session(%{})\n      |> Plug.Conn.put_session(:scope_id, id)\n\n    %{conn: conn, scope: scope}\n  end\nend\n```\n\nFinally, we configure the scope in our application's `config/config.exs`:\n\n```elixir\nconfig :my_app, :scopes,\n  session: [\n    default: true,\n    module: MyApp.Scope,\n    assign_key: :current_scope,\n    access_path: [:id],\n    schema_key: :session_id,\n    schema_type: :id,\n    schema_migration_type: :bigint,\n    schema_table: nil,\n    test_data_fixture: MyApp.ScopeFixtures,\n    test_setup_helper: :put_scope_in_session\n  ]\n```\n\nSetting `schema_table` to `nil` means that the generated resources don't have a foreign key to the scope, but instead a normal `bigint` column that directly stores the scope's id.\n\nWe can now generate a new resource, for example with `phx.gen.html`:\n\n```console\n$ mix phx.gen.html Post posts title:string\n```\n\nWhen you now visit [http://localhost:4000/posts](http://localhost:4000/posts), and create a new post, you will see that it is only visible to the current session. If you open a private browser window and visit the same URL, the previously created post is not visible. Similarly, if you create a new post in the private window, it is not visible in the other window. If you try to copy the URL of a post created in one session and access it in another, you will get an `Ecto.NoResultsError` error, which is automatically converted to 404 when the `debug_errors` setting is disabled.\n\n## Augmenting scopes\n\nLet's assume that you used `mix phx.gen.auth` to generate a scope tied to users. But now you also create a new `organization` entity, where users can be members of:\n\n```elixir\ndefmodule MyApp.Accounts.Organization do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  @derive {Phoenix.Param, key: :slug}\n  schema \"organizations\" do\n    field :name, :string\n    field :slug, :string\n    ...\n\n    many_to_many :users, MyApp.Accounts.User, join_through: \"organizations_users\"\n\n    timestamps(type: :utc_datetime)\n  end\nend\n```\n\nFirst, we'd adjust our scope struct to also include the organization:\n\n```diff\n defmodule MyApp.Accounts.Scope do\n   alias MyApp.Accounts.User\n   alias MyApp.Accounts.Organization\n\n-  defstruct user: nil\n+  defstruct user: nil, organization: nil\n\n   def for_user(%User{} = user) do\n     %__MODULE__{user: user}\n   end\n\n   def for_user(nil), do: nil\n+\n+  def put_organization(%__MODULE__{} = scope, %Organization{} = organization) do\n+    %{scope | organization: organization}\n+  end\n end\n```\n\nLet's also assume that the current organization is part of the URL path, like `http://localhost:4000/organizations/foo/posts`. Then, we'd adjust our router to fetch the organization from the path and assign it to the scope:\n\n```diff\n  # router.ex\n  pipeline :browser do\n    ...\n    plug :fetch_current_scope_for_user\n+   plug :assign_org_to_scope\n  end\n```\n\n```elixir\n# user_auth.ex\ndef assign_org_to_scope(conn, _opts) do\n  current_scope = conn.assigns.current_scope\n  if slug = conn.params[\"org\"] do\n    org = MyApp.Accounts.get_organization_by_slug!(current_scope, slug)\n    assign(conn, :current_scope, MyApp.Accounts.Scope.put_organization(current_scope, org))\n  else\n    conn\n  end\nend\n```\n\nFor LiveViews, we'll also need to add a new `:on_mount` hook and add it to `live_session`'s `on_mount` option in the router:\n\n```diff\n  # router.ex\n  scope \"/\", MyAppWeb do\n    pipe_through [:browser]\n\n    live_session :current_user,\n      on_mount: [\n        {MyAppWeb.UserAuth, :mount_current_scope},\n+       {MyAppWeb.UserAuth, :assign_org_to_scope}\n      ] do\n      ...\n    end\n  end\n```\n\n```elixir\n# user_auth.ex\ndef on_mount(:assign_org_to_scope, %{\"org\" => slug}, _session, socket) do\n  socket =\n    case socket.assigns.current_scope do\n      %{organization: nil} = scope ->\n        org = MyApp.Accounts.get_organization_by_slug!(socket.assigns.current_scope, slug)\n        Phoenix.Component.assign(socket, :current_scope, Scope.put_organization(scope, org))\n\n      _ ->\n        socket\n    end\n\n  {:cont, socket}\nend\n\ndef on_mount(:assign_org_to_scope, _params, _session, socket), do: {:cont, socket}\n```\n\nThis way, if a route is defined like `live /organizations/:org/posts`, the `assign_org_to_scope` plug would fetch the organization from the path and assign it to the scope. This code assumes that `get_organization_by_slug!/2` raises an `Ecto.NoResultsError` which would be automatically converted to `404`, but you could also handle the error explicitly and, for example, set an error flash and redirect to another page, like a dashboard. The `get_organization_by_slug!/2` function should also rely on the current scope to filter the organizations to those the user has access to.\n\nThen, we are ready to define a new scope in our application's `config/config.exs` to generate resources scoped to the organization:\n\n```elixir\nconfig :my_app, :scopes,\n  user: [\n    ...\n  ],\n  organization: [\n    module: MyApp.Accounts.Scope,\n    assign_key: :current_scope,\n    access_path: [:organization, :id],\n    route_prefix: \"/organizations/:org\",\n    route_access_path: [:organization, :slug],\n    schema_key: :org_id,\n    schema_type: :id,\n    schema_table: :organizations,\n    test_data_fixture: MyApp.AccountsFixtures,\n    test_setup_helper: :register_and_log_in_user_with_org\n  ]\n```\n\nFor the generated tests, we'll also need to define a fixture in `test/support/fixtures/accounts_fixtures.ex` and extend our `test/support/conn_case.ex`:\n\n```elixir\ndefmodule MyApp.AccountsFixtures do\n  ...\n\n  def organization_scope_fixture(scope \\\\ user_scope_fixture()) do\n    org = organization_fixture(scope)\n    Scope.put_organization(scope, org)\n  end\nend\n```\n\n```elixir\ndefmodule MyAppWeb.ConnCase do\n  ...\n\n  def register_and_log_in_user_with_org(context) do\n    %{conn: conn, user: _user, scope: scope} = register_and_log_in_user(context)\n    %{conn: conn, scope: MyApp.AccountsFixtures.organization_scope_fixture(scope)}\n  end\nend\n```\n\nNow that our scope configuration includes the `route_prefix`, we can generate resources scoped to the organization, and all paths will be automatically generated with the correct organization slug:\n\n```console\n$ mix phx.gen.live Blog Post posts title:string body:text --scope organization\n```\n\nThis shows that scopes are quite flexible, allowing you to keep a well-defined data structure, even when your application grows.\n\nMost of the time, your application will have a single scope module, like in this example. But sometimes, you might want to create a new scope module, for example to completely separate a user-facing scope from an admin scope, where also the context functions are supposed to only be called by one of the two.\n\n## Scope helpers\n\nWhen working with more complex scopes, it is often useful to create some helper functions, which can conveniently be added to the scope module:\n\n```elixir\ndefmodule MyApp.Accounts.Scope do\n  alias MyApp.Accounts\n  alias MyApp.Accounts.{User, Organization}\n\n  defstruct user: nil, organization: nil\n\n  def for_user(%User{} = user) do\n    %__MODULE__{user: user}\n  end\n\n  def for_user(nil), do: nil\n\n  def put_organization(%__MODULE__{} = scope, %Organization{} = organization) do\n    %{scope | organization: organization}\n  end\n\n  def for(opts) when is_list(opts) do\n    cond do\n      opts[:user] && opts[:org] ->\n        user = user(opts[:user])\n        org = org(opts[:org])\n\n        user\n        |> for_user()\n        |> put_organization(org)\n\n      opts[:user] ->\n        user = user(opts[:user])\n        for_user(user)\n\n      opts[:org] ->\n        %__MODULE__{organization: org(opts[:org])}\n    end\n  end\n\n  defp user(id) when is_integer(id) do\n    Accounts.get_user!(id)\n  end\n\n  defp user(email) when is_binary(email) do\n    Accounts.get_user_by_email(email)\n  end\n\n  defp org(id) when is_integer(id) do\n    Accounts.get_organization!(id)\n  end\n\n  defp org(slug) when is_binary(slug) do\n    Accounts.get_organization_by_slug!(slug)\n  end\nend\n```\n\nThen, you can alias the Scope module in your project's `.iex.exs`:\n\n```elixir\nalias MyApp.Accounts.Scope\n```\n\nAnd when working with scoped context functions, you can just do:\n\n```elixir\niex> MyApp.Blog.list_posts(Scope.for(user: 1, org: \"foo\"))\n...\niex> MyApp.Accounts.list_api_tokens(Scope.for(user: \"john@doe.com\"))\n...\n```\n"
  },
  {
    "path": "guides/cheatsheets/router.cheatmd",
    "content": "# Routing cheatsheet\n\n> Those need to be declared in the correct router module and scope.\n\nA quick reference to the common routing features' syntax. For an exhaustive overview, refer to the [routing guides](routing.md).\n\n## Routing declaration\n{: .col-2}\n\n### Single route\n\n```elixir\nget \"/users\", UserController, :index\npatch \"/users/:id\", UserController, :update\n```\n```elixir\n# Verified Routes\n~p\"/users\"\n~p\"/users/#{@user}\"\n```\nAlso accepts `put`, `patch`, `options`, `delete` and `head`.\n\n### Resources\n\n#### Simple\n\n```elixir\nresources \"/users\", UserController\n```\nGenerates `:index`, `:edit`, `:new`, `:show`, `:create`, `:update` and `:delete`.\n\n#### Options\n\n```elixir\nresources \"/users\", UserController, only: [:show]\nresources \"/users\", UserController, except: [:create, :delete]\n```\n\n#### Nested\n\n```elixir\nresources \"/users\", UserController do\n  resources \"/posts\", PostController\nend\n```\n```elixir\n# Verified Routes\n~p\"/users\"\n~p\"/users/new\"\n~p\"/users/#{@user}\"\n~p\"/users/#{@user}/edit\"\n~p\"/users/#{@user}/posts\"\n~p\"/users/#{@user}/posts/new\"\n~p\"/users/#{@user}/posts/#{@post}\"\n~p\"/users/#{@user}/posts/#{@post}/edit\"\n```\nFor more info check the [resources docs.](routing.html#resources)\n\n### Scopes\n\n#### Simple\n```elixir\nscope \"/admin\", HelloWeb.Admin do\n  pipe_through :browser\n\n  resources \"/users\", UserController\nend\n```\n```elixir\n# Verified Routes\n~p\"/admin/users\"\n~p\"/admin/users/new\"\n~p\"/admin/users/#{@user}\"\n~p\"/admin/users/#{@user}/edit\"\n```\n\n#### Nested\n```elixir\nscope \"/api\", HelloWeb.Api do\n  pipe_through :api\n\n  scope \"/v1\", V1 do\n    resources \"/users\", UserController\n  end\nend\n```\n```elixir\n# Verified Routes\n~p\"/api/v1/users\"\n~p\"/api/v1/users/new\"\n~p\"/api/v1/users/#{@user}\"\n~p\"/api/v1/users/#{@user}/edit\"\n```\nFor more info check the [scoped routes](routing.md#scoped-routes) docs.\n"
  },
  {
    "path": "guides/components.md",
    "content": "# Components and HEEx\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html).\n\nThe Phoenix endpoint pipeline takes a request, routes it to a controller, and calls a view module to render a template. The view interface from the controller is simple – the controller calls a view function with the connection's assigns, and the function's job is to return a HEEx template. We call any function that accepts an `assigns` parameter and returns a HEEx template a *function component*.\n\n> The Phoenix framework is designed for HTML applications, JSON APIs, GraphQL endpoints, etc. For this reason, all of the functionality related to HTML rendering comes as part of two separate packages:\n>\n> * [`phoenix_html`](https://hexdocs.pm/phoenix_html) - defines the building blocks for writing HTML safely. In your project, you'll interact primarily with the `Phoenix.HTML` module, which is imported by default in all templates\n>\n> * [`phoenix_live_view`](https://hexdocs.pm/phoenix_live_view) - a library for rich, real-time user experiences with server-rendered HTML. While LiveView provides several abstraction for building collaborative and dynamic applications, it also defines the `HEEx` template language, function components, and JS commands, which brings powerful abstractions for all kinds of server-rendered HTML applications\n\nIn this chapter, we will recap how components are used and dig deeper to discover new use cases.\n\n## Function components\n\nFunction components are the essential building block for any kind of markup-based template rendering you'll perform in Phoenix. They serve as a shared abstraction for the standard MVC controller-based applications, LiveView applications, layouts, and smaller UI definitions you'll use throughout other templates. Their documentation is available in [the `Phoenix.Component` module](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html).\n\nAt the end of the Request life-cycle chapter, we created a template at `lib/hello_web/controllers/hello_html/show.html.heex`, let's open it up:\n\n```heex\n<Layouts.app flash={@flash}>\n  <section>\n    <h2>Hello World, from {@messenger}!</h2>\n  </section>\n</Layouts.app>\n```\n\n`<Layouts.app>` is a function component defined inside `lib/hello_web/components/layouts.ex`. If you open the file up, you will find:\n\n```elixir\n  def app(assigns) do\n    ~H\"\"\"\n    <header class=\"navbar px-4 sm:px-6 lg:px-8\">\n    ...\n```\n\nSo far, all of the function components we have defined also worked as templates. Let's learn more about them by defining our own component with the intent of encapsulating some HTML markup.\n\nImagine we want to refactor our `show.html.heex` to move the rendering of `<h2>Hello World, from {@messenger}!</h2>` to its own function. Remember that `show.html.heex` is embedded within the `HelloHTML` module. Let's open it up:\n\n```elixir\ndefmodule HelloWeb.HelloHTML do\n  use HelloWeb, :html\n\n  embed_templates \"hello_html/*\"\nend\n```\n\nThat's simple enough. There's only two lines, `use HelloWeb, :html`. This line calls the `html/0` function defined in `HelloWeb` which sets up the basic imports and configuration for our function components and templates. All of the imports and aliases in our module will also be available in our templates. Similarly, if we want to write a function component to be invoked from `show.html.heex`, we can simply add it to `HelloHTML`. Let's do so:\n\n```elixir\ndefmodule HelloWeb.HelloHTML do\n  use HelloWeb, :html\n\n  embed_templates \"hello_html/*\"\n\n  attr :messenger, :string, required: true\n\n  def greet(assigns) do\n    ~H\"\"\"\n    <h2>Hello World, from {@messenger}!</h2>\n    \"\"\"\n  end\nend\n```\n\nWe declared the attributes we accept via the [`Phoenix.Component.attr/3`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#attr/3) macro, then we defined our `greet/1` function which returns the HEEx template.\n\nNext we need to update `show.html.heex`:\n\n```heex\n<Layouts.app flash={@flash}>\n  <section>\n    <.greet messenger={@messenger} />\n  </section>\n</Layouts.app>\n```\n\nWhen we reload `http://localhost:4000/hello/Frank`, we should see the same content as before! The `show.html.heex` is now invoking two different function components:\n\n  * `<Layouts.app` - the syntax for invoking function componente defined in a separate module and it follows the same rules as calling any other function in Elixir\n\n  * `<.greet` - when the function component is defined in the same module as the template, we can skip the module name, and invoke the component using its name prefixed with a dot\n\nSince the `show.html.heex` template is embedded within the `HelloHTML` module, we were able to invoke the function component directly as `<.greet messenger=\"...\" />`. If the component was defined elsewhere, we would need to give its full name, such as: `<HelloWeb.HelloHTML.greet messenger=\"...\" />`.\n\nBy declaring attributes as required, Phoenix will warn if we call the `<.greet />` component without passing attributes. If an attribute is optional, you can specify the `:default` option with a value:\n\n```\nattr :messenger, :string, default: nil\n```\n\nOverall, function components are the essential building block of Phoenix rendering stack. Next, let's fully understand the expressive power behind the HEEx template language.\n\n## HEEx\n\nFunction components and templates files are powered by [the HEEx template language](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2), which stands for \"HTML + Embedded Elixir\". We can write Elixir code inside `{...}` for HTML-aware interpolation inside tag attributes and the body, as done above. For example, we use `@name` to access the key `name` defined inside `assigns`.\n\nWe can also interpolate arbitrary HEEx blocks using `<%= ... %>`. This is often used for block constructs. For example, in order to have conditionals:\n\n```heex\n<%= if some_condition? do %>\n  <p>Some condition is true for user: {@messenger}</p>\n<% else %>\n  <p>Some condition is false for user: {@messenger}</p>\n<% end %>\n```\n\nor even loops:\n\n```heex\n<table>\n  <tr>\n    <th>Number</th>\n    <th>Power</th>\n  </tr>\n  <%= for number <- 1..10 do %>\n    <tr>\n      <td>{number}</td>\n      <td>{number * number}</td>\n    </tr>\n  <% end %>\n</table>\n```\n\nHEEx also comes with handy HTML extensions we will learn next.\n\n### HTML extensions\n\nBesides allowing interpolation of Elixir expressions, `.heex` templates come with HTML-aware extensions. For example, let's see what happens if you try to interpolate a value with \"<\" or \">\" in it, which would lead to HTML injection:\n\n```heex\n{\"<b>Bold?</b>\"}\n```\n\nOnce you render the template, you will see the literal `<b>` on the page. This means users cannot inject HTML content on the page. If you want to allow them to do so, you can call `raw`, but do so with extreme care:\n\n```heex\n{raw(\"<b>Bold?</b>\")}\n```\n\nAnother super power of HEEx templates is validation of HTML and interpolation syntax of attributes. You can write:\n\n```heex\n<div title=\"My div\" class={@class}>\n  <p>Hello {@username}</p>\n</div>\n```\n\nNotice how you could simply use `key={value}`. HEEx will automatically handle special values such as `false` to remove the attribute or a list of classes.\n\nTo interpolate a dynamic number of attributes in a keyword list or map, do:\n\n```heex\n<div title=\"My div\" {@many_attributes}>\n  <p>Hello {@username}</p>\n</div>\n```\n\nAlso, try removing the closing `</div>` or renaming it to `</div-typo>`. HEEx templates will let you know about your error.\n\nHEEx also supports shorthand syntax for `if` and `for` expressions via the special `:if` and `:for` attributes. For example, rather than this:\n\n```heex\n<%= if @some_condition do %>\n  <div>...</div>\n<% end %>\n```\n\nYou can write:\n\n```heex\n<div :if={@some_condition}>...</div>\n```\n\nLikewise, for comprehensions may be written as:\n\n```heex\n<ul>\n  <li :for={item <- @items}>{item.name}</li>\n</ul>\n```\n\n## CoreComponents\n\nIn a new Phoenix application, you will also find a `core_components.ex` module inside the `components` folder. This module is a great example of defining function components to be reused throughout our application. This guarantees that, as our application evolves, our components will look consistent.\n\nIf you look inside `def html` in `HelloWeb` placed at `lib/hello_web.ex`, you will see that `CoreComponents` are automatically imported into all HTML views via `use HelloWeb, :html`. This is also the reason why `CoreComponents` itself performs `use Phoenix.Component` instead of `use HelloWeb, :html` at the top: doing the latter would cause a deadlock as we would try to import `CoreComponents` into itself.\n\nCoreComponents also play an important role in Phoenix code generators, as the code generators assume those components are available in order to quickly scaffold your application. In case you want to learn more about all of these pieces, you may:\n\n  * Explore the generated `CoreComponents` module to learn more from practical examples\n\n  * Read the official documentation for [`Phoenix.Component`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html)\n\n  * Read the official documentation for [HEEx and the ~H sigils](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2)\n\n  * If you are looking for higher level components beyond the minimal ones included by Phoenix, [the LiveView project keeps a list of component systems](https://github.com/phoenixframework/phoenix_live_view#component-systems)\n\n## Layouts\n\nWhen talking about components and rendering in Phoenix, it is important to understand the concept of layouts.\n\nAll Phoenix applications have one component called the \"root layout\". This page is where you will find the `<head>` and `<body>` tags of your HTML page. The root layout is configured in your `lib/hello_web/router.ex` file:\n\n```elixir\n  plug :put_root_layout, html: {HelloWeb.Layouts, :root}\n```\n\nIn a newly generated app, the template itself can be found at `lib/hello_web/components/layouts/root.html.heex`. Open it up and, just about at the end of the `<body>`, you will see this:\n\n```heex\n{@inner_content}\n```\n\nThat's where our templates are injected once they are rendered. The root layout is reused by controllers and live views alike.\n\nAny dynamic functionality of your application is then implemented as function components. For example, your application menu and sidebar is typically part of the `app` component in `lib/hello_web/components/layouts.ex`, which is invoked in every template:\n\n```heex\n<Layouts.app flash={@flash}>\n  ...\n</Layouts.app>\n```\n\nThis mechanism is also very flexible. For example, if you want to create an admin layout, you can simply add a new function in the `Layouts` module, and then invoke `Layouts.admin` instead of `Layouts.app`:\n\n```heex\n<Layouts.admin flash={@flash}>\n  ...\n</Layouts.admin>\n```\n\n> Previous Phoenix versions used a nested layout mechanism, by passing the `:layouts` to `Phoenix.Controller` and `:layout` to `Phoenix.LiveView`, but this mechanism is discouraged in new Phoenix applications.\n"
  },
  {
    "path": "guides/controllers.md",
    "content": "# Controllers\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html).\n\nPhoenix controllers act as intermediary modules. Their functions — called actions — are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response.\n\nPhoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide, we might find what we're looking for in Plug itself. Please see the [Plug guide](plug.html) or the [Plug documentation](`Plug`) for more information.\n\nA newly generated Phoenix app will have a single controller named `PageController`, which can be found at `lib/hello_web/controllers/page_controller.ex` which looks like this:\n\n```elixir\ndefmodule HelloWeb.PageController do\n  use HelloWeb, :controller\n\n  def home(conn, _params) do\n    render(conn, :home)\n  end\nend\n```\n\nThe first line below the module definition invokes the `__using__/1` macro of the `HelloWeb` module, which imports some useful modules.\n\n`PageController` gives us the `home` action to display the Phoenix [welcome page] associated with the default route Phoenix defines in the router.\n\n## Actions\n\nController actions are just functions. We can name them anything we like as long as they follow Elixir's naming rules. The only requirement we must fulfill is that the action name matches a route defined in the router.\n\nFor example, in `lib/hello_web/router.ex` we could change the action name in the default route that Phoenix gives us in a new app from `home`:\n\n```elixir\nget \"/\", PageController, :home\n```\n\nto `index`:\n\n```elixir\nget \"/\", PageController, :index\n```\n\nas long as we change the action name in `PageController` to `index` as well, the [welcome page] will load as before.\n\n```elixir\ndefmodule HelloWeb.PageController do\n  ...\n\n  def index(conn, _params) do\n    render(conn, :home)\n  end\nend\n```\n\nWhile we can name our actions whatever we like, there are conventions for action names which we should follow whenever possible. We went over these in the [routing guide](routing.html), but we'll take another quick look here.\n\n- index   - renders a list of all items of the given resource type\n- show    - renders an individual item by ID\n- new     - renders a form for creating a new item\n- create  - receives parameters for one new item and saves it in a data store\n- edit    - retrieves an individual item by ID and displays it in a form for editing\n- update  - receives parameters for one edited item and saves the item to a data store\n- delete  - receives an ID for an item to be deleted and deletes it from a data store\n\nEach of these actions takes two parameters, which will be provided by Phoenix behind the scenes.\n\nThe first parameter is always `conn`, a struct which holds information about the request such as the host, path elements, port, query string, and much more. `conn` comes to Phoenix via Elixir's Plug middleware framework. More detailed information about `conn` can be found in the [Plug.Conn documentation](`Plug.Conn`).\n\nThe second parameter is `params`. Not surprisingly, this is a map which holds any parameters passed along in the HTTP request. It is a good practice to pattern match against parameters in the function signature to provide data in a simple package we can pass on to rendering. We saw this in the [request life-cycle guide](request_lifecycle.html) when we added a messenger parameter to our `show` route in `lib/hello_web/controllers/hello_controller.ex`.\n\n```elixir\ndefmodule HelloWeb.HelloController do\n  ...\n\n  def show(conn, %{\"messenger\" => messenger}) do\n    render(conn, :show, messenger: messenger)\n  end\nend\n```\n\nIn some cases — often in `index` actions, for instance — we don't care about parameters because our behavior doesn't depend on them. In those cases, we don't use the incoming parameters, and simply prefix the variable name with an underscore, calling it `_params`. This will keep the compiler from complaining about the unused variable while still keeping the correct arity.\n\n## Rendering\n\nControllers can render content in several ways. The simplest is to render some plain text using the [`text/2`] function which Phoenix provides.\n\nFor example, let's rewrite the `show` action from `HelloController` to return text instead. For that, we could do the following.\n\n```elixir\ndef show(conn, %{\"messenger\" => messenger}) do\n  text(conn, \"From messenger #{messenger}\")\nend\n```\n\nNow [`/hello/Frank`] in your browser should display `From messenger Frank` as plain text without any HTML.\n\nA step beyond this is rendering pure JSON with the [`json/2`] function. We need to pass it something that the [Jason library](`Jason`) can decode into JSON, such as a map. (Jason is one of Phoenix's dependencies.)\n\n```elixir\ndef show(conn, %{\"messenger\" => messenger}) do\n  json(conn, %{id: messenger})\nend\n```\n\nIf we again visit [`/hello/Frank`] in the browser, we should see a block of JSON with the key `id` mapped to the string `\"Frank\"`.\n\n```json\n{\"id\": \"Frank\"}\n```\n\nThe [`json/2`] function is useful for writing APIs and there is also the [`html/2`] function for rendering HTML, but most of the times we use Phoenix views to build our responses. For this, Phoenix includes the [`render/3`] function. It is specially important for HTML responses, as Phoenix Views provide performance and security benefits.\n\nLet's rollback our `show` action to what we originally wrote in the [request life-cycle guide](request_lifecycle.html):\n\n```elixir\ndefmodule HelloWeb.HelloController do\n  use HelloWeb, :controller\n\n  def show(conn, %{\"messenger\" => messenger}) do\n    render(conn, :show, messenger: messenger)\n  end\nend\n```\n\nIn order for the [`render/3`] function to work correctly, the controller and view must share the same root name (in this case `Hello`), and the `HelloHTML` module must include an `embed_templates` definition specifying where its templates live. By default the controller, view module, and templates are collocated together in the same controller directory. In other words, `HelloController` requires `HelloHTML`, and `HelloHTML` requires the existence of the `lib/hello_web/controllers/hello_html/` directory, which must contain the `show.html.heex` template.\n\n[`render/3`] will also pass the value which the `show` action received for `messenger` from the parameters as an assign.\n\nIf we need to pass values into the template when using `render`, that's easy. We can pass a keyword like we've seen with `messenger: messenger`, or we can use `Plug.Conn.assign/3`, which conveniently returns `conn`.\n\n```elixir\n  def show(conn, %{\"messenger\" => messenger}) do\n    conn\n    |> Plug.Conn.assign(:messenger, messenger)\n    |> render(:show)\n  end\n```\n\nNote: Using `Phoenix.Controller` imports `Plug.Conn`, so shortening the call to [`assign/3`] works just fine.\n\nPassing more than one value to our template is as simple as connecting [`assign/3`] functions together:\n\n```elixir\n  def show(conn, %{\"messenger\" => messenger}) do\n    conn\n    |> assign(:messenger, messenger)\n    |> assign(:receiver, \"Dweezil\")\n    |> render(:show)\n  end\n```\n\nOr you can pass the assigns directly to `render` instead:\n\n```elixir\n  def show(conn, %{\"messenger\" => messenger}) do\n    render(conn, :show, messenger: messenger, receiver: \"Dweezil\")\n  end\n```\n\nGenerally speaking, once all assigns are configured, we invoke the view layer. The view layer (`HelloWeb.HelloHTML`) then renders `show.html` alongside the layout and a response is sent back to the browser.\n\n## New rendering formats\n\nRendering HTML through a template is fine, but what if we need to change the rendering format on the fly? Let's say that sometimes we need HTML, sometimes we need plain text, and sometimes we need JSON. Then what?\n\nThe view's job is not only to render HTML templates. Views are about data presentation. Given a bag of data, the view's purpose is to present that in a meaningful way given some format, be it HTML, JSON, CSV, or others. Many web apps today return JSON to remote clients, and Phoenix views are *great* for JSON rendering.\n\nAs an example, let's take `PageController`'s `home` action from a newly generated app. Out of the box, this has the right view `PageHTML`, the embedded templates from (`lib/hello_web/controllers/page_html`), and the right template for rendering HTML (`home.html.heex`.)\n\n```elixir\ndef home(conn, _params) do\n  render(conn, :home)\nend\n```\n\nWhat it doesn't have is a view for rendering JSON. Phoenix Controller hands off to a view module to render templates, and it does so per format. We already have a view for the HTML format, but we need to instruct Phoenix how to render the JSON format as well. By default, you can see which formats your controllers support in `lib/hello_web.ex`:\n\n```elixir\n  def controller do\n    quote do\n      use Phoenix.Controller,\n        formats: [:html, :json]\n      ...\n    end\n  end\n```\n\nSo out of the box Phoenix will look for a `HTML` and `JSON` view modules based on the request format and the controller name. We can also explicitly tell Phoenix in our controller which view(s) to use for each format. For example, what Phoenix does by default can be explicitly set with the following in your controller:\n\n```elixir\nplug :put_view, html: HelloWeb.PageHTML, json: HelloWeb.PageJSON\n```\n\nLet's add a `PageJSON` view module at `lib/hello_web/controllers/page_json.ex`:\n\n```elixir\ndefmodule HelloWeb.PageJSON do\n  def home(_assigns) do\n    %{message: \"this is some JSON\"}\n  end\nend\n```\n\nSince the Phoenix View layer is simply a function that the controller renders, passing connection assigns, we can define a regular `home/1` function and return a map to be serialized as JSON.\n\nThere are just a few more things we need to do to make this work. Because we want to render both HTML and JSON from the same controller, we need to tell our router that it should accept the `json` format. We do that by adding `json` to the list of accepted formats in the `:browser` pipeline. Let's open up `lib/hello_web/router.ex` and change `plug :accepts` to include `json` as well as `html` like this.\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\", \"json\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.LayoutView, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end\n...\n```\n\nPhoenix allows us to change formats on the fly with the `_format` query string parameter. If we go to [`http://localhost:4000/?_format=json`](http://localhost:4000/?_format=json), we will see `%{\"message\": \"this is some JSON\"}`.\n\nIn practice, however, applications that need to render both formats typically use two distinct pipelines for each, such as the `pipeline :api` already defined in your router file. To learn more, see [our JSON and APIs guide](json_and_apis.md).\n\n### Sending responses directly\n\nIf none of the rendering options above quite fits our needs, we can compose our own using some of the functions that `Plug` gives us. Let's say we want to send a response with a status of \"201\" and no body whatsoever. We can do that with the `Plug.Conn.send_resp/3` function.\n\nEdit the `home` action of `PageController` in `lib/hello_web/controllers/page_controller.ex` to look like this:\n\n```elixir\ndef home(conn, _params) do\n  send_resp(conn, 201, \"\")\nend\n```\n\nReloading [http://localhost:4000](http://localhost:4000) should show us a completely blank page. The network tab of our browser's developer tools should show a response status of \"201\" (Created). Some browsers (Safari) will download the response, as the content type is not set.\n\nTo be specific about the content type, we can use [`put_resp_content_type/2`] in conjunction with [`send_resp/3`].\n\n```elixir\ndef home(conn, _params) do\n  conn\n  |> put_resp_content_type(\"text/plain\")\n  |> send_resp(201, \"\")\nend\n```\n\nUsing `Plug` functions in this way, we can craft just the response we need.\n\n### Setting the content type\n\nAnalogous to the `_format` query string param, we can render any sort of format we want by modifying the HTTP Content-Type Header and providing the appropriate template.\n\nIf we wanted to render an XML version of our `home` action, we might implement the action like this in `lib/hello_web/controllers/page_controller.ex`.\n\n```elixir\ndef home(conn, _params) do\n  conn\n  |> put_resp_content_type(\"text/xml\")\n  |> put_format(:xml)\n  |> render(:home, content: some_xml_content)\nend\n```\n\nThen we would need to provide a `home.xml.eex` template that creates XML and a `PageXML` view that embeds the template, and that would be it.\n\nNote: The `home.xml.eex` template uses the `.eex` extension. `.eex` templates are rendered by [`EEx`](https://hexdocs.pm/eex/main/EEx.html).\n\nFor a list of valid content mime-types, please see the `MIME` library.\n\n### Setting the HTTP Status\n\nWe can also set the HTTP status code of a response similarly to the way we set the content type. The `Plug.Conn` module, imported into all controllers, has a `put_status/2` function to do this.\n\n`Plug.Conn.put_status/2` takes `conn` as the first parameter and as the second parameter either an integer or a \"friendly name\" used as an atom for the status code we want to set. The list of status code atom representations can be found in `Plug.Conn.Status.code/1` documentation.\n\nLet's change the status in our `PageController` `home` action.\n\n```elixir\ndef home(conn, _params) do\n  conn\n  |> put_status(202)\n  |> render(:home)\nend\n```\n\nThe status code we provide must be a valid number.\n\n## Redirection\n\nOften, we need to redirect to a new URL in the middle of a request. A successful `create` action, for instance, will usually redirect to the `show` action for the resource we just created. Alternately, it could redirect to the `index` action to show all the things of that same type. There are plenty of other cases where redirection is useful as well.\n\nWhatever the circumstance, Phoenix controllers provide the handy [`redirect/2`] function to make redirection easy. Phoenix differentiates between redirecting to a path within the application and redirecting to a URL — either within our application or external to it.\n\nIn order to try out [`redirect/2`], let's create a new route in `lib/hello_web/router.ex`.\n\n```elixir\ndefmodule HelloWeb.Router do\n  ...\n\n  scope \"/\", HelloWeb do\n    ...\n    get \"/\", PageController, :home\n    get \"/redirect_test\", PageController, :redirect_test\n    ...\n  end\nend\n```\n\nThen we'll change `PageController`'s `home` action of our controller to do nothing but to redirect to our new route.\n\n```elixir\ndefmodule HelloWeb.PageController do\n  use HelloWeb, :controller\n\n  def home(conn, _params) do\n    redirect(conn, to: ~p\"/redirect_test\")\n  end\nend\n\n```\n\nWe made use of `Phoenix.VerifiedRoutes.sigil_p/2` to build our redirect path, which is the preferred approach to reference any path within our application. We learned about verified routes in the [routing guide](routing.html).\n\nFinally, let's define in the same file the action we redirect to, which simply renders the home, but now under a new address:\n\n```elixir\ndef redirect_test(conn, _params) do\n  render(conn, :home)\nend\n```\n\nWhen we reload our [welcome page], we see that we've been redirected to `/redirect_test` which shows the original welcome page. It works!\n\nIf we care to, we can open up our developer tools, click on the network tab, and visit our root route again. We see two main requests for this page - a get to `/` with a status of `302`, and a get to `/redirect_test` with a status of `200`.\n\nNotice that the redirect function takes `conn` as well as a string representing a relative path within our application. For security reasons, the `:to` option can only redirect to paths within your application. If you want to redirect to a fully-qualified path or an external URL, you should use `:external` instead:\n\n```elixir\ndef home(conn, _params) do\n  redirect(conn, external: \"https://elixir-lang.org/\")\nend\n```\n\n## Flash messages\n\nSometimes we need to communicate with users during the course of an action. Maybe there was an error updating a schema, or maybe we just want to welcome them back to the application. For this, we have flash messages.\n\nThe `Phoenix.Controller` module provides the [`put_flash/3`] to set flash messages as a key-value pair and placing them into a `@flash` assign in the connection. Let's set two flash messages in our `HelloWeb.PageController` to try this out.\n\nTo do this we modify the `home` action as follows:\n\n```elixir\ndefmodule HelloWeb.PageController do\n  ...\n  def home(conn, _params) do\n    conn\n    |> put_flash(:error, \"Let's pretend we have an error.\")\n    |> render(:home)\n  end\nend\n```\n\nIn order to see our flash messages, we need to be able to retrieve them and display them in a template layout. We can do that using [`Phoenix.Flash.get/2`] which takes the flash data and the key we care about. It then returns the value for that key.\n\nFor our convenience, a `flash_group` component is already available and added to the beginning of our [welcome page]\n\n```heex\n<.flash_group flash={@flash} />\n```\n\nWhen we reload the [welcome page], our message should appear in the top right corner of the page.\n\nThe flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we reuse the redirect action from the previous section, we can do:\n\n```elixir\n  def home(conn, _params) do\n    conn\n    |> put_flash(:error, \"Let's pretend we have an error.\")\n    |> redirect(to: ~p\"/redirect_test\")\n  end\n```\n\nNow if you reload the [welcome page], you will be redirected and the flash message will be shown once more.\n\nBesides [`put_flash/3`], the `Phoenix.Controller` module has another useful function worth knowing about. [`clear_flash/1`] takes only `conn` and removes any flash messages which might be stored in the session.\n\nPhoenix does not enforce which keys are stored in the flash. As long as we are internally consistent, all will be well. `:info` and `:error`, however, are common and are handled by default in our templates.\n\n## Error pages\n\nPhoenix has two views called `ErrorHTML` and `ErrorJSON` which live in `lib/hello_web/controllers/`. The purpose of these views is to handle errors in a general way for incoming HTML or JSON requests. Similar to the views we built in this guide, error views can return both HTML and JSON responses. See the [Custom Error Pages How-To](custom_error_pages.html) for more information.\n\n[`render/4`]: `Phoenix.Template.render/4`\n[`/hello/Frank`]:  http://localhost:4000/hello/Frank\n[`assign/3`]: `Plug.Conn.assign/3`\n[`clear_flash/1`]: `Phoenix.Controller.clear_flash/1`\n[`Phoenix.Flash.get/2`]: `Phoenix.Flash.get/2`\n[`html/2`]: `Phoenix.Controller.html/2`\n[`json/2`]: `Phoenix.Controller.json/2`\n[`put_flash/3`]: `Phoenix.Controller.put_flash/3`\n[`put_resp_content_type/2`]: `Plug.Conn.put_resp_content_type/2`\n[`put_root_layout/2`]: `Phoenix.Controller.put_root_layout/2`\n[`redirect/2`]: `Phoenix.Controller.redirect/2`\n[`render/3`]: `Phoenix.Controller.render/3`\n[`send_resp/3`]: `Plug.Conn.send_resp/3`\n[`text/2`]: `Phoenix.Controller.text/2`\n[welcome page]: http://localhost:4000/\n"
  },
  {
    "path": "guides/data_modelling/contexts.md",
    "content": "# 1. Intro to Contexts\n\nPhoenix guides are broken into several major sections. The main building blocks are outlined under the \"Core Concepts\" section, where we explored [the request life-cycle](request_lifecycle.html), wired up controller actions through our routers, and learned how Ecto allows data to be validated and persisted. Now it's time to tie it all together by writing web-facing features that interact with our greater Elixir application.\n\nWhen building a Phoenix project, we are first and foremost building an Elixir application. Phoenix's job is to provide a web interface into our Elixir application. Naturally, we compose our applications with modules and functions, but we often assign specific responsibilities to certain modules and give them names: such as controllers, routers, and live views.\n\nHowever, the most important part of your web application is often where we encapsulate data access and data validation. We call these modules **contexts**. They often talk to a database, using `Ecto`, or APIs, using an HTTP client such as `Req`. By giving these modules a name, we help developers identify these patterns and talk about them. At the end of the day, contexts are just modules, as are your controllers, views, etc.\n\nIf you have used `mix phx.gen.html`, `mix phx.gen.json`, or `mix phx.gen.live`, you have already used contexts. For example, run the following generator in a Phoenix application:\n\n```console\n$ mix phx.gen.live Post posts title body:text\n```\n\nThe command above will output a few files, among them, a `MyApp.Posts.Post` schema in `lib/my_app/posts/post.ex`, which outlines how the resource is represented in the database, and a **context** module named `MyApp.Posts` that encapsulates all the database access to said schema. The `MyApp.Posts` module centralizes all functionality related to posts, instead of scattering logic around controllers, LiveViews, etc.\n\nContexts are also useful to nest resources. For example, if you are adding comments to your posts, you can colocate their schemas, since comments belong to posts, like this:\n\n```console\n$ mix phx.gen.live Posts Comment comments post_id:references:posts body:text\n```\n\nThe first argument to the generator above is the context module, instructing Phoenix to colocate the comments functionality with posts. There is also a `post_id` attribute which specifies a foreign key reference to the posts table. As your application grows, contexts help you group related schemas, instead of having several dozens of schemas with no insights on how they relate to each other.\n\nDevelopers may also use contexts to intentionally name parts of their application. For example, `mix phx.gen.auth` requires a context name to be explicitly given. It is often invoked as:\n\n```console\n$ mix phx.gen.auth Accounts User users\n```\n\nor, using whatever name you prefer, such as:\n\n```console\n$ mix phx.gen.auth Identity Client clients\n```\n\nThe generated `Accounts` (or `Identity`) context will encapsulate all functionality for managing users (or clients) and their tokens. You could, if you wanted, name the context `Users` too, but given account/identity management has well defined name and boundary in most applications, giving it an explicit name makes its purposes clear. And, at the end of the day, they are just plain modules.\n\nIn this guide, we will use these ideas to build out our web application. Our goal is to build an ecommerce system where we can showcase products, allow users to add products to their cart, and complete their orders. We will do so by intentionally designing and naming our contexts. Opposite to other Phoenix guides, **these guides are meant to be read in order**.\n\n## Our ecommerce application\n\nLet's start an application from scratch to build our ecommerce, using Phoenix Express. We will call the application `hello`.\n\nFor macOS/Ubuntu:\n\n```bash\n$ curl https://new.phoenixframework.org/hello | sh\n```\n\nFor Windows PowerShell:\n\n```bash\ncurl.exe -fsSO https://new.phoenixframework.org/hello.bat; .\\hello.bat\n```\n\nIf those commands do not work, see the [Installation Guide](installation.html) and then run `mix phx.new`:\n\n```console\n$ mix phx.new hello\n```\n\nFollow any of the steps printed on the screen and open up the generated `hello` project in your editor.\n\nWe are ready to move to the next chapter.\n"
  },
  {
    "path": "guides/data_modelling/cross_context_boundaries.md",
    "content": "# 4. Cross-context Boundaries\n\nNow that we have the beginnings of our product catalog features, let's begin to work on the other main features of our application – carting products from the catalog. In order to properly track products that have been added to a user's cart, we'll need a new place to persist this information, along with point-in-time product information like the price at time of carting. This is necessary so we can detect product price changes in the future. We know what we need to build, but now we need to decide where the cart functionality lives in our application.\n\nIf we take a step back and think about the isolation of our application, the exhibition of products in our catalog distinctly differs from the responsibilities of managing a user's cart. A product catalog shouldn't care about the rules of our shopping cart system, and vice-versa. There's a clear need here for a separate context to handle the new cart responsibilities. Let's call it `ShoppingCart`.\n\nLet's create a `ShoppingCart` context to handle basic cart duties. Before we write code, let's imagine we have the following feature requirements:\n\n  1. Add products to a user's cart from the product show page\n  2. Store point-in-time product price information at time of carting\n  3. Store and update quantities in cart\n  4. Calculate and display sum of cart prices\n\nFrom the description, it's clear we need a `Cart` resource for storing the user's cart, along with a `CartItem` to track products in the cart. With our plan set, let's get to work.\n\n## Adding authentication\n\nMost of the cart functionality is tied to a specific user. Therefore, in order to allow each user to manage their own cart (and only their own carts), we must be able to authenticate users. To do so, we will use Phoenix's built-in `mix phx.gen.auth` generator to scaffold a solution for us:\n\n```console\nmix phx.gen.auth Accounts User users\n```\n\nYou will see output similar to the following: \n\n```console\nAn authentication system can be created in two different ways:\n- Using Phoenix.LiveView (default)\n- Using Phoenix.Controller only\nDo you want to create a LiveView based authentication system? [Yn]\n```\n\nType `n` followed by `Return` key,\nyou will see output similar to:\n\n```console\n...\n* creating lib/hello/accounts/scope.ex\n...\n* injecting config/config.exs\n...\n\nPlease re-fetch your dependencies with the following command:\n\n    $ mix deps.get\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n\nOnce you are ready, visit \"/users/register\"\nto create your account and then access \"/dev/mailbox\" to\nsee the account confirmation email.\n```\n\nAfter following the instructions to re-fetch dependencies and migrating the database, we can start the server with `mix phx.server` and re-visit the home page [`http://localhost:4000/`](http://localhost:4000/). There, we should see new registration and login links at the top of the page. On the registration page, create a new user. In development, a confirmation email is sent to the dev mailbox, which is accessible at [`http://localhost:4000/dev/mailbox`](http://localhost:4000/dev/mailbox). After clicking the confirmation link, you should be successfully logged in.\n\nOne of the benefits of `mix phx.gen.auth` is that it also generates a scope file at `lib/hello/accounts/scope.ex`. In a nutshell, authentication tells us who a user is based on their email address, but it doesn't tell us the resources the user owns or has access to. In order to do so, we need authorization. Scopes help us tie generated resources, such as the Cart we will create, to users. Let's open up the file:\n\n```elixir\ndefmodule Hello.Accounts.Scope do\n  ...\n  alias Hello.Accounts.User\n\n  defstruct user: nil\n\n  @doc \"\"\"\n  Creates a scope for the given user.\n\n  Returns nil if no user is given.\n  \"\"\"\n  def for_user(%User{} = user) do\n    %__MODULE__{user: user}\n  end\n\n  def for_user(nil), do: nil\nend\n```\n\nWe can see that it is simply a struct with a `user` field. The authentication system ensures that the `current_scope` assign is accordingly set with the current user. Let's see it in practice.\n\n## Generating scoped resources\n\nLet's generate our new context:\n\n```console\n$ mix phx.gen.context ShoppingCart Cart carts\n\n* creating lib/hello/shopping_cart/cart.ex\n* creating priv/repo/migrations/20250205203128_create_carts.exs\n* creating lib/hello/shopping_cart.ex\n* injecting lib/hello/shopping_cart.ex\n* creating test/hello/shopping_cart_test.exs\n* injecting test/hello/shopping_cart_test.exs\n* creating test/support/fixtures/shopping_cart_fixtures.ex\n* injecting test/support/fixtures/shopping_cart_fixtures.ex\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nWe generated our new context `ShoppingCart`, with a new `ShoppingCart.Cart` schema. Open up the generated schema and migration files and you will see it has automatically included a `user_id` field, thanks to the scope. Furthermore, when we explore the code later on, we will learn all queries to the carts table have been properly scoped.\n\nWith our cart in place, let's generate our cart items. This time we will pass the `--no-scope` flag, because we will associate `cart_items` to `carts` and the `carts` are already scoped to the user:\n\n```console\n$ mix phx.gen.context ShoppingCart CartItem cart_items \\\ncart_id:references:carts product_id:references:products \\\nprice_when_carted:decimal quantity:integer --no-scope\n\nYou are generating into an existing context.\n...\nWould you like to proceed? [Yn] y\n* creating lib/hello/shopping_cart/cart_item.ex\n* creating priv/repo/migrations/20250205213410_create_cart_items.exs\n* injecting lib/hello/shopping_cart.ex\n* injecting test/hello/shopping_cart_test.exs\n* injecting test/support/fixtures/shopping_cart_fixtures.ex\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nWe generated a new resource inside our `ShoppingCart` named `CartItem`. This schema and table will hold references to a cart and product, along with the price at the time we added the item to our cart, and the quantity the user wishes to purchase. Let's touch up the generated migration file in `priv/repo/migrations/*_create_cart_items.ex`:\n\n```diff\n    create table(:cart_items) do\n-     add :price_when_carted, :decimal\n+     add :price_when_carted, :decimal, precision: 15, scale: 6, null: false\n      add :quantity, :integer\n-     add :cart_id, references(:carts, on_delete: :nothing)\n+     add :cart_id, references(:carts, on_delete: :delete_all)\n-     add :product_id, references(:products, on_delete: :nothing)\n+     add :product_id, references(:products, on_delete: :delete_all)\n\n      timestamps(type: :utc_datetime)\n    end\n\n-   create index(:cart_items, [:cart_id])\n    create index(:cart_items, [:product_id])\n+   create unique_index(:cart_items, [:cart_id, :product_id])\n```\n\nWe used the `:delete_all` strategy again to enforce data integrity. This way, when a cart or product is deleted from the application, we don't have to rely on application code in our `ShoppingCart` or `Catalog` contexts to worry about cleaning up the records. This keeps our application code decoupled and the data integrity enforcement where it belongs – in the database. We also added a unique constraint to ensure a duplicate product is not allowed to be added to a cart. As with the `product_categories` table, using a multi-column index lets us remove the separate index for the leftmost field (`cart_id`). With our database tables in place, we can now migrate up:\n\n```console\n$ mix ecto.migrate\n\n16:59:51.941 [info] == Running 20250205203342 Hello.Repo.Migrations.CreateCarts.change/0 forward\n\n16:59:51.945 [info] create table carts\n\n16:59:51.952 [info] == Migrated 20250205203342 in 0.0s\n\n16:59:51.988 [info] == Running 20250205213410 Hello.Repo.Migrations.CreateCartItems.change/0 forward\n\n16:59:51.988 [info] create table cart_items\n\n16:59:52.000 [info] create index cart_items_product_id_index\n\n16:59:52.001 [info] create index cart_items_cart_id_product_id_index\n\n16:59:52.002 [info] == Migrated 20250205213410 in 0.0s\n```\n\nOur database is ready to go with new `carts` and `cart_items` tables, but now we need to map that back into application code. You may be wondering how we can mix database foreign keys across different tables and how that relates to the context pattern of isolated, grouped functionality. Let's jump in and discuss the approaches and their tradeoffs.\n\n## Cross-context data\n\nSo far, we've done a great job isolating the two main contexts of our application from each other, but now we have a necessary dependency to handle.\n\nOur `Catalog.Product` resource serves to keep the responsibilities of representing a product inside the catalog, but ultimately for an item to exist in the cart, a product from the catalog must be present. Given this, our `ShoppingCart` context will have a data dependency on the `Catalog` context. With that in mind, we have two options. One is to expose APIs on the `Catalog` context that allows us to efficiently fetch product data for use in the `ShoppingCart` system, which we would manually stitch together. Or we can use database joins to fetch the dependent data. Both are valid options given your tradeoffs and application size, but joining data from the database when you have a hard data dependency is just fine for a large class of applications and is the approach we will take here.\n\nNow that we know where our data dependencies exist, let's add our schema associations so we can tie shopping cart items to products. First, let's make a quick change to our cart schema in `lib/hello/shopping_cart/cart.ex` to associate a cart to its items:\n\n```diff\n  schema \"carts\" do\n-   field :user_id, :id\n+   belongs_to :user, Hello.Accounts.User\n+   has_many :items, Hello.ShoppingCart.CartItem\n\n    timestamps(type: :utc_datetime)\n  end\n```\n\nNow that our cart is associated to the items we place in it, let's set up the cart item associations inside `lib/hello/shopping_cart/cart_item.ex`:\n\n```diff\n  schema \"cart_items\" do\n    field :price_when_carted, :decimal\n    field :quantity, :integer\n-   field :cart_id, :id\n-   field :product_id, :id\n\n+   belongs_to :cart, Hello.ShoppingCart.Cart\n+   belongs_to :product, Hello.Catalog.Product\n\n    timestamps(type: :utc_datetime)\n  end\n\n  @doc false\n  def changeset(cart_item, attrs) do\n    cart_item\n-   |> cast(attrs, [:price_when_carted, :quantity])\n+   |> cast(attrs, [:quantity])\n-   |> validate_required([:price_when_carted, :quantity])\n+   |> validate_required([:quantity])\n+   |> validate_number(:quantity, greater_than_or_equal_to: 0, less_than: 100)\n  end\n```\n\nFirst, we replaced the `cart_id` field with a standard `belongs_to` pointing at our `ShoppingCart.Cart` schema. Next, we replaced our `product_id` field by adding our first cross-context data dependency with a `belongs_to` for the `Catalog.Product` schema. Here, we intentionally coupled the data boundaries because it provides exactly what we need: an isolated context API with the bare minimum knowledge necessary to reference a product in our system. Second, we remove `price_when_carted` from the list of permitted attributes to ensure any price provided by user input is discarded. Finally, we added a new validation to our changeset. With `validate_number/3`, we ensure any quantity provided by user input is between 0 and 100.\n\nWith our schemas in place, we can start integrating the new data structures and `ShoppingCart` context APIs into our web-facing features.\n\n## Adding Cart functions\n\nAs we mentioned before, the context generators are only a starting point for our application. We can and should write well-named, purpose built functions to accomplish the goals of our context. We have a few new features to implement. First, we need to ensure every user of our application is granted a cart if one does not yet exist. From there, we can then allow users to add items to their cart, update item quantities, and calculate cart totals. Let's get started!\n\nBecause we used `mix phx.gen.auth`, we already have a real authentication system in place. We can use the `current_scope` assign to access the currently authenticated user. Let's add a new plug that assigns a cart if there is an authenticated user:\n\n```diff\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.LayoutView, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n    plug :fetch_current_scope_for_user\n+   plug :fetch_current_cart\n  end\n\n+ alias Hello.ShoppingCart\n+\n+ defp fetch_current_cart(%{assigns: %{current_scope: scope}} = conn, _opts) when not is_nil(scope) do\n+   if cart = ShoppingCart.get_cart(scope) do\n+     assign(conn, :cart, cart)\n+   else\n+     {:ok, new_cart} = ShoppingCart.create_cart(scope, %{})\n+     assign(conn, :cart, new_cart)\n+   end\n+ end\n+\n+ defp fetch_current_cart(conn, _opts), do: conn\n```\n\nWe added a new `:fetch_current_cart` plug which either finds a cart for the user UUID or creates a cart for the current user and assigns the result in the connection assigns. We'll need to implement our `ShoppingCart.get_cart/1`, but let's add our routes first.\n\nWe'll need to implement a cart controller for handling cart operations like viewing a cart, updating quantities, and initiating the checkout process, as well as a cart items controller for adding and removing individual items to and from the cart. The authentication system already generated different router scopes that have different authentication requirements:\n\n```elixir\n...\n  ## Authentication routes\n\n  scope \"/\", HelloWeb do\n    pipe_through [:browser, :redirect_if_user_is_authenticated]\n\n    get \"/user/register\", UserRegistrationController, :new\n    post \"/user/register\", UserRegistrationController, :create\n  end\n\n  scope \"/\", HelloWeb do\n    pipe_through [:browser, :require_authenticated_user]\n\n    get \"/user/settings\", UserSettingsController, :edit\n    put \"/user/settings\", UserSettingsController, :update\n    get \"/user/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n  end\n...\n```\n\nAs you can see, the registration route has a `:redirect_if_user_is_authenticated` plug, which means it will redirect to the home page if the user is already authenticated. The user settings routes use a `:require_authenticated_user` plug, which means they will redirect to the log in page if the user is not authenticated. These plugs are defined in the `lib/hello_web/user_auth.ex` module.\n\nFor our cart routes, we want to only allow access to authenticated users. Add the following routes to your router in `lib/hello_web/router.ex`:\n\n```diff\n   scope \"/\", HelloWeb do\n     pipe_through :browser\n\n     get \"/\", PageController, :index\n     resources \"/products\", ProductController\n   end\n\n+  scope \"/\", HelloWeb do\n+    pipe_through [:browser, :require_authenticated_user]\n+\n+    resources \"/cart_items\", CartItemController, only: [:create, :delete]\n+\n+    get \"/cart\", CartController, :show\n+    put \"/cart\", CartController, :update\n+  end\n```\n\nWe added a `resources` declaration for a `CartItemController`, which will wire up the routes for a create and delete action for adding and removing individual cart items. Next, we added two new routes pointing at a `CartController`. The first route, a GET request, will map to our show action, to show the cart contents. The second route, a PUT request, will handle the submission of a form for updating our cart quantities.\n\nWith our routes in place, let's add the ability to add an item to our cart from the product show page. Create a new file at `lib/hello_web/controllers/cart_item_controller.ex` and key this in:\n\n```elixir\ndefmodule HelloWeb.CartItemController do\n  use HelloWeb, :controller\n\n  alias Hello.ShoppingCart\n\n  def create(conn, %{\"product_id\" => product_id}) do\n    case ShoppingCart.add_item_to_cart(conn.assigns.current_scope, conn.assigns.cart, product_id) do\n      {:ok, _item} ->\n        conn\n        |> put_flash(:info, \"Item added to your cart\")\n        |> redirect(to: ~p\"/cart\")\n\n      {:error, _changeset} ->\n        conn\n        |> put_flash(:error, \"There was an error adding the item to your cart\")\n        |> redirect(to: ~p\"/cart\")\n    end\n  end\n\n  def delete(conn, %{\"id\" => product_id}) do\n    {:ok, _cart} = ShoppingCart.remove_item_from_cart(conn.assigns.current_scope, conn.assigns.cart, product_id)\n    redirect(conn, to: ~p\"/cart\")\n  end\nend\n```\n\nWe defined a new `CartItemController` with the create and delete actions that we declared in our router. For `create`, we call a `ShoppingCart.add_item_to_cart/3` function which we'll implement in a moment. If successful, we show a flash successful message and redirect to the cart show page; else, we show a flash error message and redirect to the cart show page. For `delete`, we'll call a `remove_item_from_cart` function which we'll implement on our `ShoppingCart` context  and then redirect back to the cart show page. We haven't implemented these two shopping cart functions yet, but notice how their names scream their intent: `add_item_to_cart` and `remove_item_from_cart` make it obvious what we are accomplishing here. It also allows us to spec out our web layer and context APIs without thinking about all the implementation details at once.\n\nLet's implement the new interface for the `ShoppingCart` context API in `lib/hello/shopping_cart.ex`:\n\n```diff\n+  alias Hello.Catalog\n-  alias Hello.ShoppingCart.Cart\n+  alias Hello.ShoppingCart.{Cart, CartItem}\n   alias Hello.Accounts.Scope\n\n+  def get_cart(%Scope{} = scope) do\n+    Repo.one(\n+      from(c in Cart,\n+        where: c.user_id == ^scope.user.id,\n+        left_join: i in assoc(c, :items),\n+        left_join: p in assoc(i, :product),\n+        order_by: [asc: i.inserted_at],\n+        preload: [items: {i, product: p}]\n+      )\n+    )\n+  end\n\n   def create_cart(%Scope{} = scope, attrs) do\n     with {:ok, cart = %Cart{}} <-\n            %Cart{}\n            |> Cart.changeset(attrs, scope)\n            |> Repo.insert() do\n       broadcast(scope, {:created, cart})\n-      {:ok, cart}\n+      {:ok, get_cart(scope)}\n     end\n   end\n+\n+  def add_item_to_cart(%Scope{} = scope, %Cart{} = cart, product_id) do\n+    true = cart.user_id == scope.user.id\n+    product = Catalog.get_product!(product_id)\n+\n+    %CartItem{quantity: 1, price_when_carted: product.price}\n+    |> CartItem.changeset(%{})\n+    |> Ecto.Changeset.put_assoc(:cart, cart)\n+    |> Ecto.Changeset.put_assoc(:product, product)\n+    |> Repo.insert(\n+      on_conflict: [inc: [quantity: 1]],\n+      conflict_target: [:cart_id, :product_id]\n+    )\n+  end\n+\n+  def remove_item_from_cart(%Scope{} = scope, %Cart{} = cart, product_id) do\n+    true = cart.user_id == scope.user.id\n+\n+    {1, _} =\n+      Repo.delete_all(\n+        from(i in CartItem,\n+          where: i.cart_id == ^cart.id,\n+          where: i.product_id == ^product_id\n+        )\n+      )\n+\n+    {:ok, get_cart(scope)}\n+  end\n```\n\nWe started by implementing `get_cart/1` which fetches our cart and joins the cart items, and their products so that we have the full cart populated with all preloaded data. Next, we modified our `create_cart` function to use `get_cart` to reload the cart contents.\n\nNext, we wrote our new `add_item_to_cart/3` function which accepts a scope, a cart struct and a product id. We proceed to fetch the product with `Catalog.get_product!/1`, showing how contexts can naturally invoke other contexts if required. You could also have chosen to receive the product as argument and you would achieve similar results. Then we used an upsert operation against our repo to either insert a new cart item into the database, or increase the quantity by one if it already exists in the cart. This is accomplished via the `on_conflict` and `conflict_target` options, which tells our repo how to handle an insert conflict.\n\nFinally, we implemented `remove_item_from_cart/3` where we simply issue a `Repo.delete_all` call with a query to delete the cart item in our cart that matches the product ID. Finally, we reload the cart contents by calling `get_cart/1`.\n\nWith our new cart functions in place, we can now expose the \"Add to cart\" button on the product catalog show page. Open up your template in `lib/hello_web/controllers/product_html/show.html.heex` and make the following changes:\n\n```diff\n...\n     <.button variant=\"primary\" navigate={~p\"/products/#{@product}/edit?return_to=show\"}>\n       <.icon name=\"hero-pencil-square\" /> Edit product\n     </.button>\n+    <.button href={~p\"/cart_items?product_id=#{@product.id}\"} method=\"post\">\n+      Add to cart\n+    </.button>\n...\n```\n\nThe `link` function component from `Phoenix.Component` accepts a `:method` attribute to issue an HTTP verb when clicked, instead of the default GET request. With this link in place, the \"Add to cart\" link will issue a POST request, which will be matched by the route we defined in router which dispatches to the `CartItemController.create/2` function.\n\nLet's try it out. Start your server with `mix phx.server` and visit a product page. If we try clicking the add to cart link, we'll be greeted by an error page. If you are authenticated the following logs should be visible in the console:\n\n```text\n[info] POST /cart_items\n[debug] Processing with HelloWeb.CartItemController.create/2\n  Parameters: %{\"_method\" => \"post\", \"product_id\" => \"1\", ...}\n  Pipelines: [:browser, :require_authenticated_user]\n[debug] QUERY OK source=\"user_tokens\" db=2.4ms idle=1340.8ms\n...\n[debug] QUERY OK source=\"cart_items\" db=2.5ms\nINSERT INTO \"cart_items\" ...\n[info] Sent 302 in 24ms\n[info] GET /cart\n[debug] Processing with HelloWeb.CartController.show/2\n  Parameters: %{}\n  Pipelines: [:browser, :require_authenticated_user]\n[debug] QUERY OK source=\"user_tokens\" db=1.6ms idle=430.2ms\n...\n[debug] QUERY OK source=\"carts\" db=1.9ms idle=1798.5ms\n...\n[info] Sent 500 in 18ms\n[error] ** (UndefinedFunctionError) function HelloWeb.CartController.init/1 is undefined (module HelloWeb.CartController is not available)\n    ...\n```\n\nIt's working! Kind of. If we follow the logs, we see our POST to the `/cart_items` path. Next, we can see our `ShoppingCart.add_item_to_cart` function successfully inserted a row into the `cart_items` table, and then we issued a redirect to `/cart`. Before our error, we also see a query to the `carts` table, which means we're fetching the current user's cart. So far so good. We know our `CartItem` controller and new `ShoppingCart` context functions are doing their jobs, but we've hit our next unimplemented feature when the router attempts to dispatch to a nonexistent cart controller. Let's create the cart controller, view, and template to display and manage user carts.\n\nCreate a new file at `lib/hello_web/controllers/cart_controller.ex` and key this in:\n\n```elixir\ndefmodule HelloWeb.CartController do\n  use HelloWeb, :controller\n\n  alias Hello.ShoppingCart\n\n  def show(conn, _params) do\n    render(conn, :show, changeset: ShoppingCart.change_cart(conn.assigns.current_scope, conn.assigns.cart))\n  end\nend\n```\n\nWe defined a new cart controller to handle the `get \"/cart\"` route. For showing a cart, we render a `\"show.html\"` template which we'll create in a moment. We know we need to allow the cart items to be changed by quantity updates, so right away we know we'll need a cart changeset. Fortunately, the context generator included a `ShoppingCart.change_cart/1` function, which we'll use. We pass it our cart struct which is already in the connection assigns thanks to the `fetch_current_cart` plug we defined in the router.\n\nNext, we can implement the view and template. Create a new view file at `lib/hello_web/controllers/cart_html.ex` with the following content:\n\n```elixir\ndefmodule HelloWeb.CartHTML do\n  use HelloWeb, :html\n\n  alias Hello.ShoppingCart\n\n  embed_templates \"cart_html/*\"\n\n  def currency_to_str(%Decimal{} = val), do: \"$#{Decimal.round(val, 2)}\"\nend\n```\n\nWe created a view to render our `show.html` template and aliased our `ShoppingCart` context so it will be in scope for our template. We'll need to display the cart prices like product item price, cart total, etc, so we defined a `currency_to_str/1` which takes our decimal struct, rounds it properly for display, and prepends a USD dollar sign.\n\nNext we can create the template at `lib/hello_web/controllers/cart_html/show.html.heex`:\n\n```heex\n<Layouts.app flash={@flash}>\n  <.header>\n    My Cart\n    <:subtitle :if={@cart.items == []}>Your cart is empty</:subtitle>\n  </.header>\n\n  <div :if={@cart.items !== []}>\n    <.form :let={f} for={@changeset} action={~p\"/cart\"}>\n      <.inputs_for :let={%{data: item} = item_form} field={f[:items]}>\n        <.input field={item_form[:quantity]} type=\"number\" label={item.product.title} />\n        {currency_to_str(ShoppingCart.total_item_price(item))}\n      </.inputs_for>\n      <.button>Update cart</.button>\n    </.form>\n    <b>Total</b>: {currency_to_str(ShoppingCart.total_cart_price(@cart))}\n  </div>\n\n  <.button navigate={~p\"/products\"}>Back to products</.button>\n</Layouts.app>\n```\n\nWe started by showing the empty cart message if our preloaded `cart.items` is empty. If we have items, we use the `form` component provided by our `HelloWeb.CoreComponents` to take our cart changeset that we assigned in the `CartController.show/2` action and create a form which maps to our cart controller `update/2` action. Within the form, we use the [`inputs_for`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1) component to render inputs for the nested cart items. This will allow us to map item inputs back together when the form is submitted. Next, we display a number input for the item quantity and label it with the product title. We finish the item form by converting the item price to string. We haven't written the `ShoppingCart.total_item_price/1` function yet, but again we employed the idea of clear, descriptive public interfaces for our contexts. After rendering inputs for all the cart items, we show an \"update cart\" submit button, along with the total price of the entire cart. This is accomplished with another new `ShoppingCart.total_cart_price/1` function which we'll implement in a moment. Finally, we added a `back` component to go back to our products page.\n\nWe're almost ready to try out our cart page, but first we need to implement our new currency calculation functions. Open up your shopping cart context at `lib/hello/shopping_cart.ex` and add these new functions:\n\n```elixir\n  def total_item_price(%CartItem{} = item) do\n    Decimal.mult(item.product.price, item.quantity)\n  end\n\n  def total_cart_price(%Cart{} = cart) do\n    Enum.reduce(cart.items, 0, fn item, acc ->\n      item\n      |> total_item_price()\n      |> Decimal.add(acc)\n    end)\n  end\n```\n\nWe implemented `total_item_price/1` which accepts a `%CartItem{}` struct. To calculate the total price, we simply take the preloaded product's price and multiply it by the item's quantity. We used `Decimal.mult/2` to take our decimal currency struct and multiply it with proper precision. Similarly for calculating the total cart price, we implemented a `total_cart_price/1` function which accepts the cart and sums the preloaded product prices for items in the cart. We again make use of the `Decimal` functions to add our decimal structs together.\n\nNow that we can calculate price totals, let's try it out! Visit [`http://localhost:4000/cart`](http://localhost:4000/cart) and you should already see your first item in the cart. Going back to the same product and clicking \"add to cart\" will show our upsert in action. Your quantity should now be two. Nice work!\n\nOur cart page is almost complete, but submitting the form will yield yet another error.\n\n```text\n[info] POST /cart\n...\n[error] ** (UndefinedFunctionError) function HelloWeb.CartController.update/2 is undefined or private\n```\n\nLet's head back to our `CartController` at `lib/hello_web/controllers/cart_controller.ex` and implement the update action:\n\n```elixir\n  def update(conn, %{\"cart\" => cart_params}) do\n    case ShoppingCart.update_cart(conn.assigns.current_scope, conn.assigns.cart, cart_params) do\n      {:ok, _cart} ->\n        redirect(conn, to: ~p\"/cart\")\n\n      {:error, _changeset} ->\n        conn\n        |> put_flash(:error, \"There was an error updating your cart\")\n        |> redirect(to: ~p\"/cart\")\n    end\n  end\n```\n\nWe started by plucking out the cart params from the form submit. Next, we call our existing `ShoppingCart.update_cart/2` function which was added by the context generator. We'll need to make some changes to this function, but the interface is good as is. If the update is successful, we redirect back to the cart page, otherwise we show a flash error message and send the user back to the cart page to fix any mistakes. Out-of-the-box, our `ShoppingCart.update_cart/2` function only concerned itself with casting the cart params into a changeset and updates it against our repo. For our purposes, we now need it to handle nested cart item associations, and most importantly, business logic for how to handle quantity updates like zero-quantity items being removed from the cart.\n\nHead back over to your shopping cart context in `lib/hello/shopping_cart.ex` and replace your `update_cart/2` function with the following implementation:\n\n```elixir\n  def update_cart(%Scope{} = scope, %Cart{} = cart, attrs) do\n    true = cart.user_id == scope.user.id\n\n    changeset =\n      cart\n      |> Cart.changeset(attrs, scope)\n      |> Ecto.Changeset.cast_assoc(:items, with: &CartItem.changeset/2)\n  \n    Repo.transact(fn ->\n      with {:ok, cart} <- Repo.update(changeset),\n           {_count, _cart_items} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id and i.quantity == 0)) do\n        {:ok, cart}\n      end\n    end)\n    |> case do\n      {:ok, cart} ->\n        broadcast_cart(scope, {:updated, cart})\n        {:ok, cart}\n\n      {:error, reason} ->\n        {:error, reason}\n    end\n  end\n```\n\nWe started much like how our out-of-the-box code started – we take the cart struct and cast the user input to a cart changeset, except this time we use `Ecto.Changeset.cast_assoc/3` to cast the nested item data into `CartItem` changesets. Remember the [`<.inputs_for />`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#inputs_for/1) call in our cart form template? That hidden ID data is what allows Ecto's `cast_assoc` to map item data back to existing item associations in the cart. \n\nOnce the `changeset` is ready, we wrap everything in `Repo.transact/2` so the operations run safely as one. Inside the transaction, we update the cart using `Repo.update/1`. If the update succeeds, we follow up with a cleanup step using `Repo.delete_all/2` to remove any cart items with zero quantity. Running both steps in the same transaction prevents partial updates and keeps the cart data accurate. Finally, we broadcast the updated cart so that any connected LiveViews can instantly show the changes.\n\nLet's head back to the browser and try it out. Add a few products to your cart, update the quantities, and watch the values changes along with the price calculations. Setting any quantity to 0 will also remove the item. You can also try logging out and registering a new user to see how the carts are scoped to the current user. Pretty neat!\n"
  },
  {
    "path": "guides/data_modelling/faq.md",
    "content": "# 6. FAQ\n\nHere we list frequently asked questions about contexts.\n\n## When to use code generators?\n\nIn this guide, we have used code generators for schemas, contexts, controllers, and more. If you are happy to move forward with Phoenix defaults, feel free to rely on generators to scaffold large parts of your application. When using Phoenix generators, the main question you need to answer is: does this new functionality (with its schema, table, and fields) belong to one of the existing contexts or a new one?\n\nThis way, Phoenix generators guide you to use contexts to group related functionality, instead of having several dozens of schemas laying around without any structure. And remember: if you're stuck when trying to come up with a context name, you can simply use the plural form of the resource you're creating.\n\n## How do I structure code inside contexts?\n\nYou may wonder how to organize the code inside contexts. For example, should you define a module for changesets (such as ProductChangesets) and another module for queries (such as ProductQueries)?\n\nOne important benefit of contexts is that this decision does not matter much. The context is your public API, the other modules are private. Contexts isolate these modules into small groups so the surface area of your application is the context and not _all of your code_.\n\nSo while you and your team could establish patterns for organizing these private modules, it is also our opinion that it is completely fine for them to be different. The major focus should be on how the contexts are defined and how they interact with each other (and with your web application).\n\nThink about it as a well-kept neighbourhood. Your contexts are houses, you want to keep them well-preserved, well-connected, etc. Inside the houses, they may all be a little bit different, and that's fine.\n\n## Returning Ecto structures from context APIs\n\nAs we explored the context API, you might have wondered:\n\n> If one of the goals of our context is to encapsulate Ecto Repo access, why does `create_user/1` return an `Ecto.Changeset` struct when we fail to create a user?\n\nAlthough Changesets are part of Ecto, they are not tied to the database, and they can be used to map data from and to any source, which makes it a general and useful data structure for tracking field changes, perform validations, and generate error messages.\n\nFor those reasons, `%Ecto.Changeset{}` is a good choice to model the data changes between your contexts and your web layer - regardless if you are talking to an API or the database.\n\nFinally, note that your controllers and views are not hardcoded to work exclusively with Ecto either. Instead, Phoenix defines protocols such as `Phoenix.Param` and `Phoenix.HTML.FormData`, which allow any library to extend how Phoenix generates URL parameters or renders forms. Conveniently for us, the `phoenix_ecto` project implements those protocols, but you could as well bring your own data structures and implement them yourself.\n"
  },
  {
    "path": "guides/data_modelling/in_context_relationships.md",
    "content": "# 3. In-context Relationships\n\nOur basic catalog features are nice, but let's take it up a notch by categorizing products. Many ecommerce solutions allow products to be categorized in different ways, such as a product being marked for fashion, power tools, and so on. Starting with a one-to-one relationship between product and categories will cause major code changes later if we need to start supporting multiple categories. Let's set up a category association that will allow us to start off tracking a single category per product, but easily support more later as we grow our features.\n\nFor now, categories will contain only textual information. Our first order of business is to decide where categories live in the application. We have our `Catalog` context, which manages the exhibition of our products. Product categorization is a natural fit here. Phoenix is also smart enough to generate code inside an existing context, which makes adding new resources to a context a breeze. Run the following command at your project root:\n\n> Sometimes it may be tricky to determine if two resources belong to the same context or not. In those cases, prefer distinct contexts per resource and refactor later if necessary. Otherwise you can easily end up with large contexts of loosely related entities. Also keep in mind that the fact two resources are related does not necessarily mean they belong to the same context, otherwise you would quickly end up with one large context, as the majority of resources in an application are connected to each other. To sum it up: if you are unsure, you should prefer separate modules (contexts).\n\n```console\nmix phx.gen.context Catalog Category categories \\\ntitle:string:unique --no-scope\n```\n\nYou will see the following output in your terminal: \n\n```console\nYou are generating into an existing context.\n\nThe Hello.Catalog context currently has 7 functions and 1 file in its directory.\n\n  * It's OK to have multiple resources in the same context as long as they are closely related. \n\n...\n\nWould you like to proceed? [Yn]\n```\n\nType `y` followed by the `Return` key.\nYou should see output similar to:\n\n```console\n* creating lib/hello/catalog/category.ex\n* creating priv/repo/migrations/20250203192325_create_categories.exs\n* injecting lib/hello/catalog.ex\n* injecting test/hello/catalog_test.exs\n* injecting test/support/fixtures/catalog_fixtures.ex\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nThis time around, we used `mix phx.gen.context`, which is just like `mix phx.gen.html`, except it doesn't generate the web files for us. Since we already have controllers and templates for managing products, we can integrate the new category features into our existing web form and product show page. We can see we now have a new `Category` schema alongside our product schema at `lib/hello/catalog/category.ex`, and Phoenix told us it was *injecting* new functions in our existing Catalog context for the category functionality. The injected functions will look very familiar to our product functions, with new functions like `create_category`, `list_categories`, and so on. Before we migrate up, we need to do a second bit of code generation. Our category schema is great for representing an individual category in the system, but we need to support a many-to-many relationship between products and categories. Fortunately, ecto allows us to do this simply with a join table, so let's generate that now with the `ecto.gen.migration` command:\n\n```console\nmix ecto.gen.migration create_product_categories\n```\n\nYou will see output confirming the migration file was created:\n\n```console\n* creating priv/repo/migrations/20250203192958_create_product_categories.exs\n```\n\nNext, let's open up the new migration file and add the following code to the `change` function:\n\n```elixir\ndefmodule Hello.Repo.Migrations.CreateProductCategories do\n  use Ecto.Migration\n\n  def change do\n    create table(:product_categories, primary_key: false) do\n      add :product_id, references(:products, on_delete: :delete_all)\n      add :category_id, references(:categories, on_delete: :delete_all)\n    end\n\n    create index(:product_categories, [:product_id])\n    create unique_index(:product_categories, [:category_id, :product_id])\n  end\nend\n```\n\nWe created a `product_categories` table and used the `primary_key: false` option since our join table does not need a primary key. Next we defined our `:product_id` and `:category_id` foreign key fields, and passed `on_delete: :delete_all` to ensure the database prunes our join table records if a linked product or category is deleted. By using a database constraint, we enforce data integrity at the database level, rather than relying on ad-hoc and error-prone application logic.\n\nNext, we created indexes for our foreign keys, one of which is a unique index to ensure a product cannot have duplicate categories. Note that we do not necessarily need single-column index for `category_id` because it is in the leftmost prefix of multicolumn index, which is enough for the database optimizer. Adding a redundant index, on the other hand, only adds overhead on write.\n\nWith our migrations in place, we can migrate up.\n\n```console\nmix ecto.migrate\n```\n\nYou will see the following output confirming migration success:\n\n```\n18:20:36.489 [info] == Running 20250222231834 Hello.Repo.Migrations.CreateCategories.change/0 forward\n\n18:20:36.493 [info] create table categories\n\n18:20:36.508 [info] create index categories_title_index\n\n18:20:36.512 [info] == Migrated 20250222231834 in 0.0s\n\n18:20:36.547 [info] == Running 20250222231930 Hello.Repo.Migrations.CreateProductCategories.change/0 forward\n\n18:20:36.547 [info] create table product_categories\n\n18:20:36.557 [info] create index product_categories_product_id_index\n\n18:20:36.560 [info]  create index product_categories_category_id_product_id_index\n\n18:20:36.562 [info] == Migrated 20250222231930 in 0.0s\n```\n\nNow that we have a `Catalog.Product` schema and a join table to associate products and categories, we're nearly ready to start wiring up our new features. Before we dive in, we first need real categories to select in our web UI. Let's quickly seed some new categories in the application. Add the following code to your seeds file in `priv/repo/seeds.exs`:\n\n```elixir\nfor title <- [\"Home Improvement\", \"Power Tools\", \"Gardening\", \"Books\", \"Education\"] do\n  {:ok, _} = Hello.Catalog.create_category(%{title: title})\nend\n```\n\nWe simply enumerate over a list of category titles and use the generated `create_category/1` function of our catalog context to persist the new records. We can run the seeds with `mix run`:\n\n```console\nmix run priv/repo/seeds.exs\n```\n\nThe output in the terminal confirms the `seeds.exs` executed successfully: \n\n```console\n[debug] QUERY OK db=3.1ms decode=1.1ms queue=0.7ms idle=2.2ms\nINSERT INTO \"categories\" (\"title\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"Home Improvement\", ~N[2025-02-03 19:39:53], ~N[2025-02-03 19:39:53]]\n[debug] QUERY OK db=1.2ms queue=1.3ms idle=12.3ms\nINSERT INTO \"categories\" (\"title\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"Power Tools\", ~N[2025-02-03 19:39:53], ~N[2025-02-03 19:39:53]]\n[debug] QUERY OK db=1.1ms queue=1.1ms idle=15.1ms\nINSERT INTO \"categories\" (\"title\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"Gardening\", ~N[2025-02-03 19:39:53], ~N[2025-02-03 19:39:53]]\n[debug] QUERY OK db=2.4ms queue=1.0ms idle=17.6ms\nINSERT INTO \"categories\" (\"title\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"Books\", ~N[2025-02-03 19:39:53], ~N[2025-02-03 19:39:53]]\n```\n\nPerfect. Before we integrate categories in the web layer, we need to let our context know how to associate products and categories. First, open up `lib/hello/catalog/product.ex` and add the following association:\n\n```diff\n+ alias Hello.Catalog.Category\n\n  schema \"products\" do\n    field :description, :string\n    field :price, :decimal\n    field :title, :string\n    field :views, :integer\n\n+   many_to_many :categories, Category, join_through: \"product_categories\", on_replace: :delete\n\n    timestamps(type: :utc_datetime)\n  end\n\n```\n\nWe used `Ecto.Schema`'s `many_to_many` macro to let Ecto know how to associate our product to multiple categories through the `\"product_categories\"` join table. We also used the `on_replace: :delete` option to declare that any existing join records should be deleted when we are changing our categories.\n\nWith our schema associations set up, we can implement the selection of categories in our product form. To do so, we need to translate the user input of catalog IDs from the front-end to our many-to-many association. Fortunately Ecto makes this a breeze now that our schema is set up. Open up your catalog context and make the following changes:\n\n```diff\n+ alias Hello.Catalog.Category\n\n- def get_product!(id), do: Repo.get!(Product, id)\n+ def get_product!(id) do\n+   Product\n+   |> Repo.get!(id)\n+   |> Repo.preload(:categories)\n+ end\n\n  def create_product(attrs) do\n    %Product{}\n-   |> Product.changeset(attrs)\n+   |> change_product(attrs)\n    |> Repo.insert()\n  end\n\n  def update_product(%Product{} = product, attrs) do\n    product\n-   |> Product.changeset(attrs)\n+   |> change_product(attrs)\n    |> Repo.update()\n  end\n\n  def change_product(%Product{} = product, attrs \\\\ %{}) do\n-   Product.changeset(product, attrs)\n+   categories = list_categories_by_id(attrs[\"category_ids\"])\n\n+   product\n+   |> Repo.preload(:categories)\n+   |> Product.changeset(attrs)\n+   |> Ecto.Changeset.put_assoc(:categories, categories)\n  end\n\n+ def list_categories_by_id(nil), do: []\n+ def list_categories_by_id(category_ids) do\n+   Repo.all(from c in Category, where: c.id in ^category_ids)\n+ end\n```\n\nFirst, we added `Repo.preload` to preload our categories when we fetch a product. This will allow us to reference `product.categories` in our controllers, templates, and anywhere else we want to make use of category information. Next, we modified our `create_product` and `update_product` functions to call into our existing `change_product` function to produce a changeset. Within `change_product` we added a lookup to find all categories if the `\"category_ids\"` attribute is present. Then we preloaded categories and called `Ecto.Changeset.put_assoc` to place the fetched categories into the changeset. Finally, we implemented the `list_categories_by_id/1` function to query the categories matching the category IDs, or return an empty list if no `\"category_ids\"` attribute is present. Now our `create_product` and `update_product` functions receive a changeset with the category associations all ready to go once we attempt an insert or update against our repo.\n\nNext, let's expose our new feature to the web by adding the category input to our product form. To keep our form template tidy, let's write a new function to wrap up the details of rendering a category select input for our product. Open up your `ProductHTML` view in `lib/hello_web/controllers/product_html.ex` and key this in:\n\n```elixir\n  def category_opts(changeset) do\n    existing_ids =\n      changeset\n      |> Ecto.Changeset.get_change(:categories, [])\n      |> Enum.map(& &1.data.id)\n\n    for cat <- Hello.Catalog.list_categories() do\n      [key: cat.title, value: cat.id, selected: cat.id in existing_ids]\n    end\n  end\n```\n\nWe added a new `category_opts/1` function which generates the select options for a multiple select tag we will add soon. We calculated the existing category IDs from our changeset, then used those values when we generate the select options for the input tag. We did this by enumerating over all of our categories and returning the appropriate `key`, `value`, and `selected` values. We marked an option as selected if the category ID was found in those category IDs in our changeset.\n\nWith our `category_opts` function in place, we can open up `lib/hello_web/controllers/product_html/product_form.html.heex` and add:\n\n```diff\n  ...\n  <.input field={f[:views]} type=\"number\" label=\"Views\" />\n\n+ <.input field={f[:category_ids]} type=\"select\" multiple options={category_opts(@changeset)} />\n\n  <.button>Save Product</.button>\n```\n\nWe added a `category_select` above our save button. Now let's try it out. Next, let's show the product's categories in the product show template. Add the following code to the list in `lib/hello_web/controllers/product_html/show.html.heex`:\n\n```diff\n<.list>\n  ...\n+ <:item title=\"Categories\">\n+   <ul>\n+     <li :for={cat <- @product.categories}>{cat.title}</li>\n+   </ul>\n+ </:item>\n</.list>\n```\n\nNow if we start the server with `mix phx.server` and visit [http://localhost:4000/products/new](http://localhost:4000/products/new), we'll see the new category multiple select input. Enter some valid product details, select a category or two, and click save.\n\n```text\nTitle: Elixir Flashcards\nDescription: Flash card set for the Elixir programming language\nPrice: 5.000000\nViews: 0\nCategories:\nEducation\nBooks\n```\n\nIt's not much to look at yet, but it works! We added relationships within our context complete with data integrity enforced by the database. Not bad. Let's keep building!\n"
  },
  {
    "path": "guides/data_modelling/more_examples.md",
    "content": "# 5. Bringing It Home\n\nWith our `Catalog` and `ShoppingCart` contexts, we're seeing first-hand how our well-considered modules and function names are yielding clear and maintainable code. Our last order of business is to allow the user to initiate the checkout process. We won't go as far as integrating payment processing or order fulfillment, but we'll get you started in that direction. This will be a great opportunity to put what we have learned so far in practice.\n\nLike before, we need to decide where code for completing an order should live. Is it part of the catalog? Clearly not, but what about the shopping cart? Shopping carts are related to orders – after all, the user has to add items in order to purchase any products – but should the order checkout process be grouped here?\n\nIf we stop and consider the order process, we'll see that orders involve related, but distinctly different data from the cart contents. Also, business rules around the checkout process are much different than carting. For example, we may allow a user to add a back-ordered item to their cart, but we could not allow an order with no inventory to be completed. Additionally, we need to capture point-in-time product information when an order is completed, such as the price of the items *at payment transaction time*. This is essential because a product price may change in the future, but the line items in our order must always record and display what we charged at time of purchase. For these reasons, we can start to see that ordering can stand on its own with its own data concerns and business rules.\n\nNaming wise, `Orders` clearly defines our context, so let's get started by again taking advantage of the context generators. Note that the `user` scope generated by `mix phx.gen.auth` is marked as default scope (in your `config/config.exs`), therefore we don't need to specify it in our command. There can be different scopes in an application, in which case the `--scope` option can be used when running the generators. Run the following command in your console:\n\n```console\n$ mix phx.gen.context Orders Order orders total_price:decimal\n\n* creating lib/hello/orders/order.ex\n* creating priv/repo/migrations/20250209214612_create_orders.exs\n* creating lib/hello/orders.ex\n* injecting lib/hello/orders.ex\n* creating test/hello/orders_test.exs\n* injecting test/hello/orders_test.exs\n* creating test/support/fixtures/orders_fixtures.ex\n* injecting test/support/fixtures/orders_fixtures.ex\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nWe generated an `Orders` context. The order is automatically scoped to the current user and added a `total_price` column. With our starting point in place, let's open up the newly created migration in `priv/repo/migrations/*_create_orders.exs` and make the following changes:\n\n```diff\n  def change do\n    create table(:orders) do\n-     add :total_price, :decimal\n+     add :total_price, :decimal, precision: 15, scale: 6, null: false\n      add :user_id, references(:users, type: :id, on_delete: :delete_all)\n\n      timestamps(type: :utc_datetime)\n    end\n  end\n```\n\nLike we did previously, we gave appropriate precision and scale options for our decimal column which will allow us to store currency without precision loss. We also added a not-null constraint to enforce all orders to have a price.\n\nThe orders table alone doesn't hold much information, but we know we'll need to store point-in-time product price information of all the items in the order. For that, we'll add an additional struct for this context named `LineItem`. Line items will capture the price of the product *at payment transaction time*. Please run the following command:\n\n```console\n$ mix phx.gen.context Orders LineItem order_line_items \\\nprice:decimal quantity:integer \\\norder_id:references:orders product_id:references:products --no-scope\n\nYou are generating into an existing context.\n...\nWould you like to proceed? [Yn] y\n* creating lib/hello/orders/line_item.ex\n* creating priv/repo/migrations/20250209215050_create_order_line_items.exs\n* injecting lib/hello/orders.ex\n* injecting test/hello/orders_test.exs\n* injecting test/support/fixtures/orders_fixtures.ex\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nWe used the `phx.gen.context` command to generate the `LineItem` Ecto schema and inject supporting functions into our orders context. Like before, let's modify the migration in `priv/repo/migrations/*_create_order_line_items.exs` and make the following decimal field changes:\n\n```diff\n  def change do\n    create table(:order_line_items) do\n-     add :price, :decimal\n+     add :price, :decimal, precision: 15, scale: 6, null: false\n      add :quantity, :integer\n      add :order_id, references(:orders, on_delete: :nothing)\n      add :product_id, references(:products, on_delete: :nothing)\n\n      timestamps(type: :utc_datetime)\n    end\n\n    create index(:order_line_items, [:order_id])\n    create index(:order_line_items, [:product_id])\n  end\n```\n\nWith our migration in place, let's wire up our orders and line items associations in `lib/hello/orders/order.ex`:\n\n```diff\n  schema \"orders\" do\n    field :total_price, :decimal\n-   field :user_id, :id\n\n+   belongs_to :user, Hello.Accounts.User\n+   has_many :line_items, Hello.Orders.LineItem\n+   has_many :products, through: [:line_items, :product]\n\n    timestamps(type: :utc_datetime)\n  end\n```\n\nWe used `has_many :line_items` to associate orders and line items, just like we've seen before. Next, we used the `:through` feature of `has_many`, which allows us to instruct ecto how to associate resources across another relationship. In this case, we can associate products of an order by finding all products through associated line items. Next, let's wire up the association in the other direction in `lib/hello/orders/line_item.ex`:\n\n```diff\n  schema \"order_line_items\" do\n    field :price, :decimal\n    field :quantity, :integer\n-   field :order_id, :id\n-   field :product_id, :id\n\n+   belongs_to :order, Hello.Orders.Order\n+   belongs_to :product, Hello.Catalog.Product\n\n    timestamps(type: :utc_datetime)\n  end\n```\n\nWe used `belongs_to` to associate line items to orders and products. With our associations in place, we can start integrating the web interface into our order process. Open up your router `lib/hello_web/router.ex` and add the following line:\n\n```diff\n  scope \"/\", HelloWeb do\n    pipe_through [:browser, :require_authenticated_user]\n\n    resources \"/cart_items\", CartItemController, only: [:create, :delete]\n\n    get \"/cart\", CartController, :show\n    put \"/cart\", CartController, :update\n\n+   resources \"/orders\", OrderController, only: [:create, :show]\n  end\n```\n\nWe wired up `create` and `show` routes for our generated `OrderController`, since these are the only actions we need at the moment. With our routes in place, we can now migrate up:\n\n```console\n$ mix ecto.migrate\n\n17:14:37.715 [info] == Running 20250209214612 Hello.Repo.Migrations.CreateOrders.change/0 forward\n\n17:14:37.720 [info] create table orders\n\n17:14:37.755 [info] == Migrated 20250209214612 in 0.0s\n\n17:14:37.784 [info] == Running 20250209215050 Hello.Repo.Migrations.CreateOrderLineItems.change/0 forward\n\n17:14:37.785 [info] create table order_line_items\n\n17:14:37.795 [info] create index order_line_items_order_id_index\n\n17:14:37.796 [info] create index order_line_items_product_id_index\n\n17:14:37.798 [info] == Migrated 20250209215050 in 0.0s\n```\n\nBefore we render information about our orders, we need to ensure our order data is fully populated and can be looked up by a current user. Open up your orders context in `lib/hello/orders.ex` and adjust your `get_order!/2` to include a preload:\n\n```diff\n   def get_order!(%Scope{} = scope, id) do\n-    Repo.get_by!(Order, id: id, user_id: scope.user.id)\n+    Order\n+    |> Repo.get_by!(id: id, user_id: scope.user.id)\n+    |> Repo.preload([line_items: [:product]])\n   end\n```\n\nTo complete an order, our cart page can issue a POST to the `OrderController.create` action, but we need to implement the operations and logic to actually complete an order. Like before, we'll start at the web interface. Create a new file at `lib/hello_web/controllers/order_controller.ex` and key this in:\n\n```elixir\ndefmodule HelloWeb.OrderController do\n  use HelloWeb, :controller\n\n  alias Hello.Orders\n\n  def create(conn, _) do\n    case Orders.complete_order(conn.assigns.current_scope, conn.assigns.cart) do\n      {:ok, order} ->\n        conn\n        |> put_flash(:info, \"Order created successfully.\")\n        |> redirect(to: ~p\"/orders/#{order}\")\n\n      {:error, _reason} ->\n        conn\n        |> put_flash(:error, \"There was an error processing your order\")\n        |> redirect(to: ~p\"/cart\")\n    end\n  end\nend\n```\n\nWe wrote the `create` action to call an as-yet-implemented `Orders.complete_order/2` function. Our code is technically \"creating\" an order, but it's important to step back and consider the naming of your interfaces. The act of *completing* an order is extremely important in our system. Money changes hands in a transaction, physical goods could be automatically shipped, etc. Such an operation deserves a better, more obvious function name, such as `complete_order`. If the order is completed successfully we redirect to the show page, otherwise a flash error is shown as we redirect back to the cart page.\n\nHere is also a good opportunity to highlight that contexts can naturally work with data defined by other contexts too. This will be especially common with data that is used throughout the application, such as the cart here (but it can also be the current user or the current project, and so forth, depending on your project).\n\nNow we can implement our `Orders.complete_order/2` function. To complete an order, our job will require a few operations:\n\n  1. A new order record must be persisted with the total price of the order\n  2. All items in the cart must be transformed into new order line items records\n    with quantity and point-in-time product price information\n  3. After successful order insert (and eventual payment), items must be pruned\n    from the cart\n\nFrom our requirements alone, we can start to see why a generic `create_order` function doesn't cut it. Let's implement this new function in `lib/hello/orders.ex`:\n\n```elixir\n  alias Hello.Orders.LineItem\n  alias Hello.ShoppingCart\n\n  def complete_order(%Scope{} = scope, %ShoppingCart.Cart{} = cart) do\n    true = cart.user_id == scope.user.id\n\n    line_items =\n      Enum.map(cart.items, fn item ->\n        %{\n          product_id: item.product_id,\n          price: item.product.price,\n          quantity: item.quantity\n        }\n      end)\n\n    order_changeset =\n      Ecto.Changeset.change(%Order{}, %{\n        user_id: scope.user.id,\n        total_price: ShoppingCart.total_cart_price(cart),\n        line_items: line_items\n      })\n\n    Repo.transact(fn ->\n      with {:ok, order} <- Repo.insert(order_changeset),\n           {:ok, _cart} <- ShoppingCart.prune_cart_items(scope, cart) do\n        {:ok, order}\n      end\n    end)\n    |> case do\n      {:ok, order} ->\n        broadcast_order(scope, {:created, order})\n        {:ok, order}\n  \n      {:error, reason} ->\n        {:error, reason}\n    end\n  end\n```\n\nWe started by mapping the `%ShoppingCart.CartItem{}`'s in our shopping cart into a map of order line items structs. The job of the order line item record is to capture the price of the product *at payment transaction time*, so we reference the product's price here. Next, we create a bare order changeset with `Ecto.Changeset.change/2` and associate our user UUID, set our total price calculation, and place our order line items in the changeset. With a fresh order changeset ready to be inserted, we now make use of `Repo.transact/2` to execute our operations in a database transaction. We start by inserting the order, followed by a step that prunes all items from the user’s cart. The function wrapped inside `Repo.transact/2` must either return `{:ok, result}` or error, which halts and rolls back the transaction. Running the transaction will execute these operations in sequence, and we return the result to the caller once completed.\n\nTo close out our order completion, we need to implement the `ShoppingCart.prune_cart_items/1` function in `lib/hello/shopping_cart.ex`:\n\n```elixir\n  def prune_cart_items(%Scope{} = scope, %Cart{} = cart) do\n    {_, _} = Repo.delete_all(from(i in CartItem, where: i.cart_id == ^cart.id))\n    {:ok, get_cart(scope)}\n  end\n```\n\nOur new function accepts the cart struct and issues a `Repo.delete_all` which accepts a query of all items for the provided cart. We return a success result by simply reloading the pruned cart to the caller. With our context complete, we now need to show the user their completed order. Head back to your order controller and add the `show/2` action:\n\n```elixir\n  def show(conn, %{\"id\" => id}) do\n    order = Orders.get_order!(conn.assigns.current_scope, id)\n    render(conn, :show, order: order)\n  end\n```\n\nWe added the show action to pass our `conn.assigns.current_scope` to `get_order!` which authorizes orders to be viewable only by the owner of the order. Next, we can implement the view and template. Create a new view file at `lib/hello_web/controllers/order_html.ex` with the following content:\n\n```elixir\ndefmodule HelloWeb.OrderHTML do\n  use HelloWeb, :html\n\n  embed_templates \"order_html/*\"\nend\n```\nNext we can create the template at `lib/hello_web/controllers/order_html/show.html.heex`:\n\n```heex\n<Layouts.app flash={@flash}>\n  <.header>\n    Thank you for your order!\n    <:subtitle>\n      <strong>Email: </strong>{@current_scope.user.email}\n    </:subtitle>\n  </.header>\n\n  <.table id=\"items\" rows={@order.line_items}>\n    <:col :let={item} label=\"Title\">{item.product.title}</:col>\n    <:col :let={item} label=\"Quantity\">{item.quantity}</:col>\n    <:col :let={item} label=\"Price\">\n      {HelloWeb.CartHTML.currency_to_str(item.price)}\n    </:col>\n  </.table>\n\n  <strong>Total price:</strong>\n  {HelloWeb.CartHTML.currency_to_str(@order.total_price)}\n\n  <.button navigate={~p\"/products\"}>Back to products</.button>\n</Layouts.app>\n```\n\nTo show our completed order, we displayed the order's user, followed by the line item listing with product title, quantity, and the price we \"transacted\" when completing the order, along with the total price.\n\nOur last addition will be to add the \"complete order\" button to our cart page to allow completing an order. Add the following button to the <.header> of the cart show template in `lib/hello_web/controllers/cart_html/show.html.heex`:\n\n```diff\n  <.header>\n    My Cart\n+   <:actions>\n+     <.button href={~p\"/orders\"} method=\"post\">\n+       Complete order\n+     </.button>\n+   </:actions>\n  </.header>\n```\n\nWe added a link with `method=\"post\"` to send a POST request to our `OrderController.create` action. If we head back to our cart page at [`http://localhost:4000/cart`](http://localhost:4000/cart) and complete an order, we'll be greeted by our rendered template:\n\n```text\nThank you for your order!\n\nUser uuid: 08964c7c-908c-4a55-bcd3-9811ad8b0b9d\nTitle                   Quantity Price\nMetaprogramming Elixir  2        $15.00\n\nTotal price: $30.00\n```\n\nWe haven't added payments, but we can already see how our `ShoppingCart` and `Orders` context splitting is driving us towards a maintainable solution. With our cart items separated from our order line items, we are well equipped in the future to add payment transactions, cart price detection, and more.\n\nGreat work!\n"
  },
  {
    "path": "guides/data_modelling/your_first_context.md",
    "content": "# 2. Your First Context\n\nAn ecommerce platform has wide-reaching coupling across a codebase so it's important to think about writing well-defined modules. With that in mind, our goal is to build a product catalog API that handles creating, updating, and deleting the products available in our system. We'll start off with the basic features of showcasing our products, and we will add shopping cart features later. We'll see how starting with a solid foundation with isolated boundaries allows us to grow our application naturally as we add functionality.\n\nPhoenix includes the `mix phx.gen.html`, `mix phx.gen.json`, `mix phx.gen.live`, and `mix phx.gen.context` generators that apply the ideas of isolating functionality in our applications into contexts. These generators are a great way to hit the ground running while Phoenix nudges you in the right direction to grow your application. Let's put these tools to use for our new product catalog context.\n\nWhen we run the generators, the context name is optional, and Phoenix will automatically use the plural name as the context module. This allows us to keep moving forward when starting out or when it is not yet clear how the different parts of our system relate to each other. Luckily, the needs of ecommerce systems are well defined nowadays, so it provides an excellent ground for us to design with intent. So let's take a step back and think about the different parts of our system.\n\nWe know that we'll have products to showcase on pages for sale, along with descriptions, pricing, etc. Along with selling products, we know we'll need to support carting, order checkout, and so on. While the products being purchased are related to the cart and checkout processes, showcasing a product and managing the *exhibition* of our products is distinctly different than tracking what a user has placed in their cart or how an order is placed. A `Catalog` context is a natural place for the management of our product details and the showcasing of those products we have for sale.\n\n## Starting with generators\n\nTo jump-start our catalog context, we'll use `mix phx.gen.html` which creates a context module that wraps up Ecto access for creating, updating, and deleting products, along with web files like controllers and templates for the web interface into our context. Run the following command at your project root:\n\n```console\n$ mix phx.gen.html Catalog Product products title:string \\\ndescription:string price:decimal views:integer\n```\nAfter executing the command, you should see output similar to the following:\n\n```console\n* creating lib/hello_web/controllers/product_controller.ex\n* creating lib/hello_web/controllers/product_html/edit.html.heex\n* creating lib/hello_web/controllers/product_html/index.html.heex\n* creating lib/hello_web/controllers/product_html/new.html.heex\n* creating lib/hello_web/controllers/product_html/show.html.heex\n* creating lib/hello_web/controllers/product_html/product_form.html.heex\n* creating lib/hello_web/controllers/product_html.ex\n* creating test/hello_web/controllers/product_controller_test.exs\n* creating lib/hello/catalog/product.ex\n* creating priv/repo/migrations/20250201185747_create_products.exs\n* creating lib/hello/catalog.ex\n* injecting lib/hello/catalog.ex\n* creating test/hello/catalog_test.exs\n* injecting test/hello/catalog_test.exs\n* creating test/support/fixtures/catalog_fixtures.ex\n* injecting test/support/fixtures/catalog_fixtures.ex\n\nAdd the resource to your browser scope in lib/hello_web/router.ex:\n\n    resources \"/products\", ProductController\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n```\n\nPhoenix generated the web files as expected in `lib/hello_web/`. We can also see our context functions were generated inside a `lib/hello/catalog.ex` file and our product schema file is placed in the directory of the same name. Note the difference between `lib/hello` and `lib/hello_web`. We have a `Catalog` module to serve as the public API for product catalog functionality, as well as a `Catalog.Product` struct, which is an Ecto schema for casting and validating product data. Phoenix also provided web and context tests for us, it also included test helpers for creating entities via the `Hello.Catalog` context, which we'll look at later. For now, let's follow the instructions and add the route according to the console instructions, in `lib/hello_web/router.ex`:\n\n```diff\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :index\n+   resources \"/products\", ProductController\n  end\n```\n\nWith the new route in place, Phoenix reminds us to update our repo by running `mix ecto.migrate`, but first we need to make a few tweaks to the generated migration in `priv/repo/migrations/*_create_products.exs`:\n\n```diff\n  def change do\n    create table(:products) do\n      add :title, :string\n      add :description, :string\n-     add :price, :decimal\n+     add :price, :decimal, precision: 15, scale: 6, null: false\n-     add :views, :integer\n+     add :views, :integer, default: 0, null: false\n\n      timestamps(type: :utc_datetime)\n    end\n```\n\nWe modified our price column to a specific precision of 15, scale of 6, along with a not-null constraint. This ensures we store currency with proper precision for any mathematical operations we may perform. Next, we added a default value and not-null constraint to our views count. With our changes in place, we're ready to migrate up our database. Let's do that now:\n\n```console\n$ mix ecto.migrate\n14:09:02.260 [info] == Running 20250201185747 Hello.Repo.Migrations.CreateProducts.change/0 forward\n\n14:09:02.262 [info] create table products\n\n14:09:02.273 [info] == Migrated 20250201185747 in 0.0s\n```\n\nBefore we jump into the generated code, let's start the server with `mix phx.server` and visit [http://localhost:4000/products](http://localhost:4000/products). Let's follow the \"New Product\" link and click the \"Save\" button without providing any input. When we submit the form, we can see all the validation errors inline with the inputs. Nice! Out of the box, the context generator included the schema fields in our form template and we can see our default validations for required inputs are in effect. Let's enter some example product data and resubmit the form:\n\n```text\nProduct created successfully.\n\nTitle: Metaprogramming Elixir\nDescription: Write Less Code, Get More Done (and Have Fun!)\nPrice: 15.000000\nViews: 0\n```\n\nIf we follow the \"Back\" link, we get a list of all products, which should contain the one we just created. Likewise, we can update this record or delete it. Now that we've seen how it works in the browser, it's time to take a look at the generated code.\n\n> #### Naming things is hard {: .tip}\n>\n> When starting a web application, it may be hard to draw lines or name its different contexts, especially when the business domain you are working with is not as well established as ecommerce.\n>\n> For those reasons, Phoenix generators allow you to skip the context name, which is really helpful when you're stuck or still exploring your business domain. For example, our code above would work the same if we used the default `Products` context for managing products and it would still allow us to organically discover other resources that belong to the `Products` context, such as categories or image galleries.\n>\n> We also advise against being too smart when naming your contexts. Pick a name that is clear and obvious to everyone who works (and might work) in the project. As your application grows and the different parts of your system become clear, you can simply rename the context or move resources around. The beauty of Elixir modules is moving them around should be simply a matter of renaming the module names and their callers (and renaming the files for consistency).\n\n## Grokking generated code\n\nThat little `mix phx.gen.html` command packed a surprising punch. We got a lot of functionality out-of-the-box for creating, updating, and deleting products in our catalog. This is far from a full-featured app, but remember, generators are first and foremost learning tools and a starting point for you to begin building real features. Code generation can't solve all your problems, but it will teach you the ins and outs of Phoenix and nudge you towards the proper mindset when designing your application.\n\nLet's first check out the `ProductController` that was generated in `lib/hello_web/controllers/product_controller.ex`:\n\n```elixir\ndefmodule HelloWeb.ProductController do\n  use HelloWeb, :controller\n\n  alias Hello.Catalog\n  alias Hello.Catalog.Product\n\n  def index(conn, _params) do\n    products = Catalog.list_products()\n    render(conn, :index, products: products)\n  end\n\n  def new(conn, _params) do\n    changeset = Catalog.change_product(%Product{})\n    render(conn, :new, changeset: changeset)\n  end\n\n  def create(conn, %{\"product\" => product_params}) do\n    case Catalog.create_product(product_params) do\n      {:ok, product} ->\n        conn\n        |> put_flash(:info, \"Product created successfully.\")\n        |> redirect(to: ~p\"/products/#{product}\")\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, :new, changeset: changeset)\n    end\n  end\n\n  def show(conn, %{\"id\" => id}) do\n    product = Catalog.get_product!(id)\n    render(conn, :show, product: product)\n  end\n  ...\nend\n```\n\nWe've seen how controllers work in our [controller guide](controllers.html), so the code probably isn't too surprising. What is worth noticing is how our controller calls into the `Catalog` context. We can see that the `index` action fetches a list of products with `Catalog.list_products/0`, and how products are persisted in the `create` action with `Catalog.create_product/1`. We haven't yet looked at the catalog context, so we don't yet know how product fetching and creation is happening under the hood – *but that's the point*. Our Phoenix controller is the web interface into our greater application. It shouldn't be concerned with the details of how products are fetched from the database or persisted into storage. We only care about telling our application to perform some work for us. This is great because our business logic and storage details are decoupled from the web layer of our application. If we move to a full-text storage engine later for fetching products instead of a SQL query, our controller doesn't need to be changed. Likewise, we can reuse our context code from any other interface in our application, be it a channel, mix task, or long-running process importing CSV data.\n\nIn the case of our `create` action, when we successfully create a product, we use `Phoenix.Controller.put_flash/3` to show a success message, and then we redirect to the router's product show page. Conversely, if `Catalog.create_product/1` fails, we render our `\"new.html\"` template and pass along the Ecto changeset for the template to lift error messages from.\n\nNext, let's dig deeper and check out our `Catalog` context in `lib/hello/catalog.ex`:\n\n```elixir\ndefmodule Hello.Catalog do\n  @moduledoc \"\"\"\n  The Catalog context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias Hello.Repo\n\n  alias Hello.Catalog.Product\n\n  @doc \"\"\"\n  Returns the list of products.\n\n  ## Examples\n\n      iex> list_products()\n      [%Product{}, ...]\n\n  \"\"\"\n  def list_products do\n    Repo.all(Product)\n  end\n  ...\nend\n```\n\nThis module will be the public API for all product catalog functionality in our system. For example, in addition to product detail management, we may also handle product category classification and product variants for things like optional sizing, trims, etc. If we look at the `list_products/0` function, we can see the private details of product fetching. And it's super simple. We have a call to `Repo.all(Product)`. We saw how Ecto repo queries worked in the [Ecto guide](ecto.html), so this call should look familiar. Our `list_products` function is a generalized function name specifying the *intent* of our code – namely to list products. The details of that intent where we use our Repo to fetch the products from our PostgreSQL database, are hidden from our callers. This is a common theme we'll see reiterated as we use the Phoenix generators. Phoenix will push us to think about where we have different responsibilities in our application, and then to wrap up those different areas behind well-named modules and functions that make the intent of our code clear, while encapsulating the details.\n\nNow we know how data is fetched, but how are products persisted? Let's take a look at the `Catalog.create_product/1` function:\n\n```elixir\n  @doc \"\"\"\n  Creates a product.\n\n  ## Examples\n\n      iex> create_product(%{field: value})\n      {:ok, %Product{}}\n\n      iex> create_product(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_product(attrs) do\n    %Product{}\n    |> Product.changeset(attrs)\n    |> Repo.insert()\n  end\n```\n\nThere's more documentation than code here, but a couple of things are important to highlight. First, we can see again that our Ecto Repo is used under the hood for database access. You probably also noticed the call to `Product.changeset/2`. We talked about changesets before, and now we see them in action in our context.\n\nIf we open up the `Product` schema in `lib/hello/catalog/product.ex`, it will look immediately familiar:\n\n```elixir\ndefmodule Hello.Catalog.Product do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"products\" do\n    field :description, :string\n    field :price, :decimal\n    field :title, :string\n    field :views, :integer\n\n    timestamps(type: :utc_datetime)\n  end\n\n  @doc false\n  def changeset(product, attrs) do\n    product\n    |> cast(attrs, [:title, :description, :price, :views])\n    |> validate_required([:title, :description, :price, :views])\n  end\nend\n```\n\nThis is just what we saw before when we ran `mix phx.gen.schema`, except here we see a `@doc false` above our `changeset/2` function. This tells us that while this function is publicly callable, it's not part of the public context API. Callers that build changesets do so via the context API. For example, `Catalog.create_product/1` calls into our `Product.changeset/2` to build the changeset from user input. Callers, such as our controller actions, do not access `Product.changeset/2` directly. All interaction with our product changesets is done through the public `Catalog` context.\n\n## Adding Catalog functions\n\nAs we've seen, your context modules are dedicated modules that expose and group related functionality. Phoenix generates generic functions, such as `list_products` and `update_product`, but they only serve as a basis for you to grow your business logic and application from. Let's add one of the basic features of our catalog by tracking product page view count.\n\nFor any ecommerce system, the ability to track how many times a product page has been viewed is essential for marketing, suggestions, ranking, etc. While we could try to use the existing `Catalog.update_product` function, along the lines of `Catalog.update_product(product, %{views: product.views + 1})`, this would not only be prone to race conditions, but it would also require the caller to know too much about our Catalog system. To see why the race condition exists, let's walk through the possible execution of events:\n\nIntuitively, you would assume the following events:\n\n  1. User 1 loads the product page with count of 13\n  2. User 1 saves the product page with count of 14\n  3. User 2 loads the product page with count of 14\n  4. User 2 saves the product page with count of 15\n\nWhile in practice this would happen:\n\n  1. User 1 loads the product page with count of 13\n  2. User 2 loads the product page with count of 13\n  3. User 1 saves the product page with count of 14\n  4. User 2 saves the product page with count of 14\n\nThe race conditions would make this an unreliable way to update the existing table since multiple callers may be updating out of date view values. There's a better way.\n\nLet's think of a function that describes what we want to accomplish. Here's how we would like to use it:\n\n```elixir\nproduct = Catalog.inc_page_views(product)\n```\n\nThat looks great. Our callers will have no confusion over what this function does, and we can wrap up the increment in an atomic operation to prevent race conditions.\n\nOpen up your catalog context (`lib/hello/catalog.ex`), and add this new function:\n\n```elixir\n  def inc_page_views(%Product{} = product) do\n    {1, [%Product{views: views}]} =\n      from(p in Product, where: p.id == ^product.id, select: [:views])\n      |> Repo.update_all(inc: [views: 1])\n\n    put_in(product.views, views)\n  end\n```\n\nWe built a query for fetching the current product given its ID which we pass to `Repo.update_all`. Ecto's `Repo.update_all` allows us to perform batch updates against the database, and is perfect for atomically updating values, such as incrementing our views count. The result of the repo operation returns the number of updated records, along with the selected schema values specified by the `select` option. When we receive the new product views, we use `put_in(product.views, views)` to place the new view count within the product struct.\n\nWith our context function in place, let's make use of it in our product controller. Update your `show` action in `lib/hello_web/controllers/product_controller.ex` to call our new function:\n\n```elixir\n  def show(conn, %{\"id\" => id}) do\n    product =\n      id\n      |> Catalog.get_product!()\n      |> Catalog.inc_page_views()\n\n    render(conn, :show, product: product)\n  end\n```\n\nWe modified our `show` action to pipe our fetched product into `Catalog.inc_page_views/1`, which will return the updated product. Then we rendered our template just as before. Let's try it out. Refresh one of your product pages a few times and watch the view count increase.\n\nWe can also see our atomic update in action in the ecto debug logs:\n\n```text\n[debug] QUERY OK source=\"products\" db=0.5ms idle=834.5ms\nUPDATE \"products\" AS p0 SET \"views\" = p0.\"views\" + $1 WHERE (p0.\"id\" = $2) RETURNING p0.\"views\" [1, 1]\n```\n\nGood work!\n\nAs we've seen, designing with contexts gives you a solid foundation to grow your application from. Using discrete, well-defined APIs that expose the intent of your system allows you to write more maintainable applications with reusable code. Now that we know how to start extending our context API, let's explore handling relationships within a context.\n"
  },
  {
    "path": "guides/deployment/deployment.md",
    "content": "# Introduction to Deployment\n\nOnce we have a working application, we're ready to deploy it. If you're not quite finished with your own application, don't worry. Just follow the [Up and Running Guide](up_and_running.html) to create a basic application to work with.\n\nWhen preparing an application for deployment, there are three main steps:\n\n  * Handling of your application secrets\n  * Compiling your application assets\n  * Starting your server in production\n\nIn this guide, we will learn how to get the production environment running locally. You can use the same techniques in this guide to run your application in production, but depending on your deployment infrastructure, extra steps will be necessary.\n\nAs an example of deploying to other infrastructures, we also discuss four different approaches in our guides: using [Elixir's releases](releases.html) with `mix release`, [using Gigalixir](gigalixir.html), [using Fly](fly.html), and [using Heroku](heroku.html). We've also included links to deploying Phoenix on other platforms under [Community Deployment Guides](#community-deployment-guides). Finally, the release guide has a sample Dockerfile you can use if you prefer to deploy with container technologies.\n\nLet's explore those steps above one by one.\n\n## Handling of your application secrets\n\nAll Phoenix applications have data that must be kept secure, for example, the username and password for your production database, and the secret Phoenix uses to sign and encrypt important information. The general recommendation is to keep those in environment variables and load them into your application. This is done in `config/runtime.exs` (formerly `config/prod.secret.exs` or `config/releases.exs`), which is responsible for loading secrets and configuration from environment variables at boot time.\n\nTherefore, you need to make sure the proper relevant variables are set in production:\n\n```console\n$ mix phx.gen.secret\nREALLY_LONG_SECRET\n$ export SECRET_KEY_BASE=REALLY_LONG_SECRET\n$ export DATABASE_URL=ecto://USER:PASS@HOST/database\n```\n\nDo not copy those values directly, set `SECRET_KEY_BASE` according to the result of `mix phx.gen.secret` and `DATABASE_URL` according to your database address.\n\nIf for some reason you do not want to rely on environment variables, you can hard code the secrets in your `config/runtime.exs` but make sure not to check the file into your version control system.\n\nWith your secret information properly secured, it is time to configure assets!\n\nBefore taking this step, we need to do one bit of preparation. Since we will be readying everything for production, we need to do some setup in that environment by getting our dependencies and compiling.\n\n```console\n$ mix deps.get --only prod\n$ MIX_ENV=prod mix compile\n```\n\n## Compiling your application assets\n\nThis step is required only if you have compilable assets like JavaScript and stylesheets. By default, Phoenix uses `esbuild` but everything is encapsulated in a single `mix assets.deploy` task defined in your `mix.exs`:\n\n```console\n$ MIX_ENV=prod mix assets.deploy\nCheck your digested files at \"priv/static\".\n```\n\nAnd that is it! The Mix task by default builds the assets and then generates digests with a cache manifest file so Phoenix can quickly serve assets in production.\n\n> Note: if you run the task above in your local machine, it will generate many digested assets in `priv/static`. You can prune them by running `mix phx.digest.clean --all`.\n\nKeep in mind that, if you by any chance forget to run the steps above, Phoenix will show an error message:\n\n```console\n$ PORT=4001 MIX_ENV=prod mix phx.server\n10:50:18.732 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com\n10:50:18.735 [error] Could not find static manifest at \"my_app/_build/prod/lib/foo/priv/static/cache_manifest.json\". Run \"mix phx.digest\" after building your static files or remove the configuration from \"config/prod.exs\".\n```\n\nThe error message is quite clear: it says Phoenix could not find a static manifest. Just run the commands above to fix it or, if you are not serving or don't care about assets at all, you can just remove the `cache_static_manifest` configuration from your config.\n\n## Starting your server in production\n\nTo run Phoenix in production, we need to set the `PORT` and `MIX_ENV` environment variables when invoking `mix phx.server`:\n\n```console\n$ PORT=4001 MIX_ENV=prod mix phx.server\n10:59:19.136 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com\n```\n\nTo run in detached mode so that the Phoenix server does not stop and continues to run even if you close the terminal:\n\n```console\n$ PORT=4001 MIX_ENV=prod elixir --erl \"-detached\" -S mix phx.server\n```\n\nIn case you get an error message, please read it carefully, and open up a bug report if it is still not clear how to address it.\n\nYou can also run your application inside an interactive shell:\n\n```console\n$ PORT=4001 MIX_ENV=prod iex -S mix phx.server\n10:59:19.136 [info] Running MyAppWeb.Endpoint with Cowboy on http://example.com\n```\n\n## Putting it all together\n\nThe previous sections give an overview about the main steps required to deploy your Phoenix application. In practice, you will end-up adding steps of your own as well. For example, if you are using a database, you will also want to run `mix ecto.migrate` before starting the server to ensure your database is up to date.\n\nOverall, here is a script you can use as a starting point:\n\n```console\n# Initial setup\n$ mix deps.get --only prod\n$ MIX_ENV=prod mix compile\n\n# Compile assets\n$ MIX_ENV=prod mix assets.deploy\n\n# Custom tasks (like DB migrations)\n$ MIX_ENV=prod mix ecto.migrate\n\n# Finally run the server\n$ PORT=4001 MIX_ENV=prod mix phx.server\n```\n\nAnd that's it. Next, you can use one of our official guides to deploy:\n\n  * [with Elixir's releases](releases.html)\n  * [to Gigalixir](gigalixir.html), an Elixir-centric Platform as a Service (PaaS)\n  * [to Fly.io](fly.html), a PaaS that deploys your servers close to your users with built-in distribution support\n  * and [to Heroku](heroku.html), one of the most popular PaaS.\n\n## Clustering and Long-Polling Transports\n\nPhoenix supports two types of transports for its Socket implementation: WebSocket, and Long-Polling. When generating a Phoenix project, you can see the default configuration set in the generated `endpoint.ex` file:\n\n```elixir\nsocket \"/live\", Phoenix.LiveView.Socket,\n  websocket: [connect_info: [session: @session_options]],\n  longpoll: [connect_info: [session: @session_options]]\n```\n\nThis configuration tells Phoenix that both the WebSocket and the Long-Polling options are available, and based on the client's network conditions, Phoenix will first attempt to connect to the WebSocket, falling back to the Long-Poll option after the configured timeout found in the generated `app.js` file:\n\n```javascript\nlet liveSocket = new LiveSocket(\"/live\", Socket, {\n  longPollFallbackMs: 2500,\n  params: {_csrf_token: csrfToken}\n})\n```\n\nIf you are running more than one machine in production, which is the recommended approach in most cases, this automatic fallback comes with an important caveat. If you want Long-Polling to work properly, your application must either:\n\n1. Utilize the Erlang VM's clustering capabilities, so the default `Phoenix.PubSub` adapter can broadcast messages across nodes\n\n2. Choose a different `Phoenix.PubSub` adapter (such as `Phoenix.PubSub.Redis`)\n\n3. Or your deployment option must implement sticky sessions - ensuring that all requests for a specific session go to the same machine\n\nThe reason for this is simple. While a WebSocket is a long-lived open connection to the same machine, long-polling works by opening a request to the server, waiting for a timeout or until the open request is fulfilled, and repeating this process. In order to preserve the state of the user's connected socket and to preserve the behaviour of a socket being long-lived, the user's process is kept alive, and each long-poll request attempts to find the user's stateful process. If the stateful process is not reachable, every request will create a new process and a new state, thereby breaking the fact that the socket is long-lived and stateful.\n\n## Community Deployment Guides\n\n  * [Render](https://render.com) has first class support for Phoenix applications. There are guides for hosting Phoenix with [Mix releases](https://render.com/docs/deploy-phoenix) and as a [Distributed Elixir Cluster](https://render.com/docs/deploy-elixir-cluster).\n  * [Railway](https://railway.com) also provides support for Phoenix applications using [Mix releases](https://docs.railway.com/guides/phoenix).\n"
  },
  {
    "path": "guides/deployment/fly.md",
    "content": "# Deploying on Fly.io\n\nThe main goal for this guide is to get a Phoenix application running on [Fly.io](https://fly.io).\n\nFly.io maintains their own guide for Elixir/Phoenix here: [Fly.io/docs/elixir/getting-started/](https://fly.io/docs/elixir/getting-started/). We will keep this guide up but for the latest and greatest check with them!\n\n## What we'll need\n\nThe only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](https://hexdocs.pm/phoenix/up_and_running.html).\n\nYou can just:\n\n```console\n$ mix phx.new my_app\n```\n\n## Sections\n\nLet's separate this process into a few steps, so we can keep track of where we are.\n\n- Install the Fly.io CLI\n- Sign up for Fly.io\n- Deploy the app to Fly.io\n- Clustering your application\n- Extra Fly.io tips\n- Helpful Fly.io resources\n\n## Installing the Fly.io CLI\n\nFollow the instructions [here](https://fly.io/docs/getting-started/installing-flyctl/) to install Flyctl, the command-line interface for the Fly.io platform.\n\n## Sign up for Fly.io\n\nWe can [sign up for an account](https://fly.io/docs/getting-started/log-in-to-fly/) using the CLI.\n\n```console\n$ fly auth signup\n```\n\nOr sign in.\n\n```console\n$ fly auth login\n```\n\nFly has a [free tier](https://fly.io/docs/about/pricing/) for most applications. A credit card is required when setting up an account to help prevent abuse. See the [pricing](https://fly.io/docs/about/pricing/) page for more details.\n\n## Deploy the app to Fly.io\n\nTo tell Fly about your application, run `fly launch` in the directory with your source code. This creates and configures a Fly.io app.\n\n```console\n$ fly launch\n```\n\nThis scans your source, detects the Phoenix project, and runs `mix phx.gen.release --docker` for you! This creates a Dockerfile for you.\n\nThe `fly launch` command walks you through a few questions.\n\n- You can name the app or have it generate a random name for you.\n- Choose an organization (defaults to `personal`). Organizations are a way of sharing applications and resources between Fly.io users.\n- Choose a region to deploy to. Defaults to the nearest Fly.io region. You can check out the [complete list of regions here](https://fly.io/docs/reference/regions/).\n- Sets up a Postgres DB for you.\n- Builds the Dockerfile.\n- Deploys your application!\n\nThe `fly launch` command also created a `fly.toml` file for you. This is where you can set ENV values and other config.\n\n### Storing secrets on Fly.io\n\nYou may also have some secrets you'd like to set on your app.\n\nUse [`fly secrets`](https://fly.io/docs/reference/secrets/#setting-secrets) to configure those.\n\n```console\n$ fly secrets set MY_SECRET_KEY=my_secret_value\n```\n\n### Deploying again\n\nWhen you want to deploy changes to your application, use `fly deploy`.\n\n```console\n$ fly deploy\n```\n\nNote: On Apple Silicon (M1) computers, docker runs cross-platform builds using qemu which might not always work. If you get a segmentation fault error like the following:\n\n```console\n => [build  7/17] RUN mix deps.get --only\n => => # qemu: uncaught target signal 11 (Segmentation fault) - core dumped\n```\n\nYou can use fly's remote builder by adding the `--remote-only` flag:\n\n```console\n$ fly deploy --remote-only\n```\n\nYou can always check on the status of a deploy\n\n```console\n$ fly status\n```\n\nCheck your app logs\n\n```console\n$ fly logs\n```\n\nIf everything looks good, open your app on Fly\n\n```console\n$ fly open\n```\n\n## Clustering your application\n\nElixir and the Erlang VM have the incredible ability to be clustered together and pass messages seamlessly between nodes. Phoenix comes with all of the knobs in place, you only need to set the appropriate environment variables before deploying.\n\nIf you used `fly launch` to deploy your app, those environment variables are already in place, if not, open up `rel/env.ssh.eex` and add:\n\n```sh\nexport ERL_AFLAGS=\"-proto_dist inet6_tcp\"\nexport RELEASE_DISTRIBUTION=\"name\"\nexport RELEASE_NODE=\"${FLY_APP_NAME}-${FLY_IMAGE_REF##*-}@${FLY_PRIVATE_IP}\"\n\nexport ECTO_IPV6=\"true\"\nexport DNS_CLUSTER_QUERY=\"${FLY_APP_NAME}.internal\"\n```\n\nThe first three environment variables are managed by Elixir and the Erlang/VM:\n\n  * `ERL_AFLAGS` - configures Erlang to use IPv6 for its distribution\n  * `RELEASE_DISTRIBUTION` - configures Erlang to named nodes\n  * `RELEASE_NODE` - attaches a name to the node, using Fly's app name and deploy reference\n\nThe last two are handled by your `config/runtime.exs`:\n\n  * `ECTO_IPV6` - connect to the database using IPv6\n  * `DNS_CLUSTER_QUERY` - configures your app to find other nodes using the given DNS query\n\n## Extra Fly.io tips\n\n### Getting an IEx shell into a running node\n\nElixir supports getting a IEx shell into a running production node.\n\nThere are a couple prerequisites, we first need to establish an [SSH Shell](https://fly.io/docs/flyctl/ssh/) to our machine on Fly.io.\n\nThis step sets up a root certificate for your account and then issues a certificate.\n\n```console\n$ fly ssh issue --agent\n```\n\nWith SSH configured, let's open a console.\n\n```console\n$ fly ssh console\nConnecting to my-app-1234.internal... complete\n/ #\n```\n\nIf all has gone smoothly, then you have a shell into the machine! Now we just need to launch our remote IEx shell. The deployment Dockerfile was configured to pull our application into `/app`. So the command for an app named `my_app` looks like this:\n\n```console\n$ app/bin/my_app remote\nErlang/OTP 23 [erts-11.2.1] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1]\n\nInteractive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help)\niex(my_app@fdaa:0:1da8:a7b:ac4:b204:7e29:2)1>\n```\n\nNow we have a running IEx shell into our node! You can safely disconnect using CTRL+C, CTRL+C.\n\n#### Running multiple instances\n\nThere are two ways to run multiple instances.\n\n1. Scale our application to have multiple instances in one region.\n2. Add an instance to another region (multiple regions).\n\nLet's first start with a baseline of our single deployment.\n\n```console\n$ fly status\n...\nInstances\nID       VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED\nf9014bf7 26      sea    run     running 1 total, 1 passing 0        1h8m ago\n```\n\n#### Scaling in a single region\n\nLet's scale up to 2 instances in our current region.\n\n```console\n$ fly scale count 2\nCount changed to 2\n```\n\nChecking the status, we can see what happened.\n\n```console\n$ fly status\n...\nInstances\nID       VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED\neb4119d3 27      sea    run     running 1 total, 1 passing 0        39s ago\nf9014bf7 27      sea    run     running 1 total, 1 passing 0        1h13m ago\n```\n\nWe now have two instances in the same region.\n\nLet's make sure they are clustered together. From an IEx shell, we can ask the node we're connected to, what other nodes it can see.\n\n```console\n$ fly ssh console -C \"/app/bin/my_app remote\"\n```\n\n```elixir\niex(my-app-1234@fdaa:0:1da8:a7b:ac2:f901:4bf7:2)1> Node.list\n[:\"my-app-1234@fdaa:0:1da8:a7b:ac4:eb41:19d3:2\"]\n```\n\nThe IEx prompt is included to help show the IP address of the node we are connected to. Then getting the `Node.list` returns the other node. Our two instances are connected and clustered!\n\n#### Scaling to multiple regions\n\nFly makes it easy to deploy instances closer to your users. Through the magic of DNS, users are directed to the nearest region where your application is located. You can read more about [Fly.io regions here](https://fly.io/docs/reference/regions/).\n\nStarting back from our baseline of a single instance running in `sea` which is Seattle, Washington (US), let's add the region `ewr` which is Parsippany, NJ (US). This puts an instance on both coasts of the US.\n\n```console\n$ fly regions add ewr\nRegion Pool:\newr\nsea\nBackup Region:\niad\nlax\nsjc\nvin\n```\n\nLooking at the status shows that we're only in 1 region because our count is set to 1.\n\n```console\n$ fly status\n...\nInstances\nID       VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED\ncdf6c422 29      sea    run     running 1 total, 1 passing 0        58s ago\n```\n\nLet's add a 2nd instance and see it deploy to `ewr`.\n\n```console\n$ fly scale count 2\nCount changed to 2\n```\n\nNow the status shows we have two instances spread across 2 regions!\n\n```console\n$ fly status\n...\nInstances\nID       VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED\n0a8e6666 30      ewr    run     running 1 total, 1 passing 0        16s ago\ncdf6c422 30      sea    run     running 1 total, 1 passing 0        6m47s ago\n```\n\nLet's ensure they are clustered together.\n\n```console\n$ fly ssh console -C \"/app/bin/my_app remote\"\n```\n\n```elixir\niex(my-app-1234@fdaa:0:1da8:a7b:ac2:cdf6:c422:2)1> Node.list\n[:\"my-app-1234@fdaa:0:1da8:a7b:ab2:a8e:6666:2\"]\n```\n\nWe have two instances of our application deployed to the West and East coasts of the North American continent and they are clustered together! Our users will automatically be directed to the server nearest them.\n\nThe Fly.io platform has built-in distribution support making it easy to cluster distributed Elixir nodes in multiple regions.\n\n## Helpful Fly.io resources\n\nOpen the Dashboard for your account\n\n```console\n$ fly dashboard\n```\n\nDeploy your application\n\n```console\n$ fly deploy\n```\n\nShow the status of your deployed application\n\n```console\n$ fly status\n```\n\nAccess and tail the logs\n\n```console\n$ fly logs\n```\n\nScaling your application up or down\n\n```console\n$ fly scale count 2\n```\n\nRefer to the [Fly.io Elixir documentation](https://fly.io/docs/getting-started/elixir) for additional information.\n\n[The Fly.io docs](https://fly.io/docs/) covers things like:\n\n* [Status](https://fly.io/docs/flyctl/status/) and [logs](https://fly.io/docs/monitoring/logging-overview/)\n* [Custom domains](https://fly.io/docs/networking/custom-domain/)\n* [Certificates](https://fly.io/docs/networking/custom-domain-api/)\n\n## Troubleshooting\n\nSee [Troubleshooting](https://fly.io/docs/getting-started/troubleshooting/) and [Elixir Troubleshooting](https://fly.io/docs/elixir/the-basics/troubleshooting/)\n\nVisit the [Fly.io Community](https://community.fly.io/) to find solutions and ask questions.\n"
  },
  {
    "path": "guides/deployment/gigalixir.md",
    "content": "# Deploying on Gigalixir\n\nOur main goal for this guide is to get a Phoenix application running on Gigalixir.\n\n## What we'll need\n\nThe only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](https://hexdocs.pm/phoenix/up_and_running.html).\n\n## Steps\n\nLet's separate this process into a few steps, so we can keep track of where we are.\n\n- Initialize Git repository\n- Install the Gigalixir CLI\n- Sign up for Gigalixir\n- Create and set up Gigalixir application\n- Provision a database\n- Make our project ready for Gigalixir\n- Deploy time!\n- Useful Gigalixir commands\n\n## Initializing Git repository\n\nIf you haven't already, we'll need to commit our files to git. We can do so by running the following commands in our project directory:\n\n```console\n$ git init\n$ git add .\n$ git commit -m \"Initial commit\"\n```\n\n## Installing the Gigalixir CLI\n\nFollow the instructions [here](https://gigalixir.com/docs/getting-started-guide/) to install the command-line interface for your platform.\n\n## Signing up for Gigalixir\n\nWe can sign up for an account at [gigalixir.com](https://gigalixir.com) or with the CLI. Let's use the CLI.\n\n```console\n$ gigalixir signup\n\n# or with a Google account\n$ gigalixir signup:google\n```\n\nGigalixir’s free tier does not require a credit card and comes with 1 app instance and 1 PostgreSQL database for free, but please consider upgrading to a paid plan if you are running a production application.\n\nNext, let's login\n\n```console\n$ gigalixir login\n\n# or with a Google account\n$ gigalixir login:google\n```\n\nAnd verify\n\n```console\n$ gigalixir account\n```\n\n## Creating and setting up our Gigalixir application\n\nThere are two different ways to deploy a Phoenix app on Gigalixir: with mix or with Elixir's releases. In this guide, we'll be using Elixir's releases because it is the recommended way. For more information, see [Elixir Releases vs Mix](https://gigalixir.com/docs/modify-app/#elixir-releases-vs-mix). If you want to deploy with the mix method, follow the [Phoenix deploy with Mix Guide](https://gigalixir.com/docs/getting-started-guide/phoenix-mix-deploy).\n\n### Creating a Gigalixir application\n\nLet's create a Gigalixir application\n\n```console\n$ gigalixir create -n \"your-app-name\"\n```\n\nNote: the app name cannot be changed afterwards. A random name is used if you do not provide one.\n\n### Specifying versions\n\nGigalixir requires that you specify the Erlang and Elixir versions you intend to use. It's generally a good idea to run the same version in production as you do in development. For example:\n\n```console\n$ echo 'elixir_version=1.17.2' > elixir_buildpack.config\n$ echo 'erlang_version=27.0' >> elixir_buildpack.config\n$ git add elixir_buildpack.config\n```\n\nGigalixir will use the latest nodejs version if you do not specify a version. If you want to specify your nodejs version, you can do so like this:\n\n```console\n$ echo 'node_version=22.7.0' > phoenix_static_buildpack.config\n$ git add elixir_buildpack.config phoenix_static_buildpack.config assets/package.json\n```\n\nFinally, don't forget to commit:\n\n```console\n$ git commit -m \"Set versions\"\n```\n\n## Provisioning a database\n\nLet's provision a database for our app. For a free database, run the following command\n\n```console\n$ gigalixir pg:create --free\n```\n\nFor a production ready database, be sure to upgrade your account to the Standard Tier and create a Standard tier database\n```console\n$ gigalixir account:upgrade\n$ gigalixir pg:create\n```\n\nVerify the database was created\n\n```console\n$ gigalixir pg\n```\n\nVerify that a `DATABASE_URL` and `POOL_SIZE` were created\n\n```console\n$ gigalixir config\n```\n\n## Making our Project ready for Gigalixir\n\nThere's nothing we need to do to get our app running on Gigalixir, but for a production app, you probably want to enforce SSL.\n\n### Database Connection Security\n\nYou may also want to use SSL for your database connection. In your `config/runtime.exs`:\n\n```elixir\nssl: [\n  verify: :verify_peer,\n  cacerts: :public_key.cacerts_get()\n]\n```\n\n## Deploy Time!\n\nOur project is now ready to be deployed on Gigalixir.\nBe sure you have everything committed to git and run the following command:\n\n```console\n$ git push gigalixir\n```\n\nCheck the status of your deploy and wait until the app is `Healthy`\n\n```console\n$ gigalixir ps\n```\n\nRun migrations\n\n```console\n$ gigalixir ps:migrate\n```\n\nCheck your app logs\n\n```console\n$ gigalixir logs\n```\n\nIf everything looks good, let's take a look at your app running on Gigalixir\n\n```console\n$ gigalixir open\n```\n\n## Useful Gigalixir Commands\n\nOpen a remote console\n\n```console\n$ gigalixir account:ssh_keys:add \"$(cat ~/.ssh/id_rsa.pub)\"\n$ gigalixir ps:remote_console\n```\n\nTo set up clustering, see [Clustering Nodes](https://gigalixir.com/docs/cluster)\n\nFor custom domains, scaling, jobs and other features, see the [Gigalixir Documentation](https://gigalixir.com/docs/).\n\n## Troubleshooting\n\nSee [Troubleshooting](https://gigalixir.com/docs/troubleshooting) and the [FAQ](https://gigalixir.com/docs/faq)\n\nAlso, don't hesitate to email [help@gigalixir.com](mailto:help@gigalixir.com) or [request an invitation](https://elixir-lang.slack.com/join/shared_invite/zt-1f13hz7mb-N4KGjF523ONLCcHfb8jYgA#/shared-invite/email) and join the #gigalixir channel on [Slack](https://elixir-lang.slack.com).\n"
  },
  {
    "path": "guides/deployment/heroku.md",
    "content": "# Deploying on Heroku\n\nOur main goal for this guide is to get a Phoenix application running on Heroku.\n\n## What we'll need\n\nThe only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](https://hexdocs.pm/phoenix/up_and_running.html).\n\n## Limitations\n\nHeroku is a great platform and Elixir performs well on it. However, you may run into limitations if you plan to leverage advanced features provided by Elixir and Phoenix, such as:\n\n- Connections are limited.\n  - Heroku [limits the number of simultaneous connections](https://devcenter.heroku.com/articles/http-routing#request-concurrency) as well as the [duration of each connection](https://devcenter.heroku.com/articles/limits#http-timeouts). It is common to use Elixir for real-time apps which need lots of concurrent, persistent connections, and Phoenix is capable of [handling over 2 million connections on a single server](https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections).\n\n- Distributed clustering is not possible.\n  - Heroku [firewalls dynos off from one another](https://devcenter.heroku.com/articles/dynos#networking). This means things like [distributed Phoenix channels](https://dockyard.com/blog/2016/01/28/running-elixir-and-phoenix-projects-on-a-cluster-of-nodes) and [distributed tasks](https://hexdocs.pm/elixir/distributed-tasks.html) will need to rely on something like Redis instead of Elixir's built-in distribution.\n\n- In-memory state such as those in [Agents](https://hexdocs.pm/elixir/agents.html), [GenServers](https://hexdocs.pm/elixir/genservers.html), and [ETS](https://hexdocs.pm/elixir/erlang-term-storage.html) will be lost every 24 hours.\n  - Heroku [restarts dynos](https://devcenter.heroku.com/articles/dynos#restarting) every 24 hours regardless of whether the node is healthy.\n\n- [The built-in observer](https://hexdocs.pm/elixir/debugging.html#observer) can't be used with Heroku.\n  - Heroku does allow for connection into your dyno, but you won't be able to use the observer to watch the state of your dyno.\n\nIf you are just getting started, or you don't expect to use the features above, Heroku should be enough for your needs. For instance, if you are migrating an existing application running on Heroku to Phoenix, keeping a similar set of features, Elixir will perform just as well or even better than your current stack.\n\nIf you want a platform-as-a-service without these limitations, there are alternatives listed in the sidebar and also generally available elsewhere. If you would rather deploy to a cloud platform, such as EC2, Google Cloud, etc, consider using `mix release`.\n\n## Steps\n\nLet's separate this process into a few steps, so we can keep track of where we are.\n\n- Initialize Git repository\n- Sign up for Heroku\n- Install the Heroku Toolbelt\n- Create and set up Heroku application\n- Make our project ready for Heroku\n- Deploy time!\n- Useful Heroku commands\n\n## Initializing Git repository\n\n[Git](https://git-scm.com/) is a popular decentralized revision control system and is also used to deploy apps to Heroku.\n\nBefore we can push to Heroku, we'll need to initialize a local Git repository and commit our files to it. We can do so by running the following commands in our project directory:\n\n```console\n$ git init\n$ git add .\n$ git commit -m \"Initial commit\"\n```\n\nHeroku offers some great information on how it is using Git [here](https://devcenter.heroku.com/articles/git#prerequisites-install-git-and-the-heroku-cli).\n\n## Signing up for Heroku\n\nSigning up to Heroku is very simple, just head over to [https://signup.heroku.com/](https://signup.heroku.com/) and fill in the form.\n\nThe Free plan will give us one web [dyno](https://devcenter.heroku.com/articles/dynos) and one worker dyno, as well as a PostgreSQL and Redis instance for free.\n\nThese are meant to be used for testing and development, and come with some limitations. In order to run a production application, please consider upgrading to a paid plan.\n\n## Installing the Heroku Toolbelt\n\nOnce we have signed up, we can download the correct version of the Heroku Toolbelt for our system [here](https://toolbelt.heroku.com/).\n\nThe Heroku CLI, part of the Toolbelt, is useful to create Heroku applications, list currently running dynos for an existing application, tail logs or run one-off commands (mix tasks for instance).\n\n## Create and Set Up Heroku Application\n\nThere are two different ways to deploy a Phoenix app on Heroku. We could use Heroku buildpacks or their container stack. The difference between these two approaches is in how we tell Heroku to treat our build. In buildpack case, we need to update our apps configuration on Heroku to use Phoenix/Elixir specific buildpacks. On container approach, we have more control on how we want to set up our app, and we can define our container image using `Dockerfile` and `heroku.yml`. This section will explore the buildpack approach. In order to use Dockerfile, it is often recommended to convert our app to use releases, which we will describe later on.\n\n### Create Application\n\nA [buildpack](https://devcenter.heroku.com/articles/buildpacks) is a convenient way of packaging framework and/or runtime support. Phoenix requires 2 buildpacks to run on Heroku, the first adds basic Elixir support and the second adds Phoenix specific commands.\n\nWith the Toolbelt installed, let's create the Heroku application. We will do so using the latest available version of the [Elixir buildpack](https://github.com/HashNuke/heroku-buildpack-elixir):\n\n```console\n$ heroku create --buildpack hashnuke/elixir\nCreating app... done, ⬢ mysterious-meadow-6277\nSetting buildpack to hashnuke/elixir... done\nhttps://mysterious-meadow-6277.herokuapp.com/ | https://git.heroku.com/mysterious-meadow-6277.git\n```\n\n> Note: the first time we use a Heroku command, it may prompt us to log in. If this happens, just enter the email and password you specified during signup.\n\n> Note: the name of the Heroku application is the random string after \"Creating\" in the output above (mysterious-meadow-6277). This will be unique, so expect to see a different name from \"mysterious-meadow-6277\".\n\n> Note: the URL in the output is the URL to our application. If we open it in our browser now, we will get the default Heroku welcome page.\n\n> Note: if we hadn't initialized our Git repository before we ran the `heroku create` command, we wouldn't have our Heroku remote repository properly set up at this point. We can set that up manually by running: `heroku git:remote -a [our-app-name].`\n\nThe buildpack uses a predefined Elixir and Erlang version, but to avoid surprises when deploying, it is best to explicitly list the Elixir and Erlang version we want in production to be the same we are using during development or in your continuous integration servers. This is done by creating a config file named `elixir_buildpack.config` in the root directory of your project with your target version of Elixir and Erlang:\n\n```console\n# Elixir version\nelixir_version=1.15.0\n\n# Erlang version\n# https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions\nerlang_version=25.3\n\n# Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild\n# Note we nuke the esbuild executable from the image\nhook_post_compile=\"eval mix assets.deploy && rm -f _build/esbuild*\"\n```\n\nFinally, let's tell the build pack how to start our webserver. Create a file named `Procfile` at the root of your project:\n\n```console\nweb: mix phx.server\n```\n\n### Optional: Node, npm, and the Phoenix Static buildpack\n\nBy default, Phoenix uses `esbuild` and manages all assets for you. However, if you are using `node` and `npm`, you will need to install the [Phoenix Static buildpack](https://github.com/gigalixir/gigalixir-buildpack-phoenix-static) to handle them:\n\n```console\n$ heroku buildpacks:add https://github.com/gigalixir/gigalixir-buildpack-phoenix-static.git\nBuildpack added. Next release on mysterious-meadow-6277 will use:\n  1. https://github.com/HashNuke/heroku-buildpack-elixir.git\n  2. https://github.com/gigalixir/gigalixir-heroku-buildpack-phoenix-static.git\n```\n\nWhen using this buildpack, you want to delegate all asset bundling to `npm`. So you must remove the `hook_post_compile` configuration from your `elixir_buildpack.config` and move it to the deploy script of your `assets/package.json`. Something like this:\n\n```javascript\n{\n  ...\n  \"scripts\": {\n    \"deploy\": \"cd .. && mix assets.deploy && rm -f _build/esbuild*\"\n  }\n  ...\n}\n```\n\nThe Phoenix Static buildpack uses a predefined Node.js version, but to avoid surprises when deploying, it is best to explicitly list the Node.js version we want in production to be the same we are using during development or in your continuous integration servers. This is done by creating a config file named `phoenix_static_buildpack.config` in the root directory of your project with your target version of Node.js:\n\n```text\n# Node.js version\nnode_version=10.20.1\n```\n\nPlease refer to the [configuration section](https://github.com/gigalixir/gigalixir-buildpack-phoenix-static#configuration) for full details. You can make your own custom build script, but for now we will use the [default one provided](https://github.com/gigalixir/gigalixir-buildpack-phoenix-static/blob/master/compile).\n\nFinally, note that since we are using multiple buildpacks, you might run into an issue where the sequence is out of order (the Elixir buildpack needs to run before the Phoenix Static buildpack). [Heroku's docs](https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app) explain this better, but you will need to make sure the Phoenix Static buildpack comes last.\n\n## Making our Project ready for Heroku\n\nEvery new Phoenix project ships with a config file `config/runtime.exs` which loads configuration and secrets from [environment variables](https://devcenter.heroku.com/articles/config-vars). This aligns well with Heroku best practices ([12-factor apps](https://12factor.net/)), so the only work left for us to do is to configure URLs and SSL.\n\nFirst let's tell Phoenix to only use the SSL version of the website. Find the endpoint config in your `config/prod.exs`:\n\n```elixir\nconfig :scaffold, ScaffoldWeb.Endpoint,\n  url: [port: 443, scheme: \"https\"],\n```\n\n... and add `force_ssl`\n\n```elixir\nconfig :scaffold, ScaffoldWeb.Endpoint,\n  url: [port: 443, scheme: \"https\"],\n  force_ssl: [rewrite_on: [:x_forwarded_proto]],\n```\n\n`force_ssl` need to be set here because it is a _compile_ time config. It will not work when set from `runtime.exs`.\n\nThen in your `config/runtime.exs`:\n\n... add `host`\n\n```elixir\nconfig :scaffold, ScaffoldWeb.Endpoint,\n  url: [host: host, port: 443, scheme: \"https\"]\n```\n\nand uncomment the `# ssl: true,` line in your repository configuration. It will look like this:\n\n```elixir\nconfig :hello, Hello.Repo,\n  ssl: true,\n  url: database_url,\n  pool_size: String.to_integer(System.get_env(\"POOL_SIZE\") || \"10\")\n```\n\nFinally, if you plan on using websockets, then we will need to decrease the timeout for the websocket transport in `lib/hello_web/endpoint.ex`. If you do not plan on using websockets, then leaving it set to false is fine. You can find further explanation of the options available at the [documentation](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration).\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :hello\n\n  socket \"/socket\", HelloWeb.UserSocket,\n    websocket: [timeout: 45_000]\n\n  ...\nend\n```\n\nAlso set the host in Heroku:\n\n```console\n$ heroku config:set PHX_HOST=\"mysterious-meadow-6277.herokuapp.com\"\n```\n\nThis ensures that any idle connections are closed by Phoenix before they reach Heroku's 55-second timeout window.\n\n## Creating Environment Variables in Heroku\n\nThe `DATABASE_URL` config var is automatically created by Heroku when we add the [Heroku Postgres add-on](https://elements.heroku.com/addons/heroku-postgresql). We can create the database via the Heroku toolbelt:\n\n```console\n$ heroku addons:create heroku-postgresql:mini\n```\n\nNow we set the `POOL_SIZE` config var:\n\n```console\n$ heroku config:set POOL_SIZE=18\n```\n\nThis value should be just under the number of available connections, leaving a couple open for migrations and mix tasks. The mini database allows 20 connections, so we set this number to 18. If additional dynos will share the database, reduce the `POOL_SIZE` to give each dyno an equal share.\n\nWhen running a mix task later (after we have pushed the project to Heroku) you will also want to limit its pool size like so:\n\n```console\n$ heroku run \"POOL_SIZE=2 mix hello.task\"\n```\n\nSo that Ecto does not attempt to open more than the available connections.\n\nWe still have to create the `SECRET_KEY_BASE` config based on a random string. First, use `mix phx.gen.secret` to get a new secret:\n\n```console\n$ mix phx.gen.secret\nxvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53\n```\n\nYour random string will be different; don't use this example value.\n\nNow set it in Heroku:\n\n```console\n$ heroku config:set SECRET_KEY_BASE=\"xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53\"\nSetting config vars and restarting mysterious-meadow-6277... done, v3\nSECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53\n```\n\n## Deploy Time!\n\nOur project is now ready to be deployed on Heroku.\n\nLet's commit all our changes:\n\n```console\n$ git add elixir_buildpack.config\n$ git commit -a -m \"Use production config from Heroku ENV variables and decrease socket timeout\"\n```\n\nAnd deploy:\n\n```console\n$ git push heroku main\nCounting objects: 55, done.\nDelta compression using up to 8 threads.\nCompressing objects: 100% (49/49), done.\nWriting objects: 100% (55/55), 48.48 KiB | 0 bytes/s, done.\nTotal 55 (delta 1), reused 0 (delta 0)\nremote: Compressing source files... done.\nremote: Building source:\nremote:\nremote: -----> Multipack app detected\nremote: -----> Fetching custom git buildpack... done\nremote: -----> elixir app detected\nremote: -----> Checking Erlang and Elixir versions\nremote:        WARNING: elixir_buildpack.config wasn't found in the app\nremote:        Using default config from Elixir buildpack\nremote:        Will use the following versions:\nremote:        * Stack cedar-14\nremote:        * Erlang 17.5\nremote:        * Elixir 1.0.4\nremote:        Will export the following config vars:\nremote:        * Config vars DATABASE_URL\nremote:        * MIX_ENV=prod\nremote: -----> Stack changed, will rebuild\nremote: -----> Fetching Erlang 17.5\nremote: -----> Installing Erlang 17.5 (changed)\nremote:\nremote: -----> Fetching Elixir v1.0.4\nremote: -----> Installing Elixir v1.0.4 (changed)\nremote: -----> Installing Hex\nremote: 2015-07-07 00:04:00 URL:https://s3.amazonaws.com/s3.hex.pm/installs/1.0.0/hex.ez [262010/262010] ->\n\"/app/.mix/archives/hex.ez\" [1]\nremote: * creating /app/.mix/archives/hex.ez\nremote: -----> Installing rebar\nremote: * creating /app/.mix/rebar\nremote: -----> Fetching app dependencies with mix\nremote: Running dependency resolution\nremote: Dependency resolution completed successfully\nremote: [...]\nremote: -----> Compiling\nremote: [...]\nremote: Generated phoenix_heroku app\nremote: [...]\nremote: Consolidated protocols written to _build/prod/consolidated\nremote: -----> Creating .profile.d with env vars\nremote: -----> Fetching custom git buildpack... done\nremote: -----> Phoenix app detected\nremote:\nremote: -----> Loading configuration and environment\nremote:        Loading config...\nremote:        [...]\nremote:        Will export the following config vars:\nremote:        * Config vars DATABASE_URL\nremote:        * MIX_ENV=prod\nremote:\nremote: -----> Compressing... done, 82.1MB\nremote: -----> Launching... done, v5\nremote:        https://mysterious-meadow-6277.herokuapp.com/ deployed to Heroku\nremote:\nremote: Verifying deploy... done.\nTo https://git.heroku.com/mysterious-meadow-6277.git\n * [new branch]      master -> master\n```\n\nTyping `heroku open` in the terminal should launch a browser with the Phoenix welcome page opened. In the event that you are using Ecto to access a database, you will also need to run migrations after the first deploy:\n\n```console\n$ heroku run \"POOL_SIZE=2 mix ecto.migrate\"\n```\n\nAnd that's it!\n\n## Deploying to Heroku using the container stack\n\n### Create Heroku application\n\nSet the stack of your app to `container`, this allows us to use `Dockerfile` to define our app setup.\n\n```console\n$ heroku create\nCreating app... done, ⬢ mysterious-meadow-6277\n$ heroku stack:set container\n```\n\nAdd a new `heroku.yml` file to your root folder. In this file you can define addons used by your app, how to build the image and what configs are passed to the image. You can learn more about Heroku's `heroku.yml` options [here](https://devcenter.heroku.com/articles/build-docker-images-heroku-yml). Here is a sample:\n\n```yaml\nsetup:\n  addons:\n    - plan: heroku-postgresql\n      as: DATABASE\nbuild:\n  docker:\n    web: Dockerfile\n  config:\n    MIX_ENV: prod\n    SECRET_KEY_BASE: $SECRET_KEY_BASE\n    DATABASE_URL: $DATABASE_URL\n```\n\n### Set up releases and Dockerfile\n\nNow we need to define a `Dockerfile` at the root folder of your project that contains your application. We recommend to use releases when doing so, as the release will allow us to build a container with only the parts of Erlang and Elixir we actually use. Follow the [releases docs](releases.html). At the end of the guide, there is a sample Dockerfile file you can use.\n\nOnce you have the image definition set up, you can push your app to heroku and you can see it starts building the image and deploy it.\n\n## Useful Heroku Commands\n\nWe can look at the logs of our application by running the following command in our project directory:\n\n```console\n$ heroku logs # use --tail if you want to tail them\n```\n\nWe can also start an IEx session attached to our terminal for experimenting in our app's environment:\n\n```console\n$ heroku run \"POOL_SIZE=2 iex -S mix\"\n```\n\nIn fact, we can run anything using the `heroku run` command, like the Ecto migration task from above:\n\n```console\n$ heroku run \"POOL_SIZE=2 mix ecto.migrate\"\n```\n\n## Connecting to your dyno\n\nHeroku gives you the ability to connect to your dyno with an IEx shell which allows running Elixir code such as database queries.\n\n- Modify the `web` process in your Procfile to run a named node:\n\n  ```text\n  web: elixir --sname server -S mix phx.server\n  ```\n\n- Redeploy to Heroku\n- Connect to the dyno with `heroku ps:exec` (if you have several applications on the same repository you will need to specify the app name or the remote name with `--app APP_NAME` or `--remote REMOTE_NAME`)\n- Launch an iex session with `iex --sname console --remsh server`\n\nYou have an iex session into your dyno!\n\n## Troubleshooting\n\n### Compilation Error\n\nOccasionally, an application will compile locally, but not on Heroku. The compilation error on Heroku will look something like this:\n\n```console\nremote: == Compilation error on file lib/postgrex/connection.ex ==\nremote: could not compile dependency :postgrex, \"mix compile\" failed. You can recompile this dependency with \"mix deps.compile postgrex\", update it with \"mix deps.update postgrex\" or clean it with \"mix deps.clean postgrex\"\nremote: ** (CompileError) lib/postgrex/connection.ex:207: Postgrex.Connection.__struct__/0 is undefined, cannot expand struct Postgrex.Connection\nremote:     (elixir) src/elixir_map.erl:58: :elixir_map.translate_struct/4\nremote:     (stdlib) lists.erl:1353: :lists.mapfoldl/3\nremote:     (stdlib) lists.erl:1354: :lists.mapfoldl/3\nremote:\nremote:\nremote:  !     Push rejected, failed to compile elixir app\nremote:\nremote: Verifying deploy...\nremote:\nremote: !   Push rejected to mysterious-meadow-6277.\nremote:\nTo https://git.heroku.com/mysterious-meadow-6277.git\n```\n\nThis has to do with stale dependencies which are not getting recompiled properly. It's possible to force Heroku to recompile all dependencies on each deploy, which should fix this problem. The way to do it is to add a new file called `elixir_buildpack.config` at the root of the application. The file should contain this line:\n\n```text\nalways_rebuild=true\n```\n\nCommit this file to the repository and try to push again to Heroku.\n\n### Connection Timeout Error\n\nIf you are constantly getting connection timeouts while running `heroku run` this could mean that your internet provider has blocked port number 5000:\n\n```console\nheroku run \"POOL_SIZE=2 mix myapp.task\"\nRunning POOL_SIZE=2 mix myapp.task on mysterious-meadow-6277... !\nETIMEDOUT: connect ETIMEDOUT 50.19.103.36:5000\n```\n\nYou can overcome this by adding `detached` option to run command:\n\n```console\nheroku run:detached \"POOL_SIZE=2 mix ecto.migrate\"\nRunning POOL_SIZE=2 mix ecto.migrate on mysterious-meadow-6277... done, run.8089 (Free)\n```\n"
  },
  {
    "path": "guides/deployment/releases.md",
    "content": "# Deploying with Releases\n\nOur main goal for this guide is to package your Phoenix application into a self-contained directory that includes the Erlang VM, Elixir, all of your code and dependencies. This package can then be dropped into a production machine.\n\n## What we'll need\n\nThe only thing we'll need for this guide is a working Phoenix application. For those of us who need a simple application to deploy, please follow the [Up and Running guide](up_and_running.html).\n\n## Releases, assemble!\n\nIf you are not familiar with Elixir releases yet, we recommend you to read [Elixir's excellent docs](https://hexdocs.pm/mix/Mix.Tasks.Release.html) before continuing.\n\nOnce that is done, you can assemble a release by going through all of the steps in our general [deployment guide](deployment.html) with `mix release` at the end. Let's recap.\n\nFirst set the environment variables:\n\n```console\n$ mix phx.gen.secret\nREALLY_LONG_SECRET\n$ export SECRET_KEY_BASE=REALLY_LONG_SECRET\n$ export DATABASE_URL=ecto://USER:PASS@HOST/database\n```\n\nThen load dependencies to compile code and assets:\n\n```console\n# Initial setup\n$ mix deps.get --only prod\n$ MIX_ENV=prod mix compile\n\n# Compile assets\n$ MIX_ENV=prod mix assets.deploy\n```\n\nAnd now run `mix phx.gen.release`:\n\n```console\n$ mix phx.gen.release\n==> my_app\n* creating rel/overlays/bin/server\n* creating rel/overlays/bin/server.bat\n* creating rel/overlays/bin/migrate\n* creating rel/overlays/bin/migrate.bat\n* creating lib/my_app/release.ex\n\nYour application is ready to be deployed in a release!\n\n    # To start your system\n    _build/dev/rel/my_app/bin/my_app start\n\n    # To start your system with the Phoenix server running\n    _build/dev/rel/my_app/bin/server\n\n    # To run migrations\n    _build/dev/rel/my_app/bin/migrate\n\nOnce the release is running:\n\n    # To connect to it remotely\n    _build/dev/rel/my_app/bin/my_app remote\n\n    # To stop it gracefully (you may also send SIGINT/SIGTERM)\n    _build/dev/rel/my_app/bin/my_app stop\n\nTo list all commands:\n\n    _build/dev/rel/my_app/bin/my_app\n\n```\n\nThe `phx.gen.release` task generated a few files for us to assist in releases. First, it created `server` and `migrate` *overlay* scripts for conveniently running the phoenix server inside a release or invoking migrations from a release. The files in the `rel/overlays` directory are copied into every release environment. Next, it generated a `release.ex` file which is used to invoke Ecto migrations without a dependency on `mix` itself.\n\n*Note*: If you are a Docker user, you can pass the `--docker` flag to `mix phx.gen.release` to generate a Dockerfile ready for deployment.\n\nNext, we can invoke `mix release` to build the release:\n\n```console\n$ MIX_ENV=prod mix release\nGenerated my_app app\n* assembling my_app-0.1.0 on MIX_ENV=prod\n* using config/runtime.exs to configure the release at runtime\n\nRelease created at _build/prod/rel/my_app!\n\n    # To start your system\n    _build/prod/rel/my_app/bin/my_app start\n\n...\n```\n\nYou can start the release by calling `_build/prod/rel/my_app/bin/my_app start`, or boot your webserver by calling `_build/prod/rel/my_app/bin/server`, where you have to replace `my_app` by your current application name.\n\nNow you can get all of the files under the `_build/prod/rel/my_app` directory, package it, and run it in any production machine with the same OS and architecture as the one that assembled the release. For more details, check the [docs for `mix release`](https://hexdocs.pm/mix/Mix.Tasks.Release.html).\n\n## Ecto migrations\n\nA common need in production systems is to execute custom commands required to set up the production environment. One of such commands is precisely migrating the database. Since we don't have `Mix`, a *build* tool, inside releases, which are production artifacts, we need to bring said commands directly into the release.\n\nThe `phx.gen.release` command created the following `release.ex` file in your project `lib/my_app/release.ex`, with the following content:\n\n```elixir\ndefmodule MyApp.Release do\n  @app :my_app\n\n  def migrate do\n    load_app()\n\n    for repo <- repos() do\n      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))\n    end\n  end\n\n  def rollback(repo, version) do\n    load_app()\n    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))\n  end\n\n  defp repos do\n    Application.fetch_env!(@app, :ecto_repos)\n  end\n\n  defp load_app do\n    Application.ensure_all_started(:ssl)\n    Application.ensure_loaded(@app)\n  end\nend\n```\n\nWhere you replace the first two lines by your application names.\n\nNow you can assemble a new release with `MIX_ENV=prod mix release` and you can invoke any code, including the functions in the module above, by calling the `eval` command:\n\n```console\n$ _build/prod/rel/my_app/bin/my_app eval \"MyApp.Release.migrate\"\n```\n\nAnd that's it! If you peek inside the `migrate` script, you'll see it wraps exactly this invocation. Depending on where you are deploying your application, you can invoke the `migrate` command separately, or you may want to change the `server` script to migrate your database before starting your app.\n\n## Custom commands\n\nYou can use the same approach used for migrations to create any custom command to run in production. The idea is that each command invokes `load_app`, which calls `Application.ensure_loaded/1` to load the current application without starting it.\n\nHowever, some commands may need to start the whole application. In such cases, `Application.ensure_all_started/1` must be used instead of `Application.load/1`. Keep in mind starting the application will start all processes in its supervision tree, including the Phoenix endpoint. This can be circumvented by changing your supervision tree to not start certain children under certain conditions. For example, in the release commands file you could do:\n\n```elixir\ndefp start_app do\n  load_app()\n  Application.put_env(@app, :minimal, true)\n  Application.ensure_all_started(@app)\nend\n```\n\nAnd then in your application you check `Application.get_env(@app, :minimal)` and start only part of the children when it is set.\n\n## Containers\n\nElixir releases work well with container technologies such as Docker. The idea is that you assemble the release inside the Docker container and then build an image based on the release artifacts.\n\nIf you call `mix phx.gen.release --docker`, you'll see a new file with content similar to:\n\n```Dockerfile\n# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian\n# instead of Alpine to avoid DNS resolution issues in production.\n#\n# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu\n# https://hub.docker.com/_/ubuntu?tab=tags\n#\n# This file is based on these images:\n#\n#   - https://hub.docker.com/r/hexpm/elixir/tags - for the build image\n#   - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20230612-slim - for the release image\n#   - https://pkgs.org/ - resource for finding needed packages\n#   - Ex: hexpm/elixir:1.18.4-erlang-27.3.4.3-debian-trixie-20250908-slim\n#\nARG ELIXIR_VERSION=1.18.4\nARG OTP_VERSION=27.3.4.3\nARG DEBIAN_VERSION=trixie-20250908-slim\n\nARG BUILDER_IMAGE=\"hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}\"\nARG RUNNER_IMAGE=\"debian:${DEBIAN_VERSION}\"\n\nFROM ${BUILDER_IMAGE} AS builder\n\n# install build dependencies\nRUN apt-get update \\\n  && apt-get install -y --no-install-recommends build-essential git \\\n  && rm -rf /var/lib/apt/lists/*\n\n# prepare build dir\nWORKDIR /app\n\n# install hex + rebar\nRUN mix local.hex --force \\\n  && mix local.rebar --force\n\n# set build ENV\nENV MIX_ENV=\"prod\"\n\n# install mix dependencies\nCOPY mix.exs mix.lock ./\nRUN mix deps.get --only $MIX_ENV\nRUN mkdir config\n\n# copy compile-time config files before we compile dependencies\n# to ensure any relevant config change will trigger the dependencies\n# to be re-compiled.\nCOPY config/config.exs config/${MIX_ENV}.exs config/\nRUN mix deps.compile\n\nRUN mix assets.setup\n\nCOPY priv priv\n\nCOPY lib lib\n\n# Compile the release\nRUN mix compile\n\nCOPY assets assets\n\n# compile assets\nRUN mix assets.deploy\n\n# Changes to config/runtime.exs don't require recompiling the code\nCOPY config/runtime.exs config/\n\nCOPY rel rel\nRUN mix release\n\n# start a new build stage so that the final image will only contain\n# the compiled release and other runtime necessities\nFROM ${RUNNER_IMAGE} AS final\n\nRUN apt-get update \\\n  && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates \\\n  && rm -rf /var/lib/apt/lists/*\n\n# Set the locale\nRUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \\\n  && locale-gen\n\nENV LANG=en_US.UTF-8\nENV LANGUAGE=en_US:en\nENV LC_ALL=en_US.UTF-8\n\nWORKDIR \"/app\"\nRUN chown nobody /app\n\n# set runner ENV\nENV MIX_ENV=\"prod\"\n\n# Only copy the final release from the build stage\nCOPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/my_app ./\n\nUSER nobody\n\n# If using an environment that doesn't automatically reap zombie processes, it is\n# advised to add an init process such as tini via `apt-get install`\n# above and adding an entrypoint. See https://github.com/krallin/tini for details\n# ENTRYPOINT [\"/tini\", \"--\"]\n\nCMD [\"/app/bin/server\"]\n```\n\nWhere `my_app` is the name of your app. At the end, you will have an application in `/app` ready to run as `/app/bin/server`.\n\nA few points about configuring a containerized application:\n\n- The more configuration you can provide at runtime (using `config/runtime.exs`), the more reusable your images will be across environments. In particular, secrets like database credentials and API keys should not be compiled into the image, but rather should be provided when creating containers based on that image. This is why the `Endpoint`'s `:secret_key_base` is configured in `config/runtime.exs` by default.\n\n- If possible, any environment variables that are needed at runtime should be read in `config/runtime.exs`, not scattered throughout your code. Having them all visible in one place will make it easier to ensure the containers get what they need, especially if the person doing the infrastructure work does not work on the Elixir code. Libraries in particular should never directly read environment variables; all their configuration should be handed to them by the top-level application, preferably [without using the application environment](https://hexdocs.pm/elixir/design-anti-patterns.html#using-application-configuration-for-libraries).\n\n## Clustering\n\nElixir and the Erlang VM have the incredible ability to be clustered together and pass messages seamlessly between nodes. To enable clustering, we need two distinct features:\n\n* Node connection: different instances of the same service should communicate with each other. This is a feature of the Erlang VM.\n\n* Service discovery: for a given service, you must be able to find the IP address of all instances. Phoenix ships with `dns_cluster` to provide out-of-the-box DNS-based service discovery but alternative methods may be used.\n\n### DNS Discovery\n\nYour clustering configuration is typically added to `rel/env.sh.eex`. This is a file that is executed before you release starts, and it is a perfect place to configure your application runtime based on your deployment environment. Here is a general skeleton:\n\n```sh\n# Uncomment if IPv6 is required\n# export ECTO_IPV6=\"true\"\n# export ERL_AFLAGS=\"-proto_dist inet6_tcp\"\n\n# Erlang uses a port mapper daemon on each node,\n# it by default runs on port 4369\nexport ERL_EPMD_PORT=4369\n\n# Use the ports 4370-4372 for nodes to communicate.\nexport ERL_AFLAGS=\"-kernel inet_dist_listen_min 4370 inet_dist_listen_max 4372\"\n\nexport RELEASE_DISTRIBUTION=\"name\"\nexport RELEASE_NODE=\"app-${PLATFORM_DEPLOYMENT_SHA}@${PLATFORM_DEPLOYMENT_IP}\"\nexport DNS_CLUSTER_QUERY=\"your-app.internal\"\n```\n\nThe script above is doing a couple things:\n\n* It configures your app to use ports 4369, 4370, 4371, and 4372 for communication. You may need to explicitly expose those as internal TCP ports in your deployment platform (in addition to the HTTP port of your choice)\n\n* It then configures your app to use fully qualified names. The name of each app will include the current deployment sha as `PLATFORM_DEPLOYMENT_SHA` (the name of the exact environment variable is platform dependent), so each deployment establishes its own cluster, and the current IP as `PLATFORM_DEPLOYMENT_IP` (also platform specific). If the IP is not available, you may be able to compute it as `NODE_IP=hostname | tr -d ' '`\n\n* Then finally you define a DNS query which will be used to find the IPs of the other instances\n\nSome platforms, such as [Fly.io](https://fly.io/docs/networking/private-networking/), [Railway](https://docs.railway.com/guides/private-networking), and [Render](https://render.com/docs/private-network#direct-ip-communication-advanced), provide private networks with DNS querying out of the box. You only need to adapt the `DNS_CLUSTER_QUERY` variable accordingly.\n\nOther platforms, such as [Digital Ocean App Platform](https://www.digitalocean.com/products/app-platform) and [Northflank](https://northflank.com/features/platform), allow nodes to directly connect to each other, but they do not provide DNS service discovery. In this next section, we explore different service discovery mechanisms.\n\n### Alternative discovery mechanisms\n\nWhile not all platforms support DNS queries for service discovery, there are many alternative strategies for connecting your nodes together. Please checkout the following libraries:\n\n  * [libcluster](https://github.com/bitwalker/libcluster) - provides strategies for connecting your nodes using gossip protocols, kubernetes, ec2, and others\n\n  * [libcluster_postgres](https://github.com/supabase/libcluster_postgres/) - a plugin for `libcluster` which uses PostgreSQL for node discovery. Given most applications already use a database, and likely PostgreSQL, this is a suitable option which does not require additional setup\n\nWhen using the libraries above, you can likely remove `dns_query` from your application dependencies.\n\n### `epmd`-less deployment\n\nIn the snippet above, we used ports 4369, 4370, 4371, and 4372. However, the Erlang VM allows running the distribution over a fixed port, also known as `epmd`-less deployments. To enable such, do this.\n\nRemove the lines:\n\n```sh\nexport ERL_EPMD_PORT=4369\nexport ERL_AFLAGS=\"-kernel inet_dist_listen_min 4370 inet_dist_listen_max 4372\"\n```\n\nAdd a file rel/vm.args.eex with the following:\n\n```\n-start_epmd false -erl_epmd_port 6789\n```\n\nAdd a file rel/remote.vm.args.eex with the following:\n\n```\n-start_epmd false -erl_epmd_port 6789 -dist_listen false\n```\n\nAnd now only port 6789 (in addition to the HTTP one) needs to be exposed internally between instances.\n"
  },
  {
    "path": "guides/directory_structure.md",
    "content": "# Directory structure\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\nWhen we use `mix phx.new` to generate a new Phoenix application, it builds a top-level directory structure like this:\n\n```console\n├── _build\n├── assets\n├── config\n├── deps\n├── lib\n│   ├── hello\n│   ├── hello.ex\n│   ├── hello_web\n│   └── hello_web.ex\n├── priv\n└── test\n```\n\nWe will go over those directories one by one:\n\n  * `_build` - a directory that holds all compilation artifacts, created by the `mix` command line tool (shipped as part of Elixir). As we have seen in \"[Up and Running](up_and_running.html)\", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch.\n\n  * `assets` - a directory that keeps source code for your front-end assets, typically JavaScript and CSS. These sources are automatically bundled by the `esbuild` tool. Static files like images and fonts go in `priv/static`.\n\n  * `config` - a directory that holds your project configuration. The `config/config.exs` file is the entry point for your configuration. At the end of the `config/config.exs`, it imports environment specific configuration, which can be found in `config/dev.exs`, `config/test.exs`, and `config/prod.exs`. Finally, `config/runtime.exs` is executed and it is the best place to read secrets and other dynamic configuration.\n\n  * `deps` - a directory with all of our Mix dependencies. You can find all dependencies listed in the `mix.exs` file, inside the `defp deps do` function definition. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to download all deps from scratch.\n\n  * `lib` - a directory that holds your application source code. This directory is broken into two subdirectories, `lib/hello` and `lib/hello_web`. The `lib/hello` directory is responsible for hosting all of your business logic and business domain. It typically interacts directly with the database - it is the \"Model\" in Model-View-Controller (MVC) architecture. `lib/hello_web` is responsible for exposing your business domain to the world, in this case, through a web application. It holds both the View and Controller from MVC. We will discuss the contents of these directories in more detail in the next sections.\n\n  * `priv` - a directory that keeps all resources that are necessary in production but are not directly part of your source code. You typically keep database scripts, translation files, images, and more in here. Generated assets, created from files in the `assets` directory, are placed in `priv/static/assets` by default.\n\n  * `test` - a directory with all of our application tests. It often mirrors the same structure found in `lib`.\n\n## The lib/hello directory\n\nThe `lib/hello` directory hosts all of your business domain. Since our project does not have any business logic yet, the directory is mostly empty. You will only find three files:\n\n```console\nlib/hello\n├── application.ex\n├── mailer.ex\n└── repo.ex\n```\n\nThe `lib/hello/application.ex` file defines an Elixir application named `Hello.Application`. That's because at the end of the day Phoenix applications are simply Elixir applications. The `Hello.Application` module defines which services are part of our application:\n\n```elixir\nchildren = [\n  HelloWeb.Telemetry,\n  Hello.Repo,\n  {Phoenix.PubSub, name: Hello.PubSub},\n  HelloWeb.Endpoint\n]\n```\n\nIf it is your first time with Phoenix, you don't need to worry about the details right now. For now, suffice it to say our application starts a database repository, a PubSub system for sharing messages across processes and nodes, and the application endpoint, which effectively serves HTTP requests. These services are started in the order they are defined and, whenever shutting down your application, they are stopped in the reverse order.\n\nYou can learn more about applications in [Elixir's official docs for Application](https://hexdocs.pm/elixir/Application.html).\n\nThe `lib/hello/mailer.ex` file holds the `Hello.Mailer` module, which defines the main interface to deliver e-mails:\n\n```elixir\ndefmodule Hello.Mailer do\n  use Swoosh.Mailer, otp_app: :hello\nend\n```\n\nIn the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default database), you will see something like this:\n\n```elixir\ndefmodule Hello.Repo do\n  use Ecto.Repo,\n    otp_app: :hello,\n    adapter: Ecto.Adapters.Postgres\nend\n```\n\nAnd that's it for now. As you work on your project, we will add files and modules to this directory.\n\n## The lib/hello_web directory\n\nThe `lib/hello_web` directory holds the web-related parts of our application. It looks like this when expanded:\n\n```console\nlib/hello_web\n├── controllers\n│   ├── page_controller.ex\n│   ├── page_html.ex\n│   ├── error_html.ex\n│   ├── error_json.ex\n│   └── page_html\n│       └── home.html.heex\n├── components\n│   ├── core_components.ex\n│   ├── layouts.ex\n│   └── layouts\n│       └── root.html.heex\n├── endpoint.ex\n├── gettext.ex\n├── router.ex\n└── telemetry.ex\n```\n\nAll of the files which are currently in the `controllers` and `components` directories are there to create the \"Welcome to Phoenix!\" page we saw in the \"[Up and running](up_and_running.html)\" guide.\n\nBy looking at `controller` and `components` directories, we can see Phoenix provides features for handling layouts, HTML, and error pages out of the box.\n\nBesides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses [http://localhost:4000](http://localhost:4000), the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to \"controllers\", which calls a view module to render HTML pages back to clients. We explore these layers in length in other guides, starting with the \"[Request life-cycle](request_lifecycle.html)\" guide coming next.\n\nThrough _Telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).\n\nFinally, there is a `lib/hello_web/gettext.ex` file which provides internationalization through [Gettext](https://hexdocs.pm/gettext/Gettext.html). If you are not worried about internationalization, you can safely skip this file and its contents.\n\n## The assets directory\n\nThe `assets` directory contains source files related to front-end assets, such as JavaScript and CSS. Since Phoenix v1.6, we use [`esbuild`](https://github.com/evanw/esbuild/) to compile assets, which is managed by the [`esbuild`](https://github.com/phoenixframework/esbuild) Elixir package. The integration with `esbuild` is baked into your app. The relevant config can be found in your `config/config.exs` file.\n\nYour other static assets are placed in the `priv/static` folder, where `priv/static/assets` is kept for generated assets. Everything in `priv/static` is served by the `Plug.Static` plug configured in `lib/hello_web/endpoint.ex`.  When running in dev mode (`MIX_ENV=dev`), Phoenix watches for any changes you make in the `assets` directory, and then takes care of updating your front end application in your browser as you work.\n\nNote that when you first create your Phoenix app using `mix phx.new` it is possible to specify options that will affect the presence and layout of the `assets` directory.  In fact, Phoenix apps can bring their own front end tools or not have a front-end at all (handy if you're writing an API for example).  For more information you can run `mix help phx.new`.\n\nIf the default esbuild integration does not cover your needs, for example because you want to use another build tool, you can switch to a [custom assets build](asset_management.html#custom_builds).\n\nAs for CSS, Phoenix ships with the [Tailwind CSS Framework](https://tailwindcss.com/), providing a base setup for projects. You may move to any CSS framework of your choice. Additional references can be found in the [asset management](asset_management.md#css) guide.\n"
  },
  {
    "path": "guides/ecto.md",
    "content": "# Ecto\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\nMost web applications today need some form of data validation and persistence. In the Elixir ecosystem, we have `Ecto` to enable this. Before we jump into building database-backed web features, we're going to focus on the finer details of Ecto to give a solid base to build our web features on top of. Let's get started!\n\nPhoenix uses Ecto to provide builtin support to the following databases:\n\n* PostgreSQL (via [`postgrex`](https://github.com/elixir-ecto/postgrex))\n* MySQL (via [`myxql`](https://github.com/elixir-ecto/myxql))\n* MSSQL (via [`tds`](https://github.com/livehelpnow/tds))\n* ETS (via [`etso`](https://github.com/evadne/etso))\n* SQLite3 (via [`ecto_sqlite3`](https://github.com/elixir-sqlite/ecto_sqlite3))\n\nNewly generated Phoenix projects include Ecto with the PostgreSQL adapter by default. You can pass the `--database` option to change or `--no-ecto` flag to exclude this.\n\nEcto also provides support for other databases and it has many learning resources available. Please check out [Ecto's README](https://github.com/elixir-ecto/ecto) for general information.\n\nThis guide assumes that we have generated our new application with Ecto integration and that we will be using PostgreSQL. The introductory guides cover how to get your first application up and running. For using other databases, see the [Swapping Databases](swapping_databases.html) how-to guide.\n\n## Using `phx.gen.schema`\n\nOnce we have Ecto and PostgreSQL installed and configured, the easiest way to use Ecto is to generate an Ecto *schema* through the `phx.gen.schema` task. Ecto schemas are a way for us to specify how Elixir data types map to and from external sources, such as database tables. Let's generate a `User` schema with `name`, `email`, `bio`, and `number_of_pets` fields.\n\n```console\n$ mix phx.gen.schema User users name:string email:string \\\nbio:string number_of_pets:integer\n\n* creating ./lib/hello/user.ex\n* creating priv/repo/migrations/20170523151118_create_users.exs\n\nRemember to update your repository by running migrations:\n\n   $ mix ecto.migrate\n```\n\nA couple of files were generated with this task. First, we have a `user.ex` file, containing our Ecto schema with our schema definition of the fields we passed to the task. Next, a migration file was generated inside `priv/repo/migrations/` which will create our database table that our schema maps to.\n\nWith our files in place, let's follow the instructions and run our migration:\n\n```console\n$ mix ecto.migrate\nCompiling 1 file (.ex)\nGenerated hello app\n\n[info] == Running Hello.Repo.Migrations.CreateUsers.change/0 forward\n\n[info] create table users\n\n[info] == Migrated in 0.0s\n```\n\nMix assumes that we are in the development environment unless we tell it otherwise with `MIX_ENV=prod mix ecto.migrate`.\n\nIf we log in to our database server, and connect to our `hello_dev` database, we should see our `users` table. Ecto assumes that we want an integer column called `id` as our primary key, so we should see a sequence generated for that as well.\n\n```console\n$ psql -U postgres\n\nType \"help\" for help.\n\npostgres=# \\connect hello_dev\nYou are now connected to database \"hello_dev\" as user \"postgres\".\nhello_dev=# \\d\n                List of relations\n Schema |       Name        |   Type   |  Owner\n--------+-------------------+----------+----------\n public | schema_migrations | table    | postgres\n public | users             | table    | postgres\n public | users_id_seq      | sequence | postgres\n(3 rows)\nhello_dev=# \\q\n```\n\nIf we take a look at the migration generated by `phx.gen.schema` in `priv/repo/migrations/`, we'll see that it will add the columns we specified. It will also add timestamp columns for `inserted_at` and `updated_at` which come from the [`timestamps/1`] function.\n\n```elixir\ndefmodule Hello.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users) do\n      add :name, :string\n      add :email, :string\n      add :bio, :string\n      add :number_of_pets, :integer\n\n      timestamps(type: :utc_datetime)\n    end\n  end\nend\n```\n\nAnd here's what that translates to in the actual `users` table.\n\n```console\n$ psql\nhello_dev=# \\d users\nTable \"public.users\"\nColumn         |            Type                | Modifiers\n---------------+--------------------------------+----------------------------------------------------\nid             | bigint                         | not null default nextval('users_id_seq'::regclass)\nname           | character varying(255)         |\nemail          | character varying(255)         |\nbio            | character varying(255)         |\nnumber_of_pets | integer                        |\ninserted_at    | timestamp(0) without time zone | not null\nupdated_at     | timestamp(0) without time zone | not null\nIndexes:\n\"users_pkey\" PRIMARY KEY, btree (id)\n```\n\nNotice that we do get an `id` column as our primary key by default, even though it isn't listed as a field in our migration.\n\n## Repo configuration\n\nOur `Hello.Repo` module is the foundation we need to work with databases in a Phoenix application. Phoenix generated it for us in `lib/hello/repo.ex`, and this is what it looks like.\n\n```elixir\ndefmodule Hello.Repo do\n  use Ecto.Repo,\n    otp_app: :hello,\n    adapter: Ecto.Adapters.Postgres\nend\n```\n\nIt begins by defining the repository module. Then it configures our `otp_app` name, and the `adapter` – `Postgres`, in our case.\n\nOur repo has three main tasks - to bring in all the common query functions from [`Ecto.Repo`], to set the `otp_app` name equal to our application name, and to configure our database adapter. We'll talk more about how to use `Hello.Repo` in a bit.\n\nWhen `phx.new` generated our application, it included some basic repository configuration as well. Let's look at `config/dev.exs`.\n\n```elixir\n...\n# Configure your database\nconfig :hello, Hello.Repo,\n  username: \"postgres\",\n  password: \"postgres\",\n  hostname: \"localhost\",\n  database: \"hello_dev\",\n  show_sensitive_data_on_connection_error: true,\n  pool_size: 10\n...\n```\n\nWe also have similar configuration in `config/test.exs` and `config/runtime.exs` which can also be changed to match your actual credentials.\n\n## The schema\n\nEcto schemas are responsible for mapping Elixir values to external data sources, as well as mapping external data back into Elixir data structures. We can also define relationships to other schemas in our applications. For example, our `User` schema might have many posts, and each post would belong to a user. Ecto also handles data validation and type casting with changesets, which we'll discuss in a moment.\n\nHere's the `User` schema that Phoenix generated for us.\n\n```elixir\ndefmodule Hello.User do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"users\" do\n    field :bio, :string\n    field :email, :string\n    field :name, :string\n    field :number_of_pets, :integer\n\n    timestamps(type: :utc_datetime)\n  end\n\n  @doc false\n  def changeset(user, attrs) do\n    user\n    |> cast(attrs, [:name, :email, :bio, :number_of_pets])\n    |> validate_required([:name, :email, :bio, :number_of_pets])\n  end\nend\n```\n\nEcto schemas at their core are simply Elixir structs. Our `schema` block is what tells Ecto how to cast our `%User{}` struct fields to and from the external `users` table. Often, the ability to simply cast data to and from the database isn't enough and extra data validation is required. This is where Ecto changesets come in. Let's dive in!\n\n## Changesets and validations\n\nChangesets define a pipeline of transformations our data needs to undergo before it will be ready for our application to use. These transformations might include type-casting, user input validation, and filtering out any extraneous parameters. Often we'll use changesets to validate user input before writing it to the database. Ecto repositories are also changeset-aware, which allows them not only to refuse invalid data, but also perform the minimal database updates possible by inspecting the changeset to know which fields have changed.\n\nLet's take a closer look at our default changeset function.\n\n```elixir\ndef changeset(user, attrs) do\n  user\n  |> cast(attrs, [:name, :email, :bio, :number_of_pets])\n  |> validate_required([:name, :email, :bio, :number_of_pets])\nend\n```\n\nRight now, we have two transformations in our pipeline. In the first call, we invoke `Ecto.Changeset.cast/3`, passing in our external parameters and marking which fields are required for validation.\n\n[`cast/3`] first takes a struct, then the parameters (the proposed updates), and then the final field is the list of columns to be updated. [`cast/3`] also will only take fields that exist in the schema.\n\nNext, `Ecto.Changeset.validate_required/3` checks that this list of fields is present in the changeset that [`cast/3`] returns. By default with the generator, all fields are required.\n\nWe can verify this functionality in `IEx`. Let's fire up our application inside IEx by running `iex -S mix`. In order to minimize typing and make this easier to read, let's alias our `Hello.User` struct.\n\n```console\n$ iex -S mix\n\niex> alias Hello.User\nHello.User\n```\n\nNext, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters.\n\n```elixir\niex> changeset = User.changeset(%User{}, %{})\n#Ecto.Changeset<\n  action: nil,\n  changes: %{},\n  errors: [\n    name: {\"can't be blank\", [validation: :required]},\n    email: {\"can't be blank\", [validation: :required]},\n    bio: {\"can't be blank\", [validation: :required]},\n    number_of_pets: {\"can't be blank\", [validation: :required]}\n  ],\n  data: #Hello.User<>,\n  valid?: false\n>\n```\n\nOnce we have a changeset, we can check if it is valid.\n\n```elixir\niex> changeset.valid?\nfalse\n```\n\nSince this one is not valid, we can ask it what the errors are.\n\n```elixir\niex> changeset.errors\n[\n  name: {\"can't be blank\", [validation: :required]},\n  email: {\"can't be blank\", [validation: :required]},\n  bio: {\"can't be blank\", [validation: :required]},\n  number_of_pets: {\"can't be blank\", [validation: :required]}\n]\n```\n\nNow, let's make `number_of_pets` optional. In order to do this, we simply remove it from the list in the `changeset/2` function, in `Hello.User`.\n\n```elixir\n|> validate_required([:name, :email, :bio])\n```\n\nNow casting the changeset should tell us that only `name`, `email`, and `bio` can't be blank. We can test that by running `recompile()` inside IEx and then rebuilding our changeset.\n\n```elixir\niex> recompile()\nCompiling 1 file (.ex)\n:ok\n\niex> changeset = User.changeset(%User{}, %{})\n#Ecto.Changeset<\n  action: nil,\n  changes: %{},\n  errors: [\n    name: {\"can't be blank\", [validation: :required]},\n    email: {\"can't be blank\", [validation: :required]},\n    bio: {\"can't be blank\", [validation: :required]}\n  ],\n  data: #Hello.User<>,\n  valid?: false\n>\n\niex> changeset.errors\n[\n  name: {\"can't be blank\", [validation: :required]},\n  email: {\"can't be blank\", [validation: :required]},\n  bio: {\"can't be blank\", [validation: :required]}\n]\n```\n\nWhat happens if we pass a key-value pair that is neither defined in the schema nor required?\n\nInside our existing IEx shell, let's create a `params` map with valid values plus an extra `random_key: \"random value\"`.\n\n```elixir\niex> params = %{name: \"Joe Example\", email: \"joe@example.com\", bio: \"An example to all\", number_of_pets: 5, random_key: \"random value\"}\n%{\n  bio: \"An example to all\",\n  email: \"joe@example.com\",\n  name: \"Joe Example\",\n  number_of_pets: 5,\n  random_key: \"random value\"\n}\n```\n\nNext, let's use our new `params` map to create another changeset.\n\n```elixir\niex> changeset = User.changeset(%User{}, params)\n#Ecto.Changeset<\n  action: nil,\n  changes: %{\n    bio: \"An example to all\",\n    email: \"joe@example.com\",\n    name: \"Joe Example\",\n    number_of_pets: 5\n  },\n  errors: [],\n  data: #Hello.User<>,\n  valid?: true\n>\n```\n\nOur new changeset is valid.\n\n```elixir\niex> changeset.valid?\ntrue\n```\n\nWe can also check the changeset's changes - the map we get after all of the transformations are complete.\n\n```elixir\niex(9)> changeset.changes\n%{bio: \"An example to all\", email: \"joe@example.com\", name: \"Joe Example\",\n  number_of_pets: 5}\n```\n\nNotice that our `random_key` key and `\"random_value\"` value have been removed from the final changeset. Changesets allow us to cast external data, such as user input on a web form or data from a CSV file into valid data into our system. Invalid parameters will be stripped and bad data that is unable to be cast according to our schema will be highlighted in the changeset errors.\n\nWe can validate more than just whether a field is required or not. Let's take a look at some finer-grained validations.\n\nWhat if we had a requirement that all biographies in our system must be at least two characters long? We can do this easily by adding another transformation to the pipeline in our changeset which validates the length of the `bio` field.\n\n```elixir\ndef changeset(user, attrs) do\n  user\n  |> cast(attrs, [:name, :email, :bio, :number_of_pets])\n  |> validate_required([:name, :email, :bio, :number_of_pets])\n  |> validate_length(:bio, min: 2)\nend\n```\n\nNow, if we try to cast data containing a value of `\"A\"` for our user's `bio`, we should see the failed validation in the changeset's errors.\n\n```elixir\niex> recompile()\n\niex> changeset = User.changeset(%User{}, %{bio: \"A\"})\n\niex> changeset.errors[:bio]\n{\"should be at least %{count} character(s)\",\n [count: 2, validation: :length, kind: :min, type: :string]}\n```\n\nIf we also have a requirement for the maximum length that a bio can have, we can simply add another validation.\n\n```elixir\ndef changeset(user, attrs) do\n  user\n  |> cast(attrs, [:name, :email, :bio, :number_of_pets])\n  |> validate_required([:name, :email, :bio, :number_of_pets])\n  |> validate_length(:bio, min: 2)\n  |> validate_length(:bio, max: 140)\nend\n```\n\nLet's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the `@`. The `Ecto.Changeset.validate_format/3` function is just what we need.\n\n```elixir\ndef changeset(user, attrs) do\n  user\n  |> cast(attrs, [:name, :email, :bio, :number_of_pets])\n  |> validate_required([:name, :email, :bio, :number_of_pets])\n  |> validate_length(:bio, min: 2)\n  |> validate_length(:bio, max: 140)\n  |> validate_format(:email, ~r/@/)\nend\n```\n\nIf we try to cast a user with an email of `\"example.com\"`, we should see an error message like the following:\n\n```elixir\niex> recompile()\n\niex> changeset = User.changeset(%User{}, %{email: \"example.com\"})\n\niex> changeset.errors[:email]\n{\"has invalid format\", [validation: :format]}\n```\n\nThere are many more validations and transformations we can perform in a changeset. Please see the [Ecto Changeset documentation](https://hexdocs.pm/ecto/Ecto.Changeset.html) for more information.\n\n## Data persistence\n\nWe've explored migrations and schemas, but we haven't yet persisted any of our schemas or changesets. We briefly looked at our repository module in `lib/hello/repo.ex` earlier, and now it's time to put it to use.\n\nEcto repositories are the interface into a storage system, be it a database like PostgreSQL or an external service like a RESTful API. The `Repo` module's purpose is to take care of the finer details of persistence and data querying for us. As the caller, we only care about fetching and persisting data. The `Repo` module takes care of the underlying database adapter communication, connection pooling, and error translation for database constraint violations.\n\nLet's head back over to IEx with `iex -S mix`, and insert a couple of users into the database.\n\n```elixir\niex> alias Hello.{Repo, User}\n[Hello.Repo, Hello.User]\n\niex> Repo.insert(%User{email: \"user1@example.com\"})\n[debug] QUERY OK db=6.5ms queue=0.5ms idle=1358.3ms\nINSERT INTO \"users\" (\"email\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"user1@example.com\", ~U[2021-02-25 01:58:55Z], ~U[2021-02-25 01:58:55Z]]\n{:ok,\n %Hello.User{\n   __meta__: #Ecto.Schema.Metadata<:loaded, \"users\">,\n   bio: nil,\n   email: \"user1@example.com\",\n   id: 1,\n   inserted_at: ~U[2021-02-25 01:58:55Z],\n   name: nil,\n   number_of_pets: nil,\n   updated_at: ~U[2021-02-25 01:58:55Z]\n }}\n\niex> Repo.insert(%User{email: \"user2@example.com\"})\n[debug] QUERY OK db=1.3ms idle=1402.7ms\nINSERT INTO \"users\" (\"email\",\"inserted_at\",\"updated_at\") VALUES ($1,$2,$3) RETURNING \"id\" [\"user2@example.com\", ~U[2021-02-25 02:03:28Z], ~U[2021-02-25 02:03:28Z]]\n{:ok,\n %Hello.User{\n   __meta__: #Ecto.Schema.Metadata<:loaded, \"users\">,\n   bio: nil,\n   email: \"user2@example.com\",\n   id: 2,\n   inserted_at: ~U[2021-02-25 02:03:28Z],\n   name: nil,\n   number_of_pets: nil,\n   updated_at: ~U[2021-02-25 02:03:28Z]\n }}\n```\n\nWe started by aliasing our `User` and `Repo` modules for easy access. Next, we called [`Repo.insert/2`] with a User struct. Since we are in the `dev` environment, we can see the debug logs for the query our repository performed when inserting the underlying `%User{}` data. We received a two-element tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful.\n\nWe could also insert a user by passing a changeset to [`Repo.insert/2`]. If the changeset is valid, the repository will use an optimized database query to insert the record, and return a two-element tuple back, as above. If the changeset is not valid, we receive a two-element tuple consisting of `:error` plus the invalid changeset.\n\nWith a couple of users inserted, let's fetch them back out of the repo.\n\n```elixir\niex> Repo.all(User)\n[debug] QUERY OK source=\"users\" db=5.8ms queue=1.4ms idle=1672.0ms\nSELECT u0.\"id\", u0.\"bio\", u0.\"email\", u0.\"name\", u0.\"number_of_pets\", u0.\"inserted_at\", u0.\"updated_at\" FROM \"users\" AS u0 []\n[\n  %Hello.User{\n    __meta__: #Ecto.Schema.Metadata<:loaded, \"users\">,\n    bio: nil,\n    email: \"user1@example.com\",\n    id: 1,\n    inserted_at: ~U[2021-02-25 01:58:55Z],\n    name: nil,\n    number_of_pets: nil,\n    updated_at: ~U[2021-02-25 01:58:55Z]\n  },\n  %Hello.User{\n    __meta__: #Ecto.Schema.Metadata<:loaded, \"users\">,\n    bio: nil,\n    email: \"user2@example.com\",\n    id: 2,\n    inserted_at: ~U[2021-02-25 02:03:28Z],\n    name: nil,\n    number_of_pets: nil,\n    updated_at: ~U[2021-02-25 02:03:28Z]\n  }\n]\n```\n\nThat was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out.\n\n```elixir\niex> import Ecto.Query\nEcto.Query\n\niex> Repo.all(from u in User, select: u.email)\n[debug] QUERY OK source=\"users\" db=0.8ms queue=0.9ms idle=1634.0ms\nSELECT u0.\"email\" FROM \"users\" AS u0 []\n[\"user1@example.com\", \"user2@example.com\"]\n```\n\nFirst, we imported `Ecto.Query`, which imports the [`from/2`] macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example.\n\n```elixir\niex> Repo.one(from u in User, where: ilike(u.email, \"%1%\"),\n                               select: count(u.id))\n[debug] QUERY OK source=\"users\" db=1.6ms SELECT count(u0.\"id\") FROM \"users\" AS u0 WHERE (u0.\"email\" ILIKE '%1%') []\n1\n```\n\nNow we're starting to get a taste of Ecto's rich querying capabilities. We used [`Repo.one/2`] to fetch the count of all users with an email address containing `1`, and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses.\n\n```elixir\niex> Repo.all(from u in User, select: %{u.id => u.email})\n[debug] QUERY OK source=\"users\" db=0.9ms\nSELECT u0.\"id\", u0.\"email\" FROM \"users\" AS u0 []\n[\n  %{1 => \"user1@example.com\"},\n  %{2 => \"user2@example.com\"}\n]\n```\n\nThat little query packed a big punch. It both fetched all user emails from the database and efficiently built a map of the results in one go. You should browse the [Ecto.Query documentation](https://hexdocs.pm/ecto/Ecto.Query.html#content) to see the breadth of supported query features.\n\nIn addition to inserts, we can also perform updates and deletes with [`Repo.update/2`] and [`Repo.delete/2`] to update or delete a single schema. Ecto also supports bulk persistence with the [`Repo.insert_all/3`], [`Repo.update_all/3`], and [`Repo.delete_all/2`] functions.\n\nThere is quite a bit more that Ecto can do and we've only barely scratched the surface. With a solid Ecto foundation in place, we're now ready to continue building our app and integrate the web-facing application with our backend persistence. Along the way, we'll expand our Ecto knowledge and learn how to properly isolate our web interface from the underlying details of our system. Please take a look at the [Ecto documentation](https://hexdocs.pm/ecto/) for the rest of the story.\n\nIn our [Data modelling guides](contexts.html), we'll find out how to wrap up our Ecto access and business logic behind modules that group related functionality. We'll see how Phoenix helps us design maintainable applications, and we'll find out about other neat Ecto features along the way.\n\n## Mix tasks\n\nEcto comes with a collection of Mix tasks to make it easier to manage your database and your application. Here is a quick look into the most important ones.\n\n### `mix ecto.create`\n\nThis task will create the database specified by our application repositories, but we can pass in another repo if we want.\n\nHere's what it looks like in action.\n\n```console\n$ mix ecto.create\nThe database for Hello.Repo has been created.\n```\n\nThere are a few things that can go wrong with `ecto.create`. If our Postgres database doesn't have a \"postgres\" role (user), we'll get an error like this one.\n\n```console\n$ mix ecto.create\n** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role \"postgres\" does not exist\n```\n\nWe can fix this by creating the \"postgres\" role in the `psql` console with the permissions needed to log in and create a database.\n\n```console\n=# CREATE ROLE postgres LOGIN CREATEDB;\nCREATE ROLE\n```\n\nIf the \"postgres\" role does not have permission to log in to the application, we'll get this error.\n\n```console\n$ mix ecto.create\n** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  role \"postgres\" is not permitted to log in\n```\n\nTo fix this, we need to change the permissions on our \"postgres\" user to allow login.\n\n```console\n=# ALTER ROLE postgres LOGIN;\nALTER ROLE\n```\n\nIf the \"postgres\" role does not have permission to create a database, we'll get this error.\n\n```console\n$ mix ecto.create\n** (Mix) The database for Hello.Repo couldn't be created, reason given: ERROR:  permission denied to create database\n```\n\nTo fix this, we need to change the permissions on our \"postgres\" user in the `psql` console  to allow database creation.\n\n```console\n=# ALTER ROLE postgres CREATEDB;\nALTER ROLE\n```\n\nIf the \"postgres\" role is using a password different from the default \"postgres\", we'll get this error.\n\n```console\n$ mix ecto.create\n** (Mix) The database for Hello.Repo couldn't be created, reason given: psql: FATAL:  password authentication failed for user \"postgres\"\n```\n\nTo fix this, we can change the password in the environment specific configuration file. For the development environment the password used can be found at the bottom of the `config/dev.exs` file.\n\nFinally, if we happen to have another repo called `OurCustom.Repo` that we want to create the database for, we can run this.\n\n```console\n$ mix ecto.create -r OurCustom.Repo\nThe database for OurCustom.Repo has been created.\n```\n\n### `mix ecto.drop`\n\nThis task will drop the database specified in our repo. By default it will look for the repo named after our application (the one generated with our app unless we opted out of Ecto). It will not prompt us to check if we're sure we want to drop the database, so do exercise caution.\n\n```console\n$ mix ecto.drop\nThe database for Hello.Repo has been dropped.\n```\n\n### `mix ecto.gen.migration`\n\nMigrations are a programmatic, repeatable way to affect changes to a database schema. Phoenix generators take care of generating migrations for us whenever we create a new context or schema, but if you want to generate a migration from scratch, `mix ecto.gen.migration` has our back. Let's see an example.\n\nWe simply need to invoke the task with a `snake_case` version of the module name that we want. Preferably, the name will describe what we want the migration to do.\n\n```console\n$ mix ecto.gen.migration add_comments_table\n* creating priv/repo/migrations\n* creating priv/repo/migrations/20150318001628_add_comments_table.exs\n```\n\nNotice that the migration's filename begins with a string representation of the date and time the file was created.\n\nLet's take a look at the file `ecto.gen.migration` has generated for us at `priv/repo/migrations/20150318001628_add_comments_table.exs`.\n\n```elixir\ndefmodule Hello.Repo.Migrations.AddCommentsTable do\n  use Ecto.Migration\n\n  def change do\n  end\nend\n```\n\nNotice that there is a single function `change/0` which will handle both forward migrations and rollbacks. We'll define the schema changes that we want using Ecto's handy DSL, and Ecto will figure out what to do depending on whether we are rolling forward or rolling back. Very nice indeed.\n\nWhat we want to do is create a `comments` table with a `body` column, a `word_count` column, and timestamp columns for `inserted_at` and `updated_at`.\n\n```elixir\n...\ndef change do\n  create table(:comments) do\n    add :body, :string\n    add :word_count, :integer\n\n    timestamps(type: :utc_datetime)\n  end\nend\n...\n```\n\nFor more information on how to modify your database schema please refer to the\n[Ecto's migration DSL docs](https://hexdocs.pm/ecto_sql/Ecto.Migration.html).\nFor example, to alter an existing schema see the documentation on Ecto’s\n[`alter/2`](`Ecto.Migration.alter/2`) function.\n\nThat's it! We're ready to run our migration.\n\n### `mix ecto.migrate`\n\nOnce we have our migration module ready, we can simply run `mix ecto.migrate` to have our changes applied to the database. We have already used it earlier in this chapter, but let's take it for a spin once more for our newly generated migration.\n\n```console\n$ mix ecto.migrate\n[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward\n[info] create table comments\n[info] == Migrated in 0.1s\n```\n\nWhen we first run `ecto.migrate`, it will create a table for us called `schema_migrations`. This will keep track of all the migrations which we run by storing the timestamp portion of the migration's filename.\n\nHere's what the `schema_migrations` table looks like.\n\n```console\nhello_dev=# select * from schema_migrations;\nversion        |     inserted_at\n---------------+---------------------\n20250317170448 | 2025-03-17 21:07:26\n20250318001628 | 2025-03-18 01:45:00\n(2 rows)\n```\n\nWhen we roll back a migration, `mix ecto.rollback`, to be discussed next, we will remove the record representing this migration from `schema_migrations`.\n\nBy default, `ecto.migrate` will execute all pending migrations. We can exercise more control over which migrations we run by specifying some options when we run the task.\n\nWe can specify the number of pending migrations we would like to run with the `-n` or `--step` options.\n\n```console\n$ mix ecto.migrate -n 2\n[info] == Running Hello.Repo.Migrations.CreatePost.change/0 forward\n[info] create table posts\n[info] == Migrated in 0.0s\n[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 forward\n[info] create table comments\n[info] == Migrated in 0.0s\n```\n\nThe `--step` option will behave the same way.\n\n```console\n$ mix ecto.migrate --step 2\n```\n\nThe `--to` option will run all migrations up to and including given version.\n\n```console\n$ mix ecto.migrate --to 20150317170448\n```\n\n### `mix ecto.rollback`\n\nThe `mix ecto.rollback` task will reverse the last migration we have run, undoing the schema changes. `ecto.migrate` and `ecto.rollback` are mirror images of each other.\n\n```console\n$ mix ecto.rollback\n[info] == Running Hello.Repo.Migrations.AddCommentsTable.change/0 backward\n[info] drop table comments\n[info] == Migrated in 0.0s\n```\n\n`ecto.rollback` will handle the same options as `ecto.migrate`, so `-n`, `--step`, `-v`, and `--to` will behave as they do for `ecto.migrate`.\n\n[`cast/3`]: `Ecto.Changeset.cast/3`\n[`from/2`]: `Ecto.Query.from/2`\n[`Repo.delete_all/2`]: `c:Ecto.Repo.delete_all/2`\n[`Repo.delete/2`]: `c:Ecto.Repo.delete/2`\n[`Repo.insert_all/3`]: `c:Ecto.Repo.insert_all/3`\n[`Repo.insert/2`]: `c:Ecto.Repo.insert/2`\n[`Repo.one/2`]: `c:Ecto.Repo.one/2`\n[`Repo.update_all/3`]: `c:Ecto.Repo.update_all/3`\n[`Repo.update/2`]: `c:Ecto.Repo.update/2`\n[`timestamps/1`]: `Ecto.Migration.timestamps/1`\n"
  },
  {
    "path": "guides/howto/custom_error_pages.md",
    "content": "# Custom Error Pages\n\nNew Phoenix projects have two error views called `ErrorHTML` and `ErrorJSON`, which live in `lib/hello_web/controllers/`. The purpose of these views is to handle errors in a general way for each format, from one centralized location.\n\n## The Error Views\n\nFor new applications, the `ErrorHTML` and `ErrorJSON` views looks like this:\n\n```elixir\ndefmodule HelloWeb.ErrorHTML do\n  use HelloWeb, :html\n\n  # If you want to customize your error pages,\n  # uncomment the embed_templates/1 call below\n  # and add pages to the error directory:\n  #\n  #   * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex\n  #   * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex\n  #\n  # embed_templates \"error_html/*\"\n\n  # The default is to render a plain text page based on\n  # the template name. For example, \"404.html\" becomes\n  # \"Not Found\".\n  def render(template, _assigns) do\n    Phoenix.Controller.status_message_from_template(template)\n  end\nend\n\ndefmodule HelloWeb.ErrorJSON do\n  # If you want to customize a particular status code,\n  # you may add your own clauses, such as:\n  #\n  # def render(\"500.json\", _assigns) do\n  #   %{errors: %{detail: \"Internal Server Error\"}}\n  # end\n\n  # By default, Phoenix returns the status message from\n  # the template name. For example, \"404.json\" becomes\n  # \"Not Found\".\n  def render(template, _assigns) do\n    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}\n  end\nend\n```\n\nBefore we dive into this, let's see what the rendered `404 Not Found` message looks like in a browser. In the development environment, Phoenix will debug errors by default, showing us a very informative debugging page. What we want here, however, is to see what page the application would serve in production. In order to do that, we need to set `debug_errors: false` in `config/dev.exs`.\n\n```elixir\nimport Config\n\nconfig :hello, HelloWeb.Endpoint,\n  ...,\n  debug_errors: false,\n  code_reloader: true,\n  ...\n```\n\nAfter modifying our config file, we need to restart our server in order for this change to take effect. After restarting the server, let's go to [http://localhost:4000/such/a/wrong/path](http://localhost:4000/such/a/wrong/path) for a running local application and see what we get.\n\nOk, that's not very exciting. We get the bare string \"Not Found\", displayed without any markup or styling.\n\nThe first question is, where does that error string come from? The answer is right in `ErrorHTML`.\n\n```elixir\ndef render(template, _assigns) do\n  Phoenix.Controller.status_message_from_template(template)\nend\n```\n\nGreat, so we have this `render/2` function that takes a template and an `assigns` map, which we ignore. When you call `render(conn, :some_template)` from the controller, Phoenix first looks for a `some_template/1` function on the view module. If no function exists, it falls back to calling `render/2` with the template and format name, such as `\"some_template.html\"`.\n\nIn other words, to provide custom error pages, we could simply define a proper `render/2` function clause in `HelloWeb.ErrorHTML`.\n\n```elixir\n  def render(\"404.html\", _assigns) do\n    \"Page Not Found\"\n  end\n```\n\nBut we can do even better.\n\nPhoenix generates an `ErrorHTML` for us, but it doesn't give us a `lib/hello_web/controllers/error_html` directory. Let's create one now. Inside our new directory, let's add a template named `404.html.heex` and give it some markup – a mixture of our application layout and a new `<div>` with our message to the user.\n\n```heex\n<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\"/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n    <title>Welcome to Phoenix!</title>\n    <link rel=\"stylesheet\" href=\"/assets/css/app.css\"/>\n    <script defer type=\"text/javascript\" src=\"/assets/js/app.js\"></script>\n  </head>\n  <body>\n    <header>\n      <section class=\"container\">\n        <nav>\n          <ul>\n            <li><a href=\"https://hexdocs.pm/phoenix/overview.html\">Get Started</a></li>\n          </ul>\n        </nav>\n        <a href=\"https://phoenixframework.org/\" class=\"phx-logo\">\n          <img src=\"/images/logo.svg\" alt=\"Phoenix Framework Logo\"/>\n        </a>\n      </section>\n    </header>\n    <main class=\"container\">\n      <section class=\"phx-hero\">\n        <p>Sorry, the page you are looking for does not exist.</p>\n      </section>\n    </main>\n  </body>\n</html>\n```\n\nAfter you define the template file, remember to remove the equivalent `render/2` clause for that template, as otherwise the function overrides the template. Let's do so for the 404.html clause we have previously introduced in `lib/hello_web/controllers/error_html.ex`. We also need to tell Phoenix to embed our templates into the module:\n\n```diff\n+ embed_templates \"error_html/*\"\n\n- def render(\"404.html\", _assigns) do\n-  \"Page Not Found\"\n- end\n```\n\nNow, when we go back to [http://localhost:4000/such/a/wrong/path](http://localhost:4000/such/a/wrong/path), we should see a much nicer error page. It is worth noting that we did not render our `404.html.heex` template through our application layout, even though we want our error page to have the look and feel of the rest of our site. This is to avoid circular errors. For example, what happens if our application failed due to an error in the layout? Attempting to render the layout again will just trigger another error. So ideally we want to minimize the amount of dependencies and logic in our error templates, sharing only what is necessary.\n\n## Custom exceptions\n\nElixir provides a macro called `defexception/1` for defining custom exceptions. Exceptions are represented as structs, and structs need to be defined inside of modules.\n\nIn order to create a custom exception, we need to define a new module. Conventionally, this will have \"Error\" in the name. Inside that module, we need to define a new exception with `defexception/1`, the file `lib/hello_web.ex` seems like a good place for it.\n\n```elixir\ndefmodule HelloWeb.SomethingNotFoundError do\n  defexception [:message]\nend\n```\n\nYou can raise your new exception like this:\n\n```elixir\nraise HelloWeb.SomethingNotFoundError, \"oops\"\n```\n\nBy default, Plug and Phoenix will treat all exceptions as 500 errors. However, Plug provides a protocol called `Plug.Exception` where we are able to customize the status and add actions that exception structs can return on the debug error page.\n\nIf we wanted to supply a status of 404 for an `HelloWeb.SomethingNotFoundError` error, we could do it by defining an implementation for the `Plug.Exception` protocol like this, in `lib/hello_web.ex`:\n\n```elixir\ndefimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do\n  def status(_exception), do: 404\n  def actions(_exception), do: []\nend\n```\n\nAlternatively, you could define a `plug_status` field directly in the exception struct:\n\n```elixir\ndefmodule HelloWeb.SomethingNotFoundError do\n  defexception [:message, plug_status: 404]\nend\n```\n\nHowever, implementing the `Plug.Exception` protocol by hand can be convenient in certain occasions, such as when providing actionable errors.\n\n## Actionable errors\n\nException actions are functions that can be triggered from the error page, and they're basically a list of maps defining a `label` and a `handler` to be executed. As an example, Phoenix will display an error if you have pending migrations and will provide a button on the error page to perform the pending migrations.\n\nWhen `debug_errors` is `true`, they are rendered in the error page as a collection of buttons and follow the format of:\n\n```elixir\n[\n  %{\n    label: String.t(),\n    handler: {module(), function :: atom(), args :: []}\n  }\n]\n```\n\nIf we wanted to return some actions for an `HelloWeb.SomethingNotFoundError` we would implement `Plug.Exception` like this:\n\n```elixir\ndefimpl Plug.Exception, for: HelloWeb.SomethingNotFoundError do\n  def status(_exception), do: 404\n\n  def actions(_exception) do\n    [\n      %{\n        label: \"Run seeds\",\n        handler: {Code, :eval_file, [\"priv/repo/seeds.exs\"]}\n      }\n    ]\n  end\nend\n```\n"
  },
  {
    "path": "guides/howto/file_uploads.md",
    "content": "# File Uploads\n\nOne common task for web applications is uploading files. These files might be images, videos, PDFs, or files of any other type. In order to upload files through an HTML interface, we need a `file` input tag in a multipart form.\n\n> #### Looking for the LiveView Uploads guide? {: .neutral}\n>\n> This guide explains multipart HTTP file uploads via `Plug.Upload`.\n> For more information about LiveView file uploads, including direct-to-cloud external uploads on\n> the client, refer to the [LiveView Uploads guide](https://hexdocs.pm/phoenix_live_view/uploads.html).\n\nPlug provides a `Plug.Upload` struct to hold the data from the `file` input. A `Plug.Upload` struct will automatically appear in your request parameters if a user has selected a file when they submit the form.\n\nIn this guide you will do the following:\n\n  1.  Configure a multipart form\n\n  2. Add a file input element to the form\n\n  3. Verify your upload params\n\n  4. Manage your uploaded files\n\nIn the [`Contexts guide`](contexts.md), we generated an HTML resource for products. We can reuse the form we generated there in order to demonstrate how file uploads work in Phoenix. Please refer to that guide for instructions on generating the product resource you will be using here.\n\n### Configure a multipart form\n\nThe first thing you need to do is change your form into a multipart form. The `HelloWeb.CoreComponents` `form/1` component accepts a `multipart` attribute where you can specify this.\n\nHere is the form from `lib/hello_web/controllers/product_html/product_form.html.heex` with that change in place:\n\n```heex\n<.form :let={f} for={@changeset} action={@action} multipart>\n...\n```\n\n### Add a file input\n\nOnce you have a multipart form, you need a `file` input. Here's how you would do that, also in `product_form.html.heex`:\n\n```heex\n...\n  <.input field={f[:photo]} type=\"file\" label=\"Photo\" />\n\n  <.button>Save Product</.button>\n</.form>\n```\n\nWhen rendered, here is the HTML for the default `HelloWeb.CoreComponents` `input/1` component:\n\n```html\n<div>\n  <label for=\"product_photo\" class=\"block text-sm...\">Photo</label>\n  <input type=\"file\" name=\"product[photo]\" id=\"product_photo\" class=\"mt-2 block w-full...\">\n</div>\n```\n\nNote the `name` attribute of your `file` input. This will create the `\"photo\"` key in the `product_params` map which will be available in your controller action.\n\nThis is all from the form side. Now when users submit the form, a `POST` request will route to your `HelloWeb.ProductController` `create/2` action.\n\n> #### Should I add photo to my Ecto schema? {: .neutral}\n>\n> The photo input does not need to be part of your schema for it to come across in the `product_params`. If you want to persist any properties of the photo in a database, however, you would need to add it to your `Hello.Product` schema.\n\n### Verify your upload params\n\nSince you generated an HTML resource, you can now start your server with `mix phx.server`, visit [http://localhost:4000/products/new](http://localhost:4000/products/new), and create a new product with a photo.\n\nBefore you begin, add `IO.inspect product_params` to the top of your `ProductController.create/2` action in `lib/hello_web/controllers/product_controller.ex`. This will show the `product_params` in your development log so you can get a better sense of what's happening.\n\n```elixir\n...\n  def create(conn, %{\"product\" => product_params}) do\n    IO.inspect product_params\n...\n```\n\nWhen you do that, this is what your `product_params` will output in the log:\n\n```elixir\n%{\"title\" => \"Metaprogramming Elixir\", \"description\" => \"Write Less Code, Get More Done (and Have Fun!)\", \"price\" => \"15.000000\", \"views\" => \"0\",\n\"photo\" => %Plug.Upload{content_type: \"image/png\", filename: \"meta-cover.png\", path: \"/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1\"}}\n```\n\nYou have a `\"photo\"` key which maps to the pre-populated `Plug.Upload` struct representing your uploaded photo.\n\nTo make this easier to read, focus on the struct itself:\n\n```elixir\n%Plug.Upload{content_type: \"image/png\", filename: \"meta-cover.png\", path: \"/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/multipart-558399-917557-1\"}\n```\n\n`Plug.Upload` provides the file's content type, original filename, and path to the temporary file which Plug created for you. In this case, `\"/var/folders/_6/xbsnn7tx6g9dblyx149nrvbw0000gn/T//plug-1434/\"` is the directory created by Plug in which to put uploaded files. The directory will persist across requests. `\"multipart-558399-917557-1\"` is the name Plug gave to your uploaded file. If you had multiple `file` inputs and if the user selected photos for all of them, you would have multiple files scattered in temporary directories. Plug will make sure all the filenames are unique.\n\n> #### Plug.Upload files are temporary {: .info}\n>\n> Plug removes uploads from its directory as the request completes. If you need to do anything with this file, you need to do it before then (or [give it away](`Plug.Upload.give_away/3`), but that is outside the scope of this guide).\n\n### Manage your uploaded files\n\nOnce you have the `Plug.Upload` struct available in your controller, you can perform any operation on it you want. For example, you may want to do one or more of the following:\n\n* Check to make sure the file exists with `File.exists?/1`\n\n* Copy the file somewhere else on the filesystem with `File.cp/2`\n\n* Give the file away to another Elixir process with `Plug.Upload.give_away/3`\n\n* Send it to S3 with an external library\n\n* Send it back to the client with `Plug.Conn.send_file/5`\n\nIn a production system, you may want to copy the file to a root directory, such as `/media`. When doing so, it is important to guarantee the names are unique. For instance, if you are allowing users to upload product cover images, you could use the product id to generate a unique name:\n\n```elixir\nif upload = product_params[\"photo\"] do\n  extension = Path.extname(upload.filename)\n  File.cp(upload.path, \"/media/#{product.id}-cover#{extension}\")\nend\n```\n\nThen a `Plug.Static` plug could be added in your `lib/my_app_web/endpoint.ex` to serve the files at `\"/media\"`:\n\n```elixir\nplug Plug.Static, at: \"/uploads\", from: \"/media\"\n```\n\nThe uploaded file can now be accessed from your browsers using a path such as `\"/uploads/1-cover.jpg\"`. In practice, there are other concerns you want to handle when uploading files, such validating extensions, encoding names, and so on. Many times, using a library that already handles such cases is preferred.\n\nFinally, notice that when there is no data from the `file` input, you get neither the `\"photo\"` key nor a `Plug.Upload` struct. Here are the `product_params` from the log.\n\n```elixir\n%{\"title\" => \"Metaprogramming Elixir\", \"description\" => \"Write Less Code, Get More Done (and Have Fun!)\", \"price\" => \"15.000000\", \"views\" => \"0\"}\n```\n\n## Configuring upload limits\n\nThe conversion from the data being sent by the form to an actual `Plug.Upload` is done by the `Plug.Parsers` plug which you can find inside `HelloWeb.Endpoint`:\n\n```elixir\n# lib/hello_web/endpoint.ex\nplug Plug.Parsers,\n  parsers: [:urlencoded, :multipart, :json],\n  pass: [\"*/*\"],\n  json_decoder: Phoenix.json_library()\n```\n\nBesides the options above, `Plug.Parsers` accepts other options to control data upload:\n\n  * `:length` - sets the max body length to read, defaults to `8_000_000` bytes\n  * `:read_length` - set the amount of bytes to read at one time, defaults to `1_000_000` bytes\n  * `:read_timeout` - set the timeout for each chunk received, defaults to `15_000` ms\n\nThe first option configures the maximum data allowed. The remaining ones configure how much data we expect to read and its frequency. If the client cannot push data fast enough, the connection will be terminated. Phoenix ships with reasonable defaults but you may want to customize it under special circumstances, for example, if you are expecting really slow clients to send large chunks of data.\n\nIt is also worth pointing out those limits are important as a security mechanism. For example, if you don't set a limit for data upload, attackers could open up thousands of connections to your application and send one byte every 2 minutes, which would take very long to complete while using up all connections to your server. The limits above expect at least a reasonable amount of progress, making attackers' lives a bit harder.\n"
  },
  {
    "path": "guides/howto/swapping_databases.md",
    "content": "# Swapping Databases\n\nPhoenix applications are configured to use PostgreSQL by default, but what if we want to use another database, such as MySQL? In this guide, we'll walk through changing that default whether we are about to create a new application, or whether we have an existing one configured for PostgreSQL.\n\n## Using `phx.new`\n\nIf we are about to create a new application, configuring our application to use MySQL is easy. We can simply pass the `--database mysql` flag to `phx.new` and everything will be configured correctly.\n\n```console\n$ mix phx.new hello_phoenix --database mysql\n```\n\nThis will set up all the correct dependencies and configuration for us automatically. Once we install those dependencies with `mix deps.get`, we'll be ready to begin working with Ecto in our application.\n\n## Within an existing application\n\nIf we have an existing application, all we need to do is switch adapters and make some small configuration changes.\n\nTo switch adapters, we need to remove the Postgrex dependency and add a new one for MyXQL instead.\n\nLet's open up our `mix.exs` file and do that now.\n\n```elixir\ndefmodule HelloPhoenix.MixProject do\n  use Mix.Project\n\n  ...\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      {:phoenix, \"~> 1.4.0\"},\n      {:phoenix_ecto, \"~> 4.4\"},\n      {:ecto_sql, \"~> 3.13\"},\n      {:myxql, \">= 0.0.0\"},\n      ...\n    ]\n  end\nend\n```\n\nNext, we need to configure our adapter to use the default MySQL credentials by updating `config/dev.exs`:\n\n```elixir\nconfig :hello_phoenix, HelloPhoenix.Repo,\n  username: \"root\",\n  password: \"\",\n  database: \"hello_phoenix_dev\"\n```\n\nIf we have an existing configuration block for our `HelloPhoenix.Repo`, we can simply change the values to match our new ones. You also need to configure the correct values in the `config/test.exs` and `config/runtime.exs` files as well.\n\nThe last change is to open up `lib/hello_phoenix/repo.ex` and make sure to set the `:adapter` to `Ecto.Adapters.MyXQL`.\n\nNow all we need to do is fetch our new dependency, and we'll be ready to go.\n\n```console\n$ mix deps.get\n```\n\nWith our new adapter installed and configured, we're ready to create our database.\n\n```console\n$ mix ecto.create\n```\n\nThe database for HelloPhoenix.Repo has been created.\nWe're also ready to run any migrations, or do anything else with Ecto that we may choose.\n\n```console\n$ mix ecto.migrate\n[info] == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward\n[info] create table users\n[info] == Migrated in 0.2s\n```\n\n## Other options\n\nWhile Phoenix uses the `Ecto` project to interact with the data access layer, there are many other data access options, some even built into the Erlang standard library. [ETS](https://www.erlang.org/doc/man/ets.html) – available in Ecto via [`etso`](https://hexdocs.pm/etso/) – and [DETS](https://www.erlang.org/doc/man/dets.html) are key-value data stores built into [Erlang/OTP](https://www.erlang.org/doc/). Both Elixir and Erlang also have a number of libraries for working with a wide range of popular data stores.\n\nThe data world is your oyster, but we won't be covering these options in these guides.\n"
  },
  {
    "path": "guides/howto/using_ssl.md",
    "content": "# Using SSL\n\nTo prepare an application to serve requests over SSL, we need to add a little bit of configuration and two environment variables. In order for SSL to actually work, we'll need a key file and certificate file from a certificate authority. The environment variables that we'll need are paths to those two files.\n\nThe configuration consists of a new `https:` key for our endpoint whose value is a keyword list of port, path to the key file, and path to the cert (PEM) file. If we add the `otp_app:` key whose value is the name of our application, Plug will begin to look for them at the root of our application. We can then put those files in our `priv` directory and set the paths to `priv/our_keyfile.key` and `priv/our_cert.crt`.\n\nHere's an example configuration from `config/runtime.exs`.\n\n```elixir\nimport Config\n\nconfig :hello, HelloWeb.Endpoint,\n  http: [port: String.to_integer(System.get_env(\"PORT\"))],\n  url: [host: \"example.com\"],\n  cache_static_manifest: \"priv/static/cache_manifest.json\",\n  https: [\n    port: 443,\n    cipher_suite: :strong,\n    otp_app: :hello,\n    keyfile: System.get_env(\"SOME_APP_SSL_KEY_PATH\"),\n    certfile: System.get_env(\"SOME_APP_SSL_CERT_PATH\"),\n    # OPTIONAL Key for intermediate certificates:\n    cacertfile: System.get_env(\"INTERMEDIATE_CERTFILE_PATH\")\n  ]\n\n```\n\nWithout the `otp_app:` key, we need to provide absolute paths to the files wherever they are on the filesystem in order for Plug to find them.\n\n```elixir\nPath.expand(\"../../../some/path/to/ssl/key.pem\", __DIR__)\n```\n\nThe options under the `https:` key are passed to the Plug adapter, typically `Bandit`, which in turn uses `Plug.SSL` to select the TLS socket options. Please refer to the documentation for [Plug.SSL.configure/1](https://hexdocs.pm/plug/Plug.SSL.html#configure/1) for more information on the available options and their defaults. The [Plug HTTPS Guide](https://hexdocs.pm/plug/https.html) and the [Erlang/OTP ssl](https://www.erlang.org/doc/man/ssl.html) documentation also provide valuable information.\n\n## SSL in Development\n\nIf you would like to use HTTPS in development, a self-signed certificate can be generated by running: `mix phx.gen.cert`. This requires Erlang/OTP 20 or later.\n\nWith your self-signed certificate, your development configuration in `config/dev.exs` can be updated to run an HTTPS endpoint:\n\n```elixir\nconfig :my_app, MyAppWeb.Endpoint,\n  ...\n  https: [\n    port: 4001,\n    cipher_suite: :strong,\n    keyfile: \"priv/cert/selfsigned_key.pem\",\n    certfile: \"priv/cert/selfsigned.pem\"\n  ]\n```\n\nThis can replace your `http` configuration, or you can run HTTP and HTTPS servers on different ports.\n\n## Force SSL\n\nIn many cases, you'll want to force all incoming requests to use SSL by redirecting HTTP to HTTPS. This can be accomplished by setting the `:force_ssl` option in your endpoint configuration. It expects a list of options which are forwarded to `Plug.SSL`. By default, it sets the \"strict-transport-security\" header in HTTPS requests, forcing browsers to always use HTTPS. For example:\n\n```elixir\nconfig :my_app, MyAppWeb.Endpoint,\n  force_ssl: [rewrite_on: [:x_forwarded_proto]]\n```\n\n\nIf an unsafe (HTTP) request is sent, the above will redirect to the HTTPS version using the `:host` specified in the `:url` configuration. The `rewrite_on:` key specifies the HTTP header used by a reverse proxy or load balancer in front of the application to indicate whether the request was received over HTTP or HTTPS.\n\nTo dynamically redirect to the `host` of the current request, set `:host` in the `:force_ssl` configuration to `nil`. In such cases, you must also set `x_forwarded_host` and `x_forwarded_port` if running behind a proxy:\n\n```elixir\nconfig :my_app, MyAppWeb.Endpoint,\n  force_ssl: [rewrite_on: [:x_forwarded_proto, :x_forwarded_host, :x_forwarded_port], host: nil]\n```\n\nFurthermore, keep in mind `force_ssl` will redirect all requests, except the ones coming from localhost. If your application is doing probeness checks using another origin, such as \"127.0.0.1\" or an internal IP address, you may need to explicitly exclude them from `force_ssl`:\n\n```elixir\nconfig :my_app, MyAppWeb.Endpoint,\n  force_ssl: [rewrite_on: [:x_forwarded_proto], exclude: [\"localhost\", \"127.0.0.1\"]]\n```\n\nIt is important to note that `force_ssl:` is a *compile* time config, so it normally is set in `prod.exs`, it will not work when set from `runtime.exs`.\n\nFor more information on the implications of offloading TLS to an external element, in particular relating to secure cookies, refer to the [Plug HTTPS Guide](https://hexdocs.pm/plug/https.html#offloading-tls). Keep in mind that the options passed to `Plug.SSL` in that document should be set using the `force_ssl:` endpoint option in a Phoenix application.\n\n## HSTS\n\nHSTS, short for 'HTTP Strict-Transport-Security', is a mechanism that allows websites to declare themselves as accessible exclusively through a secure connection (HTTPS). It was introduced to prevent man-in-the-middle attacks that strip SSL/TLS encryption. HSTS causes web browsers to redirect from HTTP to HTTPS and to refuse to connect unless the connection uses SSL/TLS.\n\nWith `force_ssl: [hsts: true]` set, the `Strict-Transport-Security` header is added with a max-age that defines the duration for which the policy is valid. Modern web browsers will respond to this by redirecting from HTTP to HTTPS, among other consequences. [RFC6797](https://tools.ietf.org/html/rfc6797), which defines HSTS, also specifies that **the browser should keep track of a host's policy and apply it until it expires.** It further specifies that **traffic on any port other than 80 is assumed to be encrypted** as per the policy.\n\nWhile HSTS is recommended in production, it can lead to unexpected behavior when accessing applications on localhost. For instance, accessing an application with HSTS enabled at `https://localhost:4000` leads to a situation where all subsequent traffic from localhost, except for port 80, is expected to be encrypted. This can disrupt traffic to other local servers or proxies running on your computer that are unrelated to your Phoenix application and may not support encrypted traffic.\n\nIf you inadvertently enable HSTS for localhost, you may need to reset your browser's cache before it will accept HTTP traffic from localhost again.\n\nFor Chrome:\n1. Open the Developer Tools Panel.\n2. Click and hold the reload icon next to the address bar to reveal a dropdown menu.\n3. Select \"Empty Cache and Hard Reload\".\n\nFor Safari:\n1. Clear your browser cache.\n2. Remove the entry from `~/Library/Cookies/HSTS.plist` or delete the file entirely.\n3. Restart Safari.\n\nFor other browsers, please consult the documentation for HSTS.\n\nAlternatively, setting the `:expires` option on `force_ssl` to `0` should expire the entry and disable HSTS.\n\nFor more information on HSTS options, see [Plug.SSL](https://hexdocs.pm/plug/Plug.SSL.html).\n"
  },
  {
    "path": "guides/howto/writing_a_channels_client.md",
    "content": "# Writing a Channels Client\n\nClient libraries for Phoenix Channels already exist in [several languages](https://hexdocs.pm/phoenix/channels.html#client-libraries), but if you want to write your own, this guide should get you started.\nIt may also be useful as a guide for manual testing with a WebSocket client.\n\n## Overview\n\nBecause WebSockets are bidirectional, messages can flow in either direction at any time.\nFor this reason, clients typically use callbacks to handle incoming messages whenever they come.\n\nA client must join at least one topic to begin sending and receiving messages, and may join any number of topics using the same connection.\n\n## Connecting\n\nTo establish a WebSocket connection to Phoenix Channels, first make note of the `socket` declaration in the application's `Endpoint` module.\nFor example, if you see: `socket \"/mobile\", MyAppWeb.MobileSocket`, the path for the initial HTTP request is:\n\n    [host]:[port]/mobile/websocket?vsn=2.0.0\n\nPassing `&vsn=2.0.0` specifies `Phoenix.Socket.V2.JSONSerializer`, which is built into Phoenix, and which expects and returns messages in the form of lists.\n\nYou also need to include [the standard header fields for upgrading an HTTP request to a WebSocket connection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism) or use an HTTP library that handles this for you; in Elixir, [mint_web_socket](https://hex.pm/packages/mint_web_socket) is an example.\n\nOther parameters or headers may be expected or required by the specific `connect/3` function in the application's socket module (in the example above, `MyAppWeb.MobileSocket.connect/3`).\n\n## Message Format\n\nThe message format is determined by the serializer configured for the application.\nFor these examples, `Phoenix.Socket.V2.JSONSerializer` is assumed.\n\nThe general format for messages a client sends to a Phoenix Channel is as follows:\n\n```\n[join_reference, message_reference, topic_name, event_name, payload]\n```\n\n- The `join_reference` is also chosen by the client and should also be a unique value. It only needs to be sent for a `\"phx_join\"` event; for other messages it can be `null`. It is used as a message reference for `push` messages from the server, meaning those that are not replies to a specific client message. For example, imagine something like \"a new user just joined the chat room\".\n- The `message_reference` is chosen by the client and should be a unique value. The server includes it in its reply so that the client knows which message the reply is for.\n- The `topic_name` must be a known topic for the socket endpoint, and a client must join that topic before sending any messages on it.\n- The `event_name` must match the first argument of a `handle_in` function on the server channel module.\n- The `payload` should be a map and is passed as the second argument to that `handle_in` function.\n\nThere are three events that are understood by every Phoenix application.\n\nFirst, `phx_join` is used join a channel. For example, to join the `miami:weather` channel:\n\n```json\n[\"0\", \"0\", \"miami:weather\", \"phx_join\", {\"some\": \"param\"}]\n```\n\nSecond, `phx_leave` is used to leave a channel. For example, to leave the `miami:weather` channel:\n\n```json\n[null, \"1\", \"miami:weather\", \"phx_leave\", {}]\n```\n\nThird, `heartbeat` is used to maintain the WebSocket connection. For example:\n\n\n```json\n[null, \"2\", \"phoenix\", \"heartbeat\", {}]\n```\n\nThe `heartbeat` message is only needed when no other messages are being sent and prevents Phoenix from closing the connection; the exact `:timeout` is configured in the application's `Endpoint` module.\n\nOther allowed messages depend on the Phoenix application.\n\nFor example, if the Channel serving the `miami:weather` can handle a `report_emergency` event:\n\n```elixir\ndef handle_in(\"report_emergency\", payload, socket) do\n  MyApp.Emergencies.report(payload) # or whatever\n  {:reply, :ok, socket}\nend\n```\n\n...a client could send:\n\n```json\n[null, \"3\", \"miami:weather\", \"report_emergency\", {\"category\": \"sharknado\"}]\n```\n"
  },
  {
    "path": "guides/introduction/community.md",
    "content": "# Community\n\nThe Elixir and Phoenix communities are friendly and welcoming. All questions and comments are valuable, so please come join the discussion!\n\nThere are a number of places to connect with community members at all experience levels.\n\n  * We're on Libera IRC in the [\\#elixir](https://web.libera.chat/?channels=#elixir) channel.\n\n  * Feel free to join and check out the #phoenix channel on [Discord](https://discord.gg/elixir).\n\n  * Read about [bug reports](https://github.com/phoenixframework/phoenix/blob/main/CONTRIBUTING.md#bug-reports) or open an issue in the Phoenix [issue tracker](https://github.com/phoenixframework/phoenix/issues).\n\n  * Ask or answer questions about Phoenix on [Elixir Forum](https://elixirforum.com/c/phoenix-forum) or [Stack Overflow](https://stackoverflow.com/questions/tagged/phoenix-framework).\n\n  * Follow the Phoenix Framework on [Twitter](https://twitter.com/elixirphoenix).\n\n## Security\n\nFor information about security patches and how to report a vulnerability in Phoenix, see the [security policy.](https://github.com/phoenixframework/phoenix/security)\n\nTo learn about how to secure a web application written with Phoenix, see the [security documentation page.](/guides/security.md)\n\nThe Erlang Ecosystem Foundation also publishes in-depth documents which are relevant for Erlang, Elixir, and Phoenix developers. These include:\n\n  * [Web Application Security Best Practices for BEAM languages](https://security.erlef.org/web_app_security_best_practices_beam/)\n\n  * [Secure Coding and Deployment Hardening Guidelines](https://security.erlef.org/secure_coding_and_deployment_hardening/)\n\n## Books\n\n  * [Programming Phoenix LiveView - Interactive Elixir Web Programming Without Writing Any JavaScript - 2023 (by Bruce Tate and Sophie DeBenedetto)](https://pragprog.com/titles/liveview/programming-phoenix-liveview/)\n\n  * [Phoenix Tutorial (Phoenix 1.6)](https://thephoenixtutorial.org/) - [Free to read online](https://thephoenixtutorial.org/book)\n\n  * [Real-Time Phoenix - Build Highly Scalable Systems with Channels (by Stephen Bussey - 2020)](https://pragprog.com/titles/sbsockets/real-time-phoenix/)\n\n  * [Programming Phoenix 1.4 (by Bruce Tate, Chris McCord, and José Valim - 2019)](https://pragprog.com/titles/phoenix14/programming-phoenix-1-4/)\n\n  * [Phoenix in Action (by Geoffrey Lessel - 2019)](https://manning.com/books/phoenix-in-action)\n\n  * [Phoenix Inside Out - Book Series (by Shankar Dhanasekaran - 2017)](https://shankardevy.com/phoenix-book/). First book of the series Mastering Phoenix Framework is [free to read online](https://shankardevy.com/phoenix-inside-out-mpf/)\n\n  * [Functional Web Development with Elixir, OTP, and Phoenix Rethink the Modern Web App (by Lance Halvorsen - 2017)](https://pragprog.com/titles/lhelph/functional-web-development-with-elixir-otp-and-phoenix/)\n\n## Screencasts/Courses\n\n  * [Full-Stack Phoenix Course (by The Pragmatic Studio - 2025)](https://pragmaticstudio.com/courses/phoenix)\n\n  * [Free Bootcamp: Fullstack Elixir and Phoenix (by TechSchool - 2024)](https://techschool.dev/en/bootcamps/fullstack-elixir-and-phoenix)\n\n  * [Learn Phoenix LiveView (by George Arrowsmith - 2024)](https://phoenixliveview.com)\n\n  * [Phoenix LiveView Course (by The Pragmatic Studio - 2023)](https://pragmaticstudio.com/courses/phoenix-liveview)\n\n  * [Build It With Phoenix video course (by Geoffrey Lessel - 2023)](https://builditwithphoenix.com)\n\n  * [Free Crash Course: Phoenix LiveView (by Productive Programmer - 2023)](https://www.productiveprogrammer.com/learn-phoenix-liveview-free)\n\n  * [Phoenix on Rails: Elixir and Phoenix for Ruby on Rails developers (by George Arrowsmith - 2023)](https://phoenixonrails.com)\n\n  * [Groxio LiveView: Self Study Program (by Bruce Tate - 2020)](https://grox.io/language/liveview/course)\n\n  * [Alchemist Camp: Learn Elixir and Phoenix by building (2018-2022)](https://alchemist.camp/episodes)\n\n  * [The Complete Elixir and Phoenix Bootcamp Master Functional Programming Techniques with Elixir and Phoenix while Learning to Build Compelling Web Applications (by Stephen Grider - 2017)](https://www.udemy.com/the-complete-elixir-and-phoenix-bootcamp-and-tutorial/)\n\n  * [Discover Elixir & Phoenix (by Tristan Edwards - 2017)](https://www.ludu.co/course/discover-elixir-phoenix)\n\n  * [Phoenix Framework Tutorial (by Tensor Programming - 2017)](https://www.youtube.com/watch?v=irDC1nWKhZ8&index=6&list=PLJbE2Yu2zumAgKjSPyFtvYjP5LqgzafQq)\n\n  * [Getting Started with Phoenix (by Pluralsight - 2017)](https://www.pluralsight.com/courses/phoenix-getting-started)\n\n  * [LearnPhoenix.tv: Learn how to Build Fast, Dependable Web Apps with Phoenix (2017)](https://www.learnphoenix.tv/)\n\n  * [LearnPhoenix.io: Build Scalable, Real-Time Apps with Phoenix, React, and React Native (2016)](https://www.learnphoenix.io/)\n"
  },
  {
    "path": "guides/introduction/installation.md",
    "content": "# Installation\n\nIn order to build a Phoenix application, we will need a few dependencies installed in our Operating System:\n\n  * the Erlang VM and the Elixir programming language\n  * a database - Phoenix recommends PostgreSQL, but you can pick others or not use a database at all\n  * and other optional packages.\n\nPlease take a look at this list and make sure to install anything necessary for your system. Having dependencies installed in advance can prevent frustrating problems later on.\n\nIf you just want to get started quickly, the [Up and Running](up_and_running.md) page includes a link to Phoenix Express, which will get Erlang, Elixir and Phoenix installed and running in seconds.\n\n## Elixir 1.15 or later\n\nPhoenix is written in Elixir, and our application code will also be written in Elixir. We won't get far in a Phoenix app without it! The Elixir site maintains a great [Installation Page](https://elixir-lang.org/install.html) to help.\n\n## Erlang 24 or later\n\nElixir code compiles to Erlang byte code to run on the Erlang virtual machine. Without Erlang, Elixir code has no virtual machine to run on, so we need to install Erlang as well.\n\nWhen we install Elixir using instructions from the Elixir [Installation Page](https://elixir-lang.org/install.html),  we will usually get Erlang too. If Erlang was not installed along with Elixir, please see the [Erlang Instructions](https://elixir-lang.org/install.html#installing-erlang) section of the Elixir Installation Page for instructions.\n\n## Phoenix\n\nTo check that we are on Elixir 1.15 and Erlang 24 or later, run:\n\n```console\nelixir -v\nErlang/OTP 24 [erts-12.0] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [dtrace]\n\nElixir 1.15.0\n```\n\nOnce we have Elixir and Erlang, we are ready to install the Phoenix application generator:\n\n```console\n$ mix archive.install hex phx_new\n```\n\nThe `phx.new` generator is now available to generate new applications in the next guide, called [Up and Running](up_and_running.html). The flags mentioned below are command line options to the generator; see all available options by calling `mix help phx.new`.\n\n## PostgreSQL\n\nPostgreSQL is a relational database server. Phoenix configures applications to use it by default, but we can switch to MySQL, MSSQL, or SQLite3 by passing the `--database` flag when creating a new application.\n\nIn order to talk to databases, Phoenix applications use another Elixir package, called [Ecto](https://github.com/elixir-ecto/ecto). If you don't plan to use databases in your application, you can pass the `--no-ecto` flag.\n\nHowever, if you are just getting started with Phoenix, we recommend you to install PostgreSQL and make sure it is running. The PostgreSQL wiki has [installation guides](https://wiki.postgresql.org/wiki/Detailed_installation_guides) for a number of different systems.\n\n## inotify-tools (for Linux users)\n\nPhoenix provides a very handy feature called Live Reloading. As you change your views or your assets, it automatically reloads the page in the browser. In order for this functionality to work, you need a filesystem watcher.\n\nmacOS and Windows users already have a filesystem watcher, but Linux users must install inotify-tools. Please consult the [inotify-tools wiki](https://github.com/rvoicilas/inotify-tools/wiki) for distribution-specific installation instructions.\n\n## Summary\n\nAt the end of this section, you must have installed Elixir, Hex, Phoenix, and PostgreSQL. Now that we have everything installed, let's create our first Phoenix application and get [up and running](up_and_running.html).\n"
  },
  {
    "path": "guides/introduction/overview.md",
    "content": "# Overview\n\nPhoenix is a web development framework written in Elixir which implements the server-side Model View Controller (MVC) pattern. Many of its components and concepts will seem familiar to those of us with experience in other web frameworks like Ruby on Rails or Python's Django.\n\nPhoenix provides the best of both worlds - high developer productivity _and_ high application performance. It also has some interesting new twists like channels for implementing realtime features and pre-compiled templates for blazing speed.\n\nIf you are already familiar with Elixir, great! If not, there are a number of places to learn. The [Elixir guides](https://hexdocs.pm/elixir/introduction.html) and the [Elixir learning resources page](https://elixir-lang.org/learning.html) are two great places to start.\n\nThe guides that you are currently looking at provide an overview of all parts that make Phoenix. Here is a rundown of what they provide:\n\n  * Introduction - the guides you are currently reading. They will cover how to get your first application up and running\n\n  * Guides - in-depth guides covering the main components in Phoenix and Phoenix applications\n\n  * Data modelling - building the initial features of an e-commerce application to learn about more data modelling with Phoenix\n\n  * Authn and Authz - learn how to use the tools Phoenix provides for authentication and authorization\n\n  * Real-time components - in-depth guides covering Phoenix's built-in real-time components\n\n  * Testing - in-depth guides about testing\n\n  * Deployment - in-depth guides about deployment\n\n  * How-to's - a collection of articles on how to achieve certain things with Phoenix\n\nIf you would prefer to read these guides as an EPUB, [click here!](Phoenix.epub)\n\nNote, these guides are not a step-by-step introduction to Phoenix. If you want a more structured approach to learning the framework, we have a large community and many books, courses, and screencasts available. See [our community page](community.html) for a complete list.\n\n[Let's get Phoenix installed](installation.html).\n"
  },
  {
    "path": "guides/introduction/packages_glossary.md",
    "content": "# Packages Glossary\n\nBy default, Phoenix applications depend on several packages with different purposes.\nThis page is a quick reference of the different packages you may work with as a Phoenix\ndeveloper.\n\nThe main packages are:\n\n  * [Ecto](https://hexdocs.pm/ecto) - a language integrated query and\n    database wrapper\n\n  * [Phoenix](https://hexdocs.pm/phoenix) - the Phoenix web framework\n    (these docs)\n\n  * [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view) - build rich,\n    real-time user experiences with server-rendered HTML. The LiveView\n    project also defines [`Phoenix.Component`](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) and\n    [the HEEx template engine](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2),\n    used for rendering HTML content in both regular and real-time applications\n\n  * [Plug](https://hexdocs.pm/plug) - specification and conveniences for\n    building composable modules web applications. This is the package\n    responsible for the connection abstraction and the regular request-\n    response life-cycle\n\nYou will also work with the following:\n\n  * [ExUnit](https://hexdocs.pm/ex_unit) - Elixir's built-in test framework\n\n  * [Gettext](https://hexdocs.pm/gettext) - internationalization and\n    localization through [`gettext`](https://www.gnu.org/software/gettext/)\n\n  * [Swoosh](https://hexdocs.pm/swoosh) - a library for composing,\n    delivering and testing emails, also used by `mix phx.gen.auth`\n\nWhen peeking under the covers, you will find these libraries play\nan important role in Phoenix applications:\n\n  * [Phoenix HTML](https://hexdocs.pm/phoenix_html) - building blocks\n    for working with HTML and forms safely\n\n  * [Phoenix Ecto](https://hex.pm/packages/phoenix_ecto) - plugs and\n    protocol implementations for using phoenix with ecto\n\n  * [Phoenix PubSub](https://hexdocs.pm/phoenix_pubsub) - a distributed\n    pub/sub system with presence support\n\nWhen it comes to instrumentation and monitoring, check out:\n\n  * [Phoenix LiveDashboard](https://hexdocs.pm/phoenix_live_dashboard) -\n    real-time performance monitoring and debugging tools for Phoenix\n    developers\n\n  * [Telemetry Metrics](https://hexdocs.pm/telemetry_metrics) - common\n    interface for defining metrics based on Telemetry events\n"
  },
  {
    "path": "guides/introduction/up_and_running.md",
    "content": "# Up and Running\n\nThere are two mechanisms to start a new Phoenix application: the express option, supported on some OSes, and via `mix phx.new`. Let's check it out.\n\n## Phoenix Express\n\nA single command will get you up and running in seconds:\n\nFor macOS/Ubuntu:\n\n```bash\n$ curl https://new.phoenixframework.org/myapp | sh\n```\n\nFor Windows PowerShell:\n\n```bash\ncurl.exe -fsSO https://new.phoenixframework.org/myapp.bat; .\\myapp.bat\n```\n\nThe above will install Erlang, Elixir, and Phoenix, and generate a fresh Phoenix application. It will also automatically pick one of PostgreSQL or MySQL as the database, and fallback to SQLite if none of them are available. Once the command above completes, it will open up a Phoenix application, with the steps necessary to complete your installation. Note your Phoenix application name is taken from the path.\n\nIf your operating system is not supported, or the command above fails, don't fret! You can still start your Phoenix application using `mix phx.new`.\n\n## Via `mix phx.new`\n\nIn order to create a new Phoenix application, you will need to install Erlang, Elixir, and Phoenix. See the [Installation Guide](installation.html) for more information. If you share your application with someone, they will also need to follow the Installation Guide steps to set it all up.\n\nOnce you are ready, you can run `mix phx.new` from any directory in order to bootstrap our Phoenix application. Phoenix will accept either an absolute or relative path for the directory of our new project. Assuming that the name of our application is `hello`, let's run the following command:\n\n```console\n$ mix phx.new hello\n```\n\n> By default, `mix phx.new` includes a number of optional dependencies, for example:\n>\n> - [Ecto](ecto.html) for communicating with a data store, such as PostgreSQL, MySQL, and others. You can skip this with `--no-ecto`.\n>\n> - [Phoenix.HTML](https://hexdocs.pm/phoenix_html/Phoenix.HTML.html), [TailwindCSS](https://tailwindcss.com), and [Esbuild](https://esbuild.github.io) for HTML applications. You can skip them with the `--no-html` and `--no-assets` flags.\n>\n> - [Phoenix.LiveView](https://hexdocs.pm/phoenix_live_view/) for building realtime and interactive web applications. You can skip this with `--no-live`.\n>\n> Run `mix help phx.new` to learn all options.\n\n```console\nmix phx.new hello\n* creating hello/config/config.exs\n* creating hello/config/dev.exs\n* creating hello/config/prod.exs\n...\n\nFetch and install dependencies? [Yn]\n```\n\nPhoenix generates the directory structure and all the files we will need for our application.\n\n> Phoenix promotes the usage of git as version control software: among the generated files we find a `.gitignore`. We can `git init` our repository, and immediately add and commit all that hasn't been marked ignored.\n\nWhen it's done, it will ask us if we want it to install our dependencies for us. Let's say yes to that.\n\n```console\nFetch and install dependencies? [Yn] Y\n* running mix deps.get\n* running mix assets.setup\n* running mix deps.compile\n\nWe are almost there! The following steps are missing:\n\n    $ cd hello\n\nThen configure your database in config/dev.exs and run:\n\n    $ mix ecto.create\n\nStart your Phoenix app with:\n\n    $ mix phx.server\n\nYou can also run your app inside IEx (Interactive Elixir) as:\n\n    $ iex -S mix phx.server\n```\n\nOnce our dependencies are installed, the task will prompt us to change into our project directory and start our application.\n\nPhoenix assumes that our PostgreSQL database will have a `postgres` user account with the correct permissions and a password of \"postgres\". Let's give it a try.\n\nFirst, we'll `cd` into the `hello/` directory we've just created:\n\n```console\n$ cd hello\n```\n\nNow we'll create our database:\n\n```console\n$ mix ecto.create\nCompiling 13 files (.ex)\nGenerated hello app\nThe database for Hello.Repo has been created\n```\n\nIn case the database could not be created, see [our Ecto section on Mix tasks](ecto.html#mix-tasks) or run `mix help ecto.create`.\n\nAnd finally, we'll start the Phoenix server:\n\n```console\n$ mix phx.server\n[info] Running HelloWeb.Endpoint with Bandit 1.5.7 at 127.0.0.1:4000 (http)\n[info] Access HelloWeb.Endpoint at http://localhost:4000\n[watch] build finished, watching for changes...\n...\n```\n\nIf we choose not to have Phoenix install our dependencies when we generate a new application, the `mix phx.new` task will prompt us to take the necessary steps when we do want to install them.\n\n```console\nFetch and install dependencies? [Yn] n\n\nWe are almost there! The following steps are missing:\n\n    $ cd hello\n    $ mix deps.get\n\nThen configure your database in config/dev.exs and run:\n\n    $ mix ecto.create\n\nStart your Phoenix app with:\n\n    $ mix phx.server\n\nYou can also run your app inside IEx (Interactive Elixir) as:\n\n    $ iex -S mix phx.server\n```\n\nBy default, Phoenix accepts requests on port 4000. If we point our favorite web browser at [http://localhost:4000](http://localhost:4000), we should see the Phoenix Framework welcome page.\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/images/welcome-to-phoenix-dark.png\" />\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"assets/images/welcome-to-phoenix.png\" />\n  <img src=\"assets/images/welcome-to-phoenix.png\" alt=\"Phoenix Welcome Page\" />\n</picture>\n\nIf your screen looks like the image above, congratulations! You now have a working Phoenix application. In case you can't see the page above, try accessing it via [http://127.0.0.1:4000](http://127.0.0.1:4000) and later make sure your OS has defined \"localhost\" as \"127.0.0.1\".\n\nTo stop it, we hit `ctrl-c` twice.\n\nNow you are ready to explore the world provided by Phoenix! See [our community page](community.html) for books, screencasts, courses, and more.\n\nAlternatively, you can continue reading these guides to have a quick introduction into all the parts that make your Phoenix application. If that's the case, you can read the guides in any order or start with our guide that explains the [Phoenix directory structure](directory_structure.html).\n"
  },
  {
    "path": "guides/json_and_apis.md",
    "content": "# JSON and APIs\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Controllers guide](controllers.html).\n\nYou can also use the Phoenix Framework to build [Web APIs](https://en.wikipedia.org/wiki/Web_API). By default Phoenix supports JSON but you can bring any other rendering format you desire.\n\n## The JSON API\n\nFor this guide let's create a simple JSON API to store our favourite links, that will support all the CRUD (Create, Read, Update, Delete) operations out of the box.\n\nFor this guide, we will use Phoenix generators to scaffold our API infrastructure:\n\n```console\nmix phx.gen.json Urls Url urls link:string title:string\n* creating lib/hello_web/controllers/url_controller.ex\n* creating lib/hello_web/controllers/url_json.ex\n* creating lib/hello_web/controllers/changeset_json.ex\n* creating test/hello_web/controllers/url_controller_test.exs\n* creating lib/hello_web/controllers/fallback_controller.ex\n* creating lib/hello/urls/url.ex\n* creating priv/repo/migrations/20221129120234_create_urls.exs\n* creating lib/hello/urls.ex\n* injecting lib/hello/urls.ex\n* creating test/hello/urls_test.exs\n* injecting test/hello/urls_test.exs\n* creating test/support/fixtures/urls_fixtures.ex\n* injecting test/support/fixtures/urls_fixtures.ex\n```\n\nWe will break those files into four categories:\n\n  * Files in `lib/hello_web` responsible for effectively rendering JSON\n  * Files in `lib/hello` responsible for defining our context and logic to persist links to the database\n  * Files in `priv/repo/migrations` responsible for updating our database\n  * Files in `test` to test our controllers and contexts\n\nIn this guide, we will explore only the first category of files. To learn more about how Phoenix stores and manage data, check out [the Ecto guide](ecto.md) and [the Contexts guide](contexts.md) for more information. We also have a whole section dedicated to testing.\n\nAt the end, the generator asks us to add the `/url` resource to our `:api` scope in `lib/hello_web/router.ex`:\n\n```elixir\nscope \"/api\", HelloWeb do\n  pipe_through :api\n  resources \"/urls\", UrlController, except: [:new, :edit]\nend\n```\n\nThe API scope uses the `:api` pipeline, which will run specific steps such as ensuring the client can handle JSON responses.\n\nThen we need to update our repository by running migrations:\n\n```console\n$ mix ecto.migrate\n```\n\n### Trying out the JSON API\n\nBefore we go ahead and change those files, let's take a look at how our API behaves from the command line.\n\nFirst, we need to start the server:\n\n```console\n$ mix phx.server\n```\n\nNext, let's make a smoke test to check our API is working with:\n\n```console\n$ curl -i http://localhost:4000/api/urls\n```\n\nIf everything went as planned we should get a `200` response:\n\n```console\nHTTP/1.1 200 OK\ncache-control: max-age=0, private, must-revalidate\ncontent-length: 11\ncontent-type: application/json; charset=utf-8\ndate: Fri, 06 May 2022 21:22:42 GMT\nserver: Cowboy\nx-request-id: Fuyg-wMl4S-hAfsAAAUk\n\n{\"data\":[]}\n```\n\nWe didn't get any data because we haven't populated the database with any yet. So let's add some links:\n\n```console\n$ curl -iX POST http://localhost:4000/api/urls \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"url\": {\"link\":\"https://phoenixframework.org\", \"title\":\"Phoenix Framework\"}}'\n\n$ curl -iX POST http://localhost:4000/api/urls \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"url\": {\"link\":\"https://elixir-lang.org\", \"title\":\"Elixir\"}}'\n```\n\nNow we can retrieve all links:\n\n```console\n$ curl -i http://localhost:4000/api/urls\n```\n\nOr we can just retrieve a link by its `id`:\n\n```console\n$ curl -i http://localhost:4000/api/urls/1\n```\n\nNext, we can update a link with:\n\n```console\n$ curl -iX PUT http://localhost:4000/api/urls/2 \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"url\": {\"title\":\"Elixir Programming Language\"}}'\n```\n\nThe response should be a `200` with the updated link in the body.\n\nFinally, we need to try out the removal of a link:\n\n```console\n$ curl -iX DELETE http://localhost:4000/api/urls/2 \\\n   -H 'Content-Type: application/json'\n```\n\nA `204` response should be returned to indicate the successful removal of the link.\n\n## Rendering JSON\n\nTo understand how to render JSON, let's start with the `index` action from `UrlController` defined at `lib/hello_web/controllers/url_controller.ex`:\n\n```elixir\n  def index(conn, _params) do\n    urls = Urls.list_urls()\n    render(conn, :index, urls: urls)\n  end\n```\n\nAs we can see, this is not any different from how Phoenix renders HTML templates. We call `render/3`, passing the connection, the template we want our views to render (`:index`), and the data we want to make available to our views.\n\nPhoenix typically uses one view per rendering format. When rendering HTML, we would use `UrlHTML`. Now that we are rendering JSON, we will find a `UrlJSON` view collocated with the template at `lib/hello_web/controllers/url_json.ex`. Let's open it up:\n\n```elixir\ndefmodule HelloWeb.UrlJSON do\n  alias Hello.Urls.Url\n\n  @doc \"\"\"\n  Renders a list of urls.\n  \"\"\"\n  def index(%{urls: urls}) do\n    %{data: for(url <- urls, do: data(url))}\n  end\n\n  @doc \"\"\"\n  Renders a single url.\n  \"\"\"\n  def show(%{url: url}) do\n    %{data: data(url)}\n  end\n\n  defp data(%Url{} = url) do\n    %{\n      id: url.id,\n      link: url.link,\n      title: url.title\n    }\n  end\nend\n```\n\nThis view is very simple. The `index` function receives all URLs, and converts them into a list of maps. Those maps are placed inside the data key at the root, exactly as we saw when interfacing with our application from `cURL`. In other words, our JSON view converts our complex data into simple Elixir data-structures. Once our view layer returns, Phoenix uses the `Jason` library to encode JSON and send the response to the client.\n\nIf you explore the remaining controller, you will learn the `show` action is similar to the `index` one. For `create`, `update`, and `delete` actions, Phoenix uses one other important feature, called \"Action fallback\".\n\n## Reading request data\n\nAs we've seen in [Request life-cycle](request_lifecycle.html), all controller actions take two arguments, `conn` and `params`. Plug automatically parses path parameters, query parameters and the request body into the `params` argument.\n\n### Minimal example\n\nLet's consider this minimal setup, with three API routes (`lib/hello_web/router.ex`):\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end\n\n  scope \"/api\", HelloWeb do\n    pipe_through :api\n\n    get \"/\", HelloController, :show\n    get \"/:name\", HelloController, :show\n    post \"/\", HelloController, :show\n  end\nend\n```\n\nA controller with a single action (`lib/hello_web/controllers/hello_controller.ex`):\n\n```elixir\ndefmodule HelloWeb.HelloController do\n  use HelloWeb, :controller\n\n  def show(conn, params) do\n    render(conn, :greet, params)\n  end\nend\n```\n\nand a JSON view (`lib/hello_web/controllers/hello_json.ex`):\n\n```elixir\ndefmodule HelloWeb.HelloJSON do\n  def greet(%{\"name\" => name}) do\n    %{greeting: \"Hello #{name}!\"}\n  end\nend\n```\n\nThis simple setup can process all of the following requests:\n\n```console\n$ curl http://localhost:4000/api/world\n\n$ curl http://localhost:4000/api?name=world\n\n$ curl -iX POST http://localhost:4000/api \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"name\": \"world\"}'\n```\n\nIn the exact same way: `Hello world!`\n\n### Request data is merged\n\nSince all data is merged under a single map, providing more than one source of data (such as both a path parameter and a JSON body, or a query parameter and a form), will result in **fields with the same name overriding each other.**\n\nThe priority is as follows: `Path Parameters > Body (any kind) > Query Parameters`\n\nFollowing our last example, lets add another POST request handler which accepts a path parameter:\n\n```elixir\n  scope \"/api\", HelloWeb do\n    pipe_through :api\n\n    get \"/\", HelloController, :show\n    get \"/:name\", HelloController, :show\n    post \"/\", HelloController, :show\n    post \"/:name\", HelloController, :show # New route\n    #      ^^^^^ New parameter\n  end\n```\n\nNow consider the following requests:\n\n```console\n$ curl -iX POST http://localhost:4000/api/name1?name=name3 \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"name\": \"name2\"}'\n\n$ curl -iX POST http://localhost:4000/api?name=name3 \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"name\": \"name2\"}'\n\n$ curl -iX POST http://localhost:4000/api?name=name3\n```\n\nThey would return, respectively, `Hello name1!` (Path parameter), `Hello name2!` (Request body) and `Hello name3!` (Query parameter).\nYou can access those parameters individually if desired via `conn.path_params`, `conn.body_params`, and `conn.query_params` respectively.\n\n## Action fallback\n\nAction fallback allows us to centralize error handling code in plugs, which are called when a controller action fails to return a [`%Plug.Conn{}`](`t:Plug.Conn.t/0`) struct. These plugs receive both the `conn` which was originally passed to the controller action along with the return value of the action.\n\nLet's say we have a `show` action which uses [`with`](`with/1`) to fetch a blog post and then authorize the current user to view that blog post. In this example we might expect `fetch_post/1` to return `{:error, :not_found}` if the post is not found and `authorize_user/3` might return `{:error, :unauthorized}` if the user is unauthorized. We could use our `ErrorHTML` and `ErrorJSON` views which are generated by Phoenix for every new application to handle these error paths accordingly:\n\n```elixir\ndefmodule HelloWeb.MyController do\n  use Phoenix.Controller\n\n  def show(conn, %{\"id\" => id}, current_user) do\n    with {:ok, post} <- fetch_post(id),\n         :ok <- authorize_user(current_user, :view, post) do\n      render(conn, :show, post: post)\n    else\n      {:error, :not_found} ->\n        conn\n        |> put_status(:not_found)\n        |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON)\n        |> render(:\"404\")\n\n      {:error, :unauthorized} ->\n        conn\n        |> put_status(403)\n        |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON)\n        |> render(:\"403\")\n    end\n  end\nend\n```\n\nNow imagine you may need to implement similar logic for every controller and action handled by your API. This would result in a lot of repetition.\n\nInstead we can define a module plug which knows how to handle these error cases specifically. Since controllers are module plugs, let's define our plug as a controller:\n\n```elixir\ndefmodule HelloWeb.MyFallbackController do\n  use Phoenix.Controller\n\n  def call(conn, {:error, :not_found}) do\n    conn\n    |> put_status(:not_found)\n    |> put_view(json: HelloWeb.ErrorJSON)\n    |> render(:\"404\")\n  end\n\n  def call(conn, {:error, :unauthorized}) do\n    conn\n    |> put_status(403)\n    |> put_view(json: HelloWeb.ErrorJSON)\n    |> render(:\"403\")\n  end\nend\n```\n\nThen we can reference our new controller as the `action_fallback` and simply remove the `else` block from our `with`:\n\n```elixir\ndefmodule HelloWeb.MyController do\n  use Phoenix.Controller\n\n  action_fallback HelloWeb.MyFallbackController\n\n  def show(conn, %{\"id\" => id}, current_user) do\n    with {:ok, post} <- fetch_post(id),\n         :ok <- authorize_user(current_user, :view, post) do\n      render(conn, :show, post: post)\n    end\n  end\nend\n```\n\nWhenever the `with` conditions do not match, `HelloWeb.MyFallbackController` will receive the original `conn` as well as the result of the action and respond accordingly.\n\n## FallbackController and ChangesetJSON\n\nWith this knowledge in hand, we can explore the `FallbackController` (`lib/hello_web/controllers/fallback_controller.ex`) generated by `mix phx.gen.json`. In particular, it handles one clause (the other is generated as an example):\n\n```elixir\n  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(json: HelloWeb.ChangesetJSON)\n    |> render(:error, changeset: changeset)\n  end\n```\n\nThe goal of this clause is to handle the `{:error, changeset}` return types from the `HelloWeb.Urls` context and render them into rendered errors via the `ChangesetJSON` view. Let's open up `lib/hello_web/controllers/changeset_json.ex` to learn more:\n\n```elixir\ndefmodule HelloWeb.ChangesetJSON do\n  @doc \"\"\"\n  Renders changeset errors.\n  \"\"\"\n  def error(%{changeset: changeset}) do\n    # When encoded, the changeset returns its errors\n    # as a JSON object. So we just pass it forward.\n    %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}\n  end\nend\n```\n\nAs we can see, it will convert the errors into a data structure, which will be rendered as JSON. The changeset is a data structure responsible for casting and validating data. For our example, it is defined in `Hello.Urls.Url.changeset/1`. Let's open up `lib/hello/urls/url.ex` and see its definition:\n\n```elixir\n  @doc false\n  def changeset(url, attrs) do\n    url\n    |> cast(attrs, [:link, :title])\n    |> validate_required([:link, :title])\n  end\n```\n\nAs you can see, the changeset requires both link and title to be given. This means we can try posting a url with no link and title and see how our API responds:\n\n```console\n$ curl -iX POST http://localhost:4000/api/urls \\\n   -H 'Content-Type: application/json' \\\n   -d '{\"url\": {}}'\n\n{\"errors\": {\"link\": [\"can't be blank\"], \"title\": [\"can't be blank\"]}}\n```\n\nFeel free to modify the `changeset` function and see how your API behaves.\n\n## API-only applications\n\nIn case you want to generate a Phoenix application exclusively for APIs, you can pass\nseveral options when invoking `mix phx.new`. Let's check which `--no-*` flags we need\nto use to not generate the scaffolding that isn't necessary on our Phoenix application\nfor the REST API.\n\nFrom your terminal run:\n\n```console\n$ mix help phx.new\n```\n\nThe output should contain the following:\n\n```text\n  • --no-assets - equivalent to --no-esbuild and --no-tailwind\n  • --no-dashboard - do not include Phoenix.LiveDashboard\n  • --no-ecto - do not generate Ecto files\n  • --no-esbuild - do not include esbuild dependencies and\n    assets. We do not recommend setting this option, unless for API\n    only applications, as doing so requires you to manually add and\n    track JavaScript dependencies\n  • --no-gettext - do not generate gettext files\n  • --no-html - do not generate HTML views\n  • --no-live - comment out LiveView socket setup in your Endpoint\n    and assets/js/app.js. Automatically disabled if --no-html is given\n  • --no-mailer - do not generate Swoosh mailer files\n  • --no-tailwind - do not include tailwind dependencies and\n    assets. The generated markup will still include Tailwind CSS\n    classes, those are left-in as reference for the subsequent\n    styling of your layout and components\n```\n\nThe `--no-html` is the obvious one we want to use when creating any Phoenix application for an API in order to leave out all the unnecessary HTML scaffolding. You may also pass `--no-assets`, if you don't want any of the asset management bit, `--no-gettext` if you don't support internationalization, and so on.\n\nSo, in order to generate a simple API called **Hello**, without any frontend, database connection, internationalization or mailing service, you can provide the following flags:\n\n```\n$ mix phx.new hello --no-assets --no-dashboard --no-ecto --no-gettext --no-html --no-mailer\n```\n\nThis still includes the Telemetry, PubSub and DNS Cluster modules by default, which you can remove manually if necessary.\n\nAlso bear in mind that nothing stops you to have a backend that supports simultaneously the REST API and a Web App (HTML, assets, internationalization and sockets).\n"
  },
  {
    "path": "guides/live_view.md",
    "content": "# LiveView\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [request life-cycle guide](request_lifecycle.html).\n\nWe've already seen how the typical request lifecycle in Phoenix works: a request is matched in the router, a controller handles the request and turns to a view to return a response in the correct format. But what if we want to build interactive pages? In a typical server rendered application, changing the content of the page either needs a form submission rendering the new page, or moving application logic to the client (JavaScript frameworks like jQuery, React, Vue, etc.) and building an API interface for the client to talk to.\n\nPhoenix LiveView offers a different approach, keeping all the state on the server while providing rich, real-time user experiences with server-rendered HTML. It's an alternative to client-side JavaScript frameworks that allows you to build dynamic, interactive applications with minimal JavaScript code on the client.\n\n## What is a LiveView?\n\nLiveViews are processes that receive events, update their state, and render updates to a page as diffs.\n\nThe LiveView programming model is declarative: instead of saying \"once event X happens, change Y on the page\", events in LiveView are regular messages which may cause changes to the state. Once the state changes, the LiveView will re-render the relevant parts of its HTML template and push it to the browser, which updates the page in the most efficient manner.\n\nLiveView state is nothing more than functional and immutable Elixir data structures. The events are either internal application messages (usually emitted by `Phoenix.PubSub`) or sent by the client/browser.\n\nEvery LiveView is first rendered statically as part of a regular HTTP request, which provides quick times for \"First Meaningful Paint\", in addition to helping search and indexing engines. A persistent connection is then established between the client and server to exchange events and changes to the page. This allows LiveView applications to react faster to user events as there is less work to be done and less data to be sent compared to stateless requests that have to authenticate, decode, load, and encode data on every request. You can think of LiveView as \"diffs over the wire\".\n\n## LiveView vs Controller + View\n\nWhile Phoenix controllers and LiveViews serve similar purposes in handling user interactions, they operate very differently:\n\n### Controller + View\n\n- Controllers handle each HTTP request-response pair as separate transactions\n- Each page load or form submission requires a full request/response cycle\n- Controllers are stateless, with data stored externally (database, session)\n- Views are separate modules that render templates with the data from controllers\n- Page updates and dynamic interactions require either full page reloads or custom client-side JavaScript code\n\n### LiveView approach\n\n- Initial page load uses the regular request lifecycle, but then establishes a bidirectional connection using [Phoenix Channels](channels.md)\n- A LiveView process maintains state throughout user interaction\n- State changes automatically trigger re-renders of only the changed parts of the page\n- Events flow through the persistent connection instead of separate HTTP requests\n- Minimal JavaScript is required for interactive features\n\nLiveViews combine the concerns of controllers and views into a more unified model.\n\n## Basic example\n\nLiveView is included by default in new Phoenix applications. Let's see a simple example:\n\n```elixir\ndefmodule MyAppWeb.ThermostatLive do\n  use MyAppWeb, :live_view\n\n  def render(assigns) do\n    ~H\"\"\"\n    Current temperature: {@temperature}°F\n    <button phx-click=\"inc_temperature\">+</button>\n    \"\"\"\n  end\n\n  def mount(_params, _session, socket) do\n    temperature = 70 # Let's assume a fixed temperature for now\n    {:ok, assign(socket, :temperature, temperature)}\n  end\n\n  def handle_event(\"inc_temperature\", _params, socket) do\n    {:noreply, update(socket, :temperature, &(&1 + 1))}\n  end\nend\n```\n\nThis LiveView demonstrates the core lifecycle:\n\n1. The `mount/3` callback initializes state when the LiveView starts\n2. The `render/1` function defines what is displayed using [HEEx templates](components.md)\n3. The `handle_event/3` callback responds to events from the client\n\nTo wire this up in your router:\n\n```elixir\ndefmodule MyAppWeb.Router do\n  use MyAppWeb, :router\n\n  pipeline :browser do\n    ...\n  end\n\n  scope \"/\", MyAppWeb do\n    pipe_through :browser\n    ...\n\n    live \"/thermostat\", ThermostatLive\n  end\nend\n```\n\nOnce the LiveView is rendered, a regular HTML response is sent. In your\napp.js file, you should find the following:\n\n```javascript\nimport {Socket} from \"phoenix\"\nimport {LiveSocket} from \"phoenix_live_view\"\n\nlet csrfToken = document.querySelector(\"meta[name='csrf-token']\").getAttribute(\"content\")\nlet liveSocket = new LiveSocket(\"/live\", Socket, {params: {_csrf_token: csrfToken}})\nliveSocket.connect()\n```\n\nNow the JavaScript client will connect over WebSockets and `mount/3` will be invoked\ninside a spawned LiveView process.\n\n## Key concepts\n\n### Socket and state\n\nThe LiveView socket is the fundamental data structure that holds all state in a LiveView. It's an immutable structure containing \"assigns\" - the data available to your templates. While controllers have `conn`, LiveViews have `socket`.\n\nChanges to the socket (via `assign/3` or `update/3`) trigger re-renders. All state is maintained on the server, with only the diffs sent to the client, minimizing network traffic.\n\n### LiveView lifecycle\n\nLiveViews have several important lifecycle stages:\n\n- [`mount`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:mount/3) - initializes the LiveView with parameters, session data, and socket\n- [`handle_params`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_params/3) - responds to URL changes and updates LiveView state accordingly\n- [`handle_event`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_event/3) - responds to user interactions coming from the client\n- [`handle_info`](https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#c:handle_info/2) - responds to regular process messages\n\n### DOM Bindings\n\nLiveView provides DOM bindings for convenient client-server interaction:\n\n```html\n<button phx-click=\"inc_temperature\">+</button>\n<form phx-submit=\"save\">...</form>\n<input phx-blur=\"validate\">\n```\n\nThese bindings automatically send events to the server when the specified browser events occur, which are then handled in `handle_event/3`.\n\n## Getting Started\n\nPhoenix includes code generators for LiveView. Try:\n\n```\n$ mix phx.gen.live Blog Post posts title:string body:text\n```\n\nThis generates a complete LiveView CRUD implementation, similar to `mix phx.gen.html`.\n\nTo learn more about LiveView, please refer to the [Phoenix LiveView documentation](https://hexdocs.pm/phoenix_live_view).\n"
  },
  {
    "path": "guides/plug.md",
    "content": "# Plug\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).\n\nPlug lives at the heart of Phoenix's HTTP layer, and Phoenix puts Plug front and center. We interact with plugs at every step of the request life-cycle, and the core Phoenix components like endpoints, routers, and controllers are all just plugs internally. Let's jump in and find out just what makes Plug so special.\n\n[Plug](https://github.com/elixir-lang/plug) is a specification for composable modules in between web applications. It is also an abstraction layer for connection adapters of different web servers. The basic idea of Plug is to unify the concept of a \"connection\" that we operate on. This differs from other HTTP middleware layers such as Rack, where the request and response are separated in the middleware stack.\n\nAt the simplest level, the Plug specification comes in two flavors: *function plugs* and *module plugs*.\n\n## Function plugs\n\nIn order to act as a plug, a function needs to:\n\n1. accept a connection struct (`%Plug.Conn{}`) as its first argument, and connection options as its second one;\n2. return a connection struct.\n\nAny function that meets these two criteria will do. Here's an example.\n\n```elixir\ndef introspect(conn, _opts) do\n  IO.puts \"\"\"\n  Verb: #{inspect(conn.method)}\n  Host: #{inspect(conn.host)}\n  Headers: #{inspect(conn.req_headers)}\n  \"\"\"\n\n  conn\nend\n```\n\nThis function does the following:\n\n  1. It receives a connection and options (that we do not use)\n  2. It prints some connection information to the terminal\n  3. It returns the connection\n\nPretty simple, right? Let's see this function in action by adding it to our endpoint in `lib/hello_web/endpoint.ex`. We can plug it anywhere, so let's do it by inserting `plug :introspect` right before we delegate the request to the router:\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  ...\n\n  plug :introspect\n  plug HelloWeb.Router\n\n  def introspect(conn, _opts) do\n    IO.puts \"\"\"\n    Verb: #{inspect(conn.method)}\n    Host: #{inspect(conn.host)}\n    Headers: #{inspect(conn.req_headers)}\n    \"\"\"\n\n    conn\n  end\nend\n```\n\nFunction plugs are plugged by passing the function name as an atom. To try the plug out, go back to your browser and fetch [http://localhost:4000](http://localhost:4000). You should see something like this printed in your shell terminal:\n\n```console\nVerb: \"GET\"\nHost: \"localhost\"\nHeaders: [...]\n```\n\nOur plug simply prints information from the connection. Although our initial plug is very simple, you can do virtually anything you want inside of it. To learn about all fields available in the connection and all of the functionality associated to it, see the [documentation for `Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html).\n\nNow let's look at the other plug variant, the module plugs.\n\n## Module plugs\n\nModule plugs are another type of plug that let us define a connection transformation in a module. The module only needs to implement two functions:\n\n- [`init/1`] which initializes any arguments or options to be passed to [`call/2`]\n- [`call/2`] which carries out the connection transformation. [`call/2`] is just a function plug that we saw earlier\n\nTo see this in action, let's write a module plug that puts the `:locale` key and value into the connection for downstream use in other plugs, controller actions, and our views. Put the contents below in a file named `lib/hello_web/plugs/locale.ex`:\n\n```elixir\ndefmodule HelloWeb.Plugs.Locale do\n  import Plug.Conn\n\n  @locales [\"en\", \"fr\", \"de\"]\n\n  def init(default), do: default\n\n  def call(%Plug.Conn{params: %{\"locale\" => loc}} = conn, _default) when loc in @locales do\n    assign(conn, :locale, loc)\n  end\n\n  def call(conn, default) do\n    assign(conn, :locale, default)\n  end\nend\n```\n\nTo give it a try, let's add this module plug to our router, by appending `plug HelloWeb.Plugs.Locale, \"en\"`  to our `:browser` pipeline in `lib/hello_web/router.ex`:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.LayoutView, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n    plug HelloWeb.Plugs.Locale, \"en\"\n  end\n  ...\n```\n\nIn the [`init/1`] callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple [`call/2`] function heads to validate the locale in the params, and fall back to `\"en\"` if there is no match. The [`assign/3`] is a part of the `Plug.Conn` module and it's how we store values in the `conn` data structure.\n\nTo see the assign in action, go to the template in `lib/hello_web/controllers/page_html/home.html.heex` and add the following code after the closing of the `</h1>` tag:\n\n```heex\n<p>Locale: {@locale}</p>\n```\n\nGo to [http://localhost:4000/](http://localhost:4000/) and you should see the locale exhibited. Visit [http://localhost:4000/?locale=fr](http://localhost:4000/?locale=fr) and you should see the assign changed to `\"fr\"`. You can use this information alongside [Gettext](https://hexdocs.pm/gettext/Gettext.html) to provide a fully internationalized web application.\n\nThat's all there is to Plug. Phoenix embraces the plug design of composable transformations all the way up and down the stack. Let's see some examples!\n\n## Where to plug\n\nThe endpoint, router, and controllers in Phoenix accept plugs.\n\n### Endpoint plugs\n\nEndpoints organize all the plugs common to every request, and apply them before dispatching into the router with its custom pipelines. We added a plug to the endpoint like this:\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  ...\n\n  plug :introspect\n  plug HelloWeb.Router\n```\n\nThe default endpoint plugs do quite a lot of work. Here they are in order:\n\n- `Plug.Static` - serves static assets. Since this plug comes before the logger, requests for static assets are not logged.\n\n- `Phoenix.LiveDashboard.RequestLogger` - sets up the *Request Logger* for Phoenix LiveDashboard, this will allow you to have the option to either pass a query parameter to stream requests logs or to enable/disable a cookie that streams requests logs from your dashboard.\n\n- `Plug.RequestId` - generates a unique request ID for each request.\n\n- `Plug.Telemetry` - adds instrumentation points so Phoenix can log the request path, status code and request time by default.\n\n- `Plug.Parsers` - parses the request body when a known parser is available. By default, this plug can handle URL-encoded, multipart and JSON content (with `Jason`). The request body is left untouched if the request content-type cannot be parsed.\n\n- `Plug.MethodOverride` - converts the request method to PUT, PATCH or DELETE for POST requests with a valid `_method` parameter.\n\n- `Plug.Head` - converts HEAD requests to GET requests.\n\n- `Plug.Session` - a plug that sets up session management. Note that `fetch_session/2` must still be explicitly called before using the session, as this plug just sets up how the session is fetched.\n\nIn the middle of the endpoint, there is also a conditional block:\n\n```elixir\n  if code_reloading? do\n    socket \"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket\n    plug Phoenix.LiveReloader\n    plug Phoenix.CodeReloader\n    plug Phoenix.Ecto.CheckRepoStatus, otp_app: :hello\n  end\n```\n\nThis block is only executed in development. It enables:\n\n* live reloading - if you change a CSS file, they are updated in-browser without refreshing the page;\n* [code reloading](`Phoenix.CodeReloader`) - so we can see changes to our application without restarting the server;\n* check repo status - which makes sure our database is up to date, raising a readable and actionable error otherwise.\n\n### Router plugs\n\nIn the router, we can declare plugs inside pipelines:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.LayoutView, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n    plug HelloWeb.Plugs.Locale, \"en\"\n  end\n\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :index\n  end\n```\n\nRoutes are defined inside scopes and scopes may pipe through multiple pipelines. Once a route matches, Phoenix invokes all plugs defined in all pipelines associated to that route. For example, accessing \"/\" will pipe through the `:browser` pipeline, consequently invoking all of its plugs.\n\nAs we will see in the [routing guide](routing.html), the pipelines themselves are plugs. There, we will also discuss all plugs in the `:browser` pipeline.\n\n### Controller plugs\n\nFinally, controllers are plugs too, so we can do:\n\n```elixir\ndefmodule HelloWeb.PageController do\n  use HelloWeb, :controller\n\n  plug HelloWeb.Plugs.Locale, \"en\"\n```\n\nIn particular, controller plugs provide a feature that allows us to execute plugs only within certain actions. For example, you can do:\n\n```elixir\ndefmodule HelloWeb.PageController do\n  use HelloWeb, :controller\n\n  plug HelloWeb.Plugs.Locale, \"en\" when action in [:index]\n```\n\nAnd the plug will only be executed for the `index` action.\n\n## Plugs as composition\n\nBy abiding by the plug contract, we turn an application request into a series of explicit transformations. It doesn't stop there. To really see how effective Plug's design is, let's imagine a scenario where we need to check a series of conditions and then either redirect or halt if a condition fails. Without plug, we would end up with something like this:\n\n```elixir\ndefmodule HelloWeb.MessageController do\n  use HelloWeb, :controller\n\n  def show(conn, params) do\n    case Authenticator.find_user(conn) do\n      {:ok, user} ->\n        case find_message(params[\"id\"]) do\n          nil ->\n            conn |> put_flash(:info, \"That message wasn't found\") |> redirect(to: ~p\"/\")\n          message ->\n            if Authorizer.can_access?(user, message) do\n              render(conn, :show, page: message)\n            else\n              conn |> put_flash(:info, \"You can't access that page\") |> redirect(to: ~p\"/\")\n            end\n        end\n      :error ->\n        conn |> put_flash(:info, \"You must be logged in\") |> redirect(to: ~p\"/\")\n    end\n  end\nend\n```\n\nNotice how just a few steps of authentication and authorization require complicated nesting and duplication? Let's improve this with a couple of plugs.\n\n```elixir\ndefmodule HelloWeb.MessageController do\n  use HelloWeb, :controller\n\n  plug :authenticate\n  plug :fetch_message\n  plug :authorize_message\n\n  def show(conn, params) do\n    render(conn, :show, page: conn.assigns[:message])\n  end\n\n  defp authenticate(conn, _) do\n    case Authenticator.find_user(conn) do\n      {:ok, user} ->\n        assign(conn, :user, user)\n      :error ->\n        conn |> put_flash(:info, \"You must be logged in\") |> redirect(to: ~p\"/\") |> halt()\n    end\n  end\n\n  defp fetch_message(conn, _) do\n    case find_message(conn.params[\"id\"]) do\n      nil ->\n        conn |> put_flash(:info, \"That message wasn't found\") |> redirect(to: ~p\"/\") |> halt()\n      message ->\n        assign(conn, :message, message)\n    end\n  end\n\n  defp authorize_message(conn, _) do\n    if Authorizer.can_access?(conn.assigns[:user], conn.assigns[:message]) do\n      conn\n    else\n      conn |> put_flash(:info, \"You can't access that page\") |> redirect(to: ~p\"/\") |> halt()\n    end\n  end\nend\n```\n\nTo make this all work, we converted the nested blocks of code and used `halt(conn)` whenever we reached a failure path. The `halt(conn)` functionality is essential here: it tells Plug that the next plug should not be invoked.\n\nAt the end of the day, by replacing the nested blocks of code with a flattened series of plug transformations, we are able to achieve the same functionality in a much more composable, clear, and reusable way.\n\nTo learn more about plugs, see the documentation for the [Plug project](`Plug`), which provides many built-in plugs and functionalities.\n\n[`init/1`]: `c:Plug.init/1`\n[`call/2`]: `c:Plug.call/2`\n[`assign/3`]: `Plug.Conn.assign/3`\n"
  },
  {
    "path": "guides/real_time/channels.md",
    "content": "# Channels\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\nChannels are an exciting part of Phoenix that enable soft real-time communication with and between millions of connected clients.\n\nSome possible use cases include:\n\n- Chat rooms and APIs for messaging apps\n- Breaking news, like \"a goal was scored\" or \"an earthquake is coming\"\n- Tracking trains, trucks, or race participants on a map\n- Events in multiplayer games\n- Monitoring sensors and controlling lights\n- Notifying a browser that a page's CSS or JavaScript has changed (this is handy in development)\n\nConceptually, Channels are pretty simple.\n\nFirst, clients connect to the server using some transport, like WebSocket. Once connected, they join one or more topics. For example, to interact with a public chat room clients may join a topic called `public_chat`, and to receive updates from a product with ID 7, they may need to join a topic called `product_updates:7`.\n\nClients can push messages to the topics they've joined, and can also receive messages from them. The other way around, Channel servers receive messages from their connected clients, and can push messages to them too.\n\nServers are able to broadcast messages to all clients subscribed to a certain topic. This is illustrated in the following diagram:\n\n```plaintext\n                                                                  +----------------+\n                                                     +--Topic X-->| Mobile Client  |\n                                                     |            +----------------+\n                              +-------------------+  |\n+----------------+            |                   |  |            +----------------+\n| Browser Client |--Topic X-->| Phoenix Server(s) |--+--Topic X-->| Desktop Client |\n+----------------+            |                   |  |            +----------------+\n                              +-------------------+  |\n                                                     |            +----------------+\n                                                     +--Topic X-->|   IoT Client   |\n                                                                  +----------------+\n```\n\nBroadcasts work even if the application runs on several nodes/computers. That is, if two clients have their socket connected to different application nodes and are subscribed to the same topic `T`, both of them will receive messages broadcasted to `T`. That is possible thanks to an internal PubSub mechanism.\n\nChannels can support any kind of client: a browser, native app, smart watch, embedded device, or anything else that can connect to a network.\nAll the client needs is a suitable library; see the [Client Libraries](#client-libraries) section below.\nEach client library communicates using one of the \"transports\" that Channels understand.\nCurrently, that's either Websockets or long polling, but other transports may be added in the future.\n\nUnlike stateless HTTP connections, Channels support long-lived connections, each backed by a lightweight Erlang VM process, working in parallel and maintaining its own state.\n\nThis architecture scales well; Phoenix Channels [can support millions of subscribers with reasonable latency on a single box](https://phoenixframework.org/blog/the-road-to-2-million-websocket-connections), passing hundreds of thousands of messages per second.\nAnd that capacity can be multiplied by adding more nodes to the cluster.\n\n## The Moving Parts\n\nAlthough Channels are simple to use from a client perspective, there are a number of components involved in routing messages to clients across a cluster of servers.\nLet's take a look at them.\n\n### Overview\n\nTo start communicating, a client connects to a node (a Phoenix server) using a transport (e.g., Websockets or long polling) and joins one or more channels using that single network connection.\nOne channel server lightweight process is created per client, per topic. Each channel holds onto the `%Phoenix.Socket{}` and can maintain any state it needs within its `socket.assigns`.\n\nOnce the connection is established, each incoming message from a client is routed, based on its topic, to the correct channel server.\nIf the channel server asks to broadcast a message, that message is sent to the local PubSub, which sends it out to any clients connected to the same server and subscribed to that topic.\n\nIf there are other nodes in the cluster, the local PubSub also forwards the message to their PubSubs, which send it out to their own subscribers.\nBecause only one message has to be sent per additional node, the performance cost of adding nodes is negligible, while each new node supports many more subscribers.\n\nThe message flow looks something like this:\n\n```plaintext\n                                 Channel   +-------------------------+      +--------+\n                                  route    | Sending Client, Topic 1 |      | Local  |\n                              +----------->|     Channel.Server      |----->| PubSub |--+\n+----------------+            |            +-------------------------+      +--------+  |\n| Sending Client |-Transport--+                                                  |      |\n+----------------+                         +-------------------------+           |      |\n                                           | Sending Client, Topic 2 |           |      |\n                                           |     Channel.Server      |           |      |\n                                           +-------------------------+           |      |\n                                                                                 |      |\n                                           +-------------------------+           |      |\n+----------------+                         | Browser Client, Topic 1 |           |      |\n| Browser Client |<-------Transport--------|     Channel.Server      |<----------+      |\n+----------------+                         +-------------------------+                  |\n                                                                                        |\n                                                                                        |\n                                                                                        |\n                                           +-------------------------+                  |\n+----------------+                         |  Phone Client, Topic 1  |                  |\n|  Phone Client  |<-------Transport--------|     Channel.Server      |<-+               |\n+----------------+                         +-------------------------+  |   +--------+  |\n                                                                        |   | Remote |  |\n                                           +-------------------------+  +---| PubSub |<-+\n+----------------+                         |  Watch Client, Topic 1  |  |   +--------+  |\n|  Watch Client  |<-------Transport--------|     Channel.Server      |<-+               |\n+----------------+                         +-------------------------+                  |\n                                                                                        |\n                                                                                        |\n                                           +-------------------------+      +--------+  |\n+----------------+                         |   IoT Client, Topic 1   |      | Remote |  |\n|   IoT Client   |<-------Transport--------|     Channel.Server      |<-----| PubSub |<-+\n+----------------+                         +-------------------------+      +--------+\n```\n\n### Endpoint\n\nIn your Phoenix app's `Endpoint` module, a `socket` declaration specifies which socket handler will receive connections on a given URL.\n\n```elixir\nsocket \"/socket\", HelloWeb.UserSocket,\n  websocket: true,\n  longpoll: false,\n  auth_token: true\n```\n\nPhoenix comes with two default transports: websocket and longpoll. You can configure them directly via the `socket` declaration.\n\n### Socket Handlers\n\nOn the client side, you will establish a socket connection to the route above:\n\n```javascript\nlet socket = new Socket(\"/socket\", {authToken: window.userToken})\n```\n\nOn the server, Phoenix will invoke `HelloWeb.UserSocket.connect/2`, passing your parameters and the initial socket state. Within the socket, you can authenticate and identify a socket connection and set default socket assigns. The socket is also where you define your channel routes.\n\n### Channel Routes\n\nChannel routes match on the topic string and dispatch matching requests to the given Channel module.\n\nThe star character `*` acts as a wildcard matcher, so in the following example route, requests for `room:lobby` and `room:123` would both be dispatched to the `RoomChannel`. In your `UserSocket`, you would have:\n\n```elixir\nchannel \"room:*\", HelloWeb.RoomChannel\n```\n\n### Channels\n\nChannels handle events from clients, so they are similar to Controllers, but there are two key differences. Channel events can go both directions - incoming and outgoing. Channel connections also persist beyond a single request/response cycle. Channels are the highest level abstraction for real-time communication components in Phoenix.\n\nEach Channel will implement one or more clauses of each of these four callback functions - `join/3`, `terminate/2`, `handle_in/3`, and `handle_out/3`.\n\n### Topics\n\nTopics are string identifiers - names that the various layers use in order to make sure messages end up in the right place. As we saw above, topics can use wildcards. This allows for a useful `\"topic:subtopic\"` convention. Often, you'll compose topics using record IDs from your application layer, such as `\"users:123\"`.\n\n### Messages\n\nThe `Phoenix.Socket.Message` module defines a struct with the following keys which denotes a valid message. From the [Phoenix.Socket.Message docs](https://hexdocs.pm/phoenix/Phoenix.Socket.Message.html).\n\n- `topic` - The string topic or `\"topic:subtopic\"` pair namespace, such as `\"messages\"` or `\"messages:123\"`\n- `event` - The string event name, for example `\"phx_join\"`\n- `payload` - The message payload\n- `ref` - The unique string ref\n\n### PubSub\n\nPubSub is provided by the `Phoenix.PubSub` module. Interested parties can receive events by subscribing to topics. Other processes can broadcast events to certain topics.\n\nThis is useful to broadcast messages on channel and also for application development in general. For instance, letting all connected [live views](https://github.com/phoenixframework/phoenix_live_view) to know that a new comment has been added to a post.\n\nThe PubSub system takes care of getting messages from one node to another so that they can be sent to all subscribers across the cluster.\nBy default, this is done using [Phoenix.PubSub.PG2](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.PG2.html), which uses native Erlang VM messaging.\n\nIf your deployment environment does not support distributed Elixir or direct communication between servers, Phoenix also ships with a [Redis Adapter](https://hexdocs.pm/phoenix_pubsub_redis/Phoenix.PubSub.Redis.html) that uses Redis to exchange PubSub data. Please see the [Phoenix.PubSub docs](https://hexdocs.pm/phoenix_pubsub/Phoenix.PubSub.html) for more information.\n\n### Client Libraries\n\nAny networked device can connect to Phoenix Channels as long as it has a client library.\nThe following libraries exist today, and new ones are always welcome; to write your own, see our how-to guide [Writing a Channels Client](writing_a_channels_client.md).\n\n#### Official\n\nPhoenix ships with a JavaScript client that is available when generating a new Phoenix project. The documentation for the JavaScript module is available at [https://hexdocs.pm/phoenix/js/](https://hexdocs.pm/phoenix/js/); the code is in [multiple js files](https://github.com/phoenixframework/phoenix/blob/main/assets/js/phoenix/).\n\n#### 3rd Party\n\n+ Swift (iOS)\n  - [SwiftPhoenix](https://github.com/davidstump/SwiftPhoenixClient)\n+ Java (Android)\n  - [JavaPhoenixChannels](https://github.com/eoinsha/JavaPhoenixChannels)\n+ Kotlin (Android)\n  - [JavaPhoenixClient](https://github.com/dsrees/JavaPhoenixClient)\n+ C#\n  - [PhoenixSharp](https://github.com/Mazyod/PhoenixSharp)\n+ Elixir\n  - [phoenix_gen_socket_client](https://github.com/Aircloak/phoenix_gen_socket_client)\n  - [slipstream](https://hexdocs.pm/slipstream/Slipstream.html)\n+ GDScript (Godot Game Engine)\n  - [GodotPhoenixChannels](https://github.com/alfredbaudisch/GodotPhoenixChannels)\n\n## Tying it all together\n\nLet's tie all these ideas together by building a simple chat application. Make sure [you created a new Phoenix application](https://hexdocs.pm/phoenix/up_and_running.html) and now we are ready to generate the `UserSocket`.\n\n### Generating a socket\n\nLet's invoke the socket generator to get started:\n\n```console\n$ mix phx.gen.socket User\n```\n\nIt will create two files, the client code in `assets/js/user_socket.js` and the server counter-part in `lib/hello_web/channels/user_socket.ex`. After running, the generator will also ask to add the following line to `lib/hello_web/endpoint.ex`:\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :hello\n\n  socket \"/socket\", HelloWeb.UserSocket,\n    websocket: true,\n    longpoll: false\n\n  ...\nend\n```\n\nThe generator also asks us to import the client code, we will do that later.\n\nNext, we will configure our socket to ensure messages get routed to the correct channel. To do that, we'll uncomment the `\"room:*\"` channel definition:\n\n```elixir\ndefmodule HelloWeb.UserSocket do\n  use Phoenix.Socket\n\n  ## Channels\n  channel \"room:*\", HelloWeb.RoomChannel\n  ...\n```\n\nNow, whenever a client sends a message whose topic starts with `\"room:\"`, it will be routed to our RoomChannel. Next, we'll define a `HelloWeb.RoomChannel` module to manage our chat room messages.\n\n### Joining Channels\n\nThe first priority of your channels is to authorize clients to join a given topic. For authorization, we must implement `join/3` in `lib/hello_web/channels/room_channel.ex`.\n\n```elixir\ndefmodule HelloWeb.RoomChannel do\n  use Phoenix.Channel\n\n  def join(\"room:lobby\", _message, socket) do\n    {:ok, socket}\n  end\n\n  def join(\"room:\" <> _private_room_id, _params, _socket) do\n    {:error, %{reason: \"unauthorized\"}}\n  end\nend\n```\n\nFor our chat app, we'll allow anyone to join the `\"room:lobby\"` topic, but any other room will be considered private and special authorization, say from a database, will be required.\n(We won't worry about private chat rooms for this exercise, but feel free to explore after we finish.)\n\nWith our channel in place, let's get the client and server talking.\n\nThe generated `assets/js/user_socket.js` defines a simple client based on the socket implementation that ships with Phoenix.\n\nWe can use that library to connect to our socket and join our channel, we just need to set our room name to `\"room:lobby\"` in that file.\n\n```javascript\n// assets/js/user_socket.js\n// ...\nsocket.connect()\n\n// Now that you are connected, you can join channels with a topic:\nlet channel = socket.channel(\"room:lobby\", {})\nchannel.join()\n  .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n  .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n```\n\nAfter that, we need to make sure `assets/js/user_socket.js` gets imported into our application JavaScript file. To do that, uncomment this line in `assets/js/app.js`.\n\n```javascript\n// ...\nimport \"./user_socket.js\"\n```\n\nSave the file and your browser should auto refresh, thanks to the Phoenix live reloader. If everything worked, we should see \"Joined successfully\" in the browser's JavaScript console. Our client and server are now talking over a persistent connection. Now let's make it useful by enabling chat.\n\nIn `lib/hello_web/controllers/page_html/home.html.heex`, we'll replace the existing code with a container to hold our chat messages, and an input field to send them:\n\n```heex\n<div id=\"messages\" role=\"log\" aria-live=\"polite\"></div>\n<input id=\"chat-input\" type=\"text\">\n```\n\nNow let's add a couple of event listeners to `assets/js/user_socket.js`:\n\n```javascript\n// ...\nlet channel           = socket.channel(\"room:lobby\", {})\nlet chatInput         = document.querySelector(\"#chat-input\")\nlet messagesContainer = document.querySelector(\"#messages\")\n\nchatInput.addEventListener(\"keypress\", event => {\n  if(event.key === 'Enter'){\n    channel.push(\"new_msg\", {body: chatInput.value})\n    chatInput.value = \"\"\n  }\n})\n\nchannel.join()\n  .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n  .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n```\n\nAll we had to do is detect that enter was pressed and then `push` an event over the channel with the message body. We named the event `\"new_msg\"`. With this in place, let's handle the other piece of a chat application, where we listen for new messages and append them to our messages container.\n\n```javascript\n// ...\nlet channel           = socket.channel(\"room:lobby\", {})\nlet chatInput         = document.querySelector(\"#chat-input\")\nlet messagesContainer = document.querySelector(\"#messages\")\n\nchatInput.addEventListener(\"keypress\", event => {\n  if(event.key === 'Enter'){\n    channel.push(\"new_msg\", {body: chatInput.value})\n    chatInput.value = \"\"\n  }\n})\n\nchannel.on(\"new_msg\", payload => {\n  let messageItem = document.createElement(\"p\")\n  messageItem.innerText = `[${Date()}] ${payload.body}`\n  messagesContainer.appendChild(messageItem)\n})\n\nchannel.join()\n  .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n  .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n```\n\nWe listen for the `\"new_msg\"` event using `channel.on`, and then append the message body to the DOM. Now let's handle the incoming and outgoing events on the server to complete the picture.\n\n### Incoming Events\n\nWe handle incoming events with `handle_in/3`. We can pattern match on the event names, like `\"new_msg\"`, and then grab the payload that the client passed over the channel. For our chat application, we simply need to notify all other `room:lobby` subscribers of the new message with `broadcast!/3`.\n\n```elixir\ndefmodule HelloWeb.RoomChannel do\n  use Phoenix.Channel\n\n  def join(\"room:lobby\", _message, socket) do\n    {:ok, socket}\n  end\n\n  def join(\"room:\" <> _private_room_id, _params, _socket) do\n    {:error, %{reason: \"unauthorized\"}}\n  end\n\n  def handle_in(\"new_msg\", %{\"body\" => body}, socket) do\n    broadcast!(socket, \"new_msg\", %{body: body})\n    {:noreply, socket}\n  end\nend\n```\n\n`broadcast!/3` will notify all joined clients on this `socket`'s topic and invoke their `handle_out/3` callbacks. `handle_out/3` isn't a required callback, but it allows us to customize and filter broadcasts before they reach each client. By default, `handle_out/3` is implemented for us and simply pushes the message on to the client. Hooking into outgoing events allows for powerful message customization and filtering. Let's see how.\n\n### Intercepting Outgoing Events\n\nWe won't implement this for our application, but imagine our chat app allowed users to ignore messages about new users joining a room. We could implement that behavior like this, where we explicitly tell Phoenix which outgoing event we want to intercept and then define a `handle_out/3` callback for those events. (Of course, this assumes that we have an `Accounts` context with an `ignoring_user?/2` function, and that we pass a user in via the `assigns` map). It is important to note that the `handle_out/3` callback will be called for every recipient of a message, so more expensive operations like hitting the database should be considered carefully before being included in `handle_out/3`.\n\n```elixir\nintercept [\"user_joined\"]\n\ndef handle_out(\"user_joined\", msg, socket) do\n  if Accounts.ignoring_user?(socket.assigns[:user], msg.user_id) do\n    {:noreply, socket}\n  else\n    push(socket, \"user_joined\", msg)\n    {:noreply, socket}\n  end\nend\n```\n\nThat's all there is to our basic chat app. Fire up multiple browser tabs and you should see your messages being pushed and broadcasted to all windows!\n\n## Using Token Authentication\n\nWhen we connect, we'll often need to authenticate the client. Fortunately, this is a 5-step process with [Phoenix.Token](https://hexdocs.pm/phoenix/Phoenix.Token.html).\n\n### Step 1 - Enable the `auth_token` functionality in the socket\n\nPhoenix supports a transport agnostic way to pass an authentication token to the server. To enable this, we need to pass the `:auth_token` option to the socket declaration in our `Endpoint` module.\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :hello\n\n  socket \"/socket\", HelloWeb.UserSocket,\n    websocket: true,\n    longpoll: false,\n    auth_token: true\n\n  ...\nend\n```\n\n### Step 2 - Assign a Token in the Connection\n\nLet's say we have an authentication plug in our app called `OurAuth`. When `OurAuth` authenticates a user, it sets a value for the `:current_user` key in `conn.assigns`. Since the `current_user` exists, we can simply assign the user's token in the connection for use in the layout. We can wrap that behavior up in a private function plug, `put_user_token/2`. This could also be put in its own module as well. To make this all work, we just add `OurAuth` and `put_user_token/2` to the browser pipeline.\n\n```elixir\npipeline :browser do\n  ...\n  plug OurAuth\n  plug :put_user_token\nend\n\ndefp put_user_token(conn, _) do\n  if current_user = conn.assigns[:current_user] do\n    token = Phoenix.Token.sign(conn, \"user socket\", current_user.id)\n    assign(conn, :user_token, token)\n  else\n    conn\n  end\nend\n```\n\nNow our `conn.assigns` contains the `current_user` and `user_token`.\n\n### Step 3 - Pass the Token to the JavaScript\n\nNext, we need to pass this token to JavaScript. We can do so inside a script tag in `lib/hello_web/components/layouts/root.html.heex` right above the app.js script, as follows:\n\n```heex\n<script>window.userToken = \"<%= assigns[:user_token] %>\";</script>\n<script src={~p\"/assets/js/app.js\"}></script>\n```\n\n### Step 4 - Pass the Token to the Socket Constructor and Verify\n\nWe also need to pass the `:auth_token` to the socket constructor and verify the user token in the `connect/3` function. To do so, edit `lib/hello_web/channels/user_socket.ex`, as follows:\n\n```elixir\ndef connect(_params_, socket, connect_info) do\n  # max_age: 1209600 is equivalent to two weeks in seconds\n  case Phoenix.Token.verify(socket, \"user socket\", connect_info[:auth_token], max_age: 1209600) do\n    {:ok, user_id} ->\n      {:ok, assign(socket, :current_user, user_id)}\n    {:error, reason} ->\n      :error\n  end\nend\n```\n\nIn our JavaScript, we can use the token set previously when constructing the Socket:\n\n```javascript\nlet socket = new Socket(\"/socket\", {authToken: window.userToken})\n```\n\nWe used `Phoenix.Token.verify/4` to verify the user token provided by the client. `Phoenix.Token.verify/4` returns either `{:ok, user_id}` or `{:error, reason}`. We can pattern match on that return in a `case` statement. With a verified token, we set the user's id as the value to `:current_user` in the socket. Otherwise, we return `:error`.\n\n### Step 5 - Connect to the socket in JavaScript\n\nWith authentication set up, we can connect to sockets and channels from JavaScript.\n\n```javascript\nlet socket = new Socket(\"/socket\", {authToken: window.userToken})\nsocket.connect()\n```\n\nNow that we are connected, we can join channels with a topic:\n\n```javascript\nlet channel = socket.channel(\"topic:subtopic\", {})\nchannel.join()\n  .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n  .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n```\n\nNote that token authentication is preferable since it's transport agnostic and well-suited for long running-connections like channels, as opposed to using sessions or other authentication approaches.\n\n## Fault Tolerance and Reliability Guarantees\n\nServers restart, networks split, and clients lose connectivity. In order to design robust systems, we need to understand how Phoenix responds to these events and what guarantees it offers.\n\n### Handling Reconnection\n\nClients subscribe to topics, and Phoenix stores those subscriptions in an in-memory ETS table. If a channel crashes, the clients will need to reconnect to the topics they had previously subscribed to. Fortunately, the Phoenix JavaScript client knows how to do this. The server will notify all the clients of the crash. This will trigger each client's `Channel.onError` callback. The clients will attempt to reconnect to the server using an exponential backoff strategy. Once they reconnect, they'll attempt to rejoin the topics they had previously subscribed to. If they are successful, they'll start receiving messages from those topics as before.\n\n### Resending Client Messages\n\nChannel clients queue outgoing messages into a `PushBuffer`, and send them to the server when there is a connection. If no connection is available, the client holds on to the messages until it can establish a new connection. With no connection, the client will hold the messages in memory until it establishes a connection, or until it receives a `timeout` event. The default timeout is set to 5000 milliseconds. The client won't persist the messages in the browser's local storage, so if the browser tab closes, the messages will be gone.\n\n### Resending Server Messages\n\nPhoenix uses an at-most-once strategy when sending messages to clients. If the client is offline and misses the message, Phoenix won't resend it. Phoenix doesn't persist messages on the server. If the server restarts, unsent messages will be gone. If our application needs stronger guarantees around message delivery, we'll need to write that code ourselves. Common approaches involve persisting messages on the server and having clients request missing messages. \n\nFor example, you can track a `last_seen_id` (or `last_updated_at`) on the client. On join, the client will pass the `last_seen_id` via the channel params for the last thing it saw, which lets the server know how to catch the client up with side effects that have happened since that id. On the client, anytime an event is received for a \"new_message\", the client bumps its `last_seen_id` so that it can recover gracefully across disconnect/reconnect. The code might look something like this:\n\n```javascript\n// on the client\nlet socket = new Socket(...)\n\nlet lastSeenMsg = {}\nlet roomParams = () => lastSeenMsg.id ? {last_seen_id: lastSeenMsg.id} : {}\nlet roomChannel = socket.channel(\"rooms:123\", roomParams)\n\nroomChannel.on(\"new_message\", msg => {\n  lastSeenMsg = msg\n  renderNewMessage(msg)\n})\n\nroomChannel.join().receive(\"ok\", ({messages}) => {\n  lastSeenMsg = messages[messages.length - 1]\n  renderMessages(messages)\n})\n```\n\n```elixir\n# room_channel.ex on the server\ndef join(\"rooms:\" <> id, params, socket) do\n  ...\n  messages = fetch_messages_since(params[\"last_seen_id\"])\n  {:ok, %{messages: messages}, socket}\nend\n```\n\n## Example Application\n\nTo see an example of the application we just built, checkout the project [phoenix_chat_example](https://github.com/chrismccord/phoenix_chat_example).\n"
  },
  {
    "path": "guides/real_time/presence.md",
    "content": "# Presence\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html).\n\nPhoenix Presence is a feature which allows you to register process information on a topic and replicate it transparently across a cluster. It's a combination of both a server-side and client-side library, which makes it simple to implement. A simple use-case would be showing which users are currently online in an application.\n\nPhoenix Presence is special for a number of reasons. It has no single point of failure, no single source of truth, relies entirely on the standard library with no operational dependencies and self-heals.\n\n## Setting up\n\nWe are going to use Presence to track which users are connected on the server and send updates to the client as users join and leave. We will deliver those updates via Phoenix Channels. Therefore, let's create a `RoomChannel`, as we did in the channels guides:\n\n```console\n$ mix phx.gen.channel Room\n```\n\nFollow the steps after the generator and you are ready to start tracking presence.\n\n## The Presence generator\n\nTo get started with Presence, we'll first need to generate a presence module. We can do this with the `mix phx.gen.presence` task:\n\n```console\n$ mix phx.gen.presence\n* creating lib/hello_web/channels/presence.ex\n\nAdd your new module to your supervision tree,\nin lib/hello/application.ex:\n\n    children = [\n      ...\n      HelloWeb.Presence,\n    ]\n\nYou're all set! See the Phoenix.Presence docs for more details:\nhttps://hexdocs.pm/phoenix/Phoenix.Presence.html\n```\n\nIf we open up the `lib/hello_web/channels/presence.ex` file, we will see the following line:\n\n```elixir\nuse Phoenix.Presence,\n  otp_app: :hello,\n  pubsub_server: Hello.PubSub\n```\n\nThis sets up the module for presence, defining the functions we require for tracking presences. As mentioned in the generator task, we should add this module to our supervision tree in\n`application.ex`:\n\n```elixir\nchildren = [\n  ...\n  HelloWeb.Presence,\n]\n```\n\n## Usage With Channels and JavaScript\n\nNext, we will create the channel that we'll communicate presence over. After a user joins, we can push the list of presences down the channel and then track the connection. We can also provide a map of additional information to track.\n\n```elixir\ndefmodule HelloWeb.RoomChannel do\n  use Phoenix.Channel\n  alias HelloWeb.Presence\n\n  def join(\"room:lobby\", %{\"name\" => name}, socket) do\n    send(self(), :after_join)\n    {:ok, assign(socket, :name, name)}\n  end\n\n  def handle_info(:after_join, socket) do\n    {:ok, _} =\n      Presence.track(socket, socket.assigns.name, %{\n        online_at: inspect(System.system_time(:second))\n      })\n\n    push(socket, \"presence_state\", Presence.list(socket))\n    {:noreply, socket}\n  end\nend\n```\n\nFinally, we can use the client-side Presence library included in `phoenix.js` to manage the state and presence diffs that come down the socket. It listens for the `\"presence_state\"` and `\"presence_diff\"` events and provides a simple callback for you to handle the events as they happen, with the `onSync` callback.\n\nThe `onSync` callback allows you to easily react to presence state changes, which most often results in re-rendering an updated list of active users. You can use the `list` method to format and return each individual presence based on the needs of your application.\n\nTo iterate users, we use the `presences.list()` function which accepts a callback. The callback will be called for each presence item with 2 arguments, the presence id and a list of metas (one for each presence for that presence id). We use this to display the users and the number of devices they are online with.\n\nWe can see presence working by adding the following to `assets/js/app.js`:\n\n```javascript\nimport {Socket, Presence} from \"phoenix\"\n\nlet socket = new Socket(\"/socket\", {authToken: window.userToken})\nlet channel = socket.channel(\"room:lobby\", {name: window.location.search.split(\"=\")[1]})\nlet presence = new Presence(channel)\n\nfunction renderOnlineUsers(presence) {\n  let response = \"\"\n\n  presence.list((id, {metas: [first, ...rest]}) => {\n    let count = rest.length + 1\n    response += `<br>${id} (count: ${count})</br>`\n  })\n\n  document.querySelector(\"main\").innerHTML = response\n}\n\nsocket.connect()\n\npresence.onSync(() => renderOnlineUsers(presence))\n\nchannel.join()\n```\n\nWe can ensure this is working by opening 3 browser tabs. If we navigate to <http://localhost:4000/?name=Alice> on two browser tabs and <http://localhost:4000/?name=Bob> then we should see:\n\n```plaintext\nAlice (count: 2)\nBob (count: 1)\n```\n\nIf we close one of the Alice tabs, then the count should decrease to 1. If we close another tab, the user should disappear from the list entirely.\n\n### Making it safe\n\nIn our initial implementation, we are passing the name of the user as part of the URL. However, in many systems, you want to allow only logged in users to access the presence functionality. To do so, you should set up token authentication, [as detailed in the token authentication section of the channels guide](channels.html#using-token-authentication).\n\nWith token authentication, you should access `socket.assigns.user_id`, set in `UserSocket`, instead of `socket.assigns.name` set from parameters.\n\n## Usage With LiveView\n\nWhilst Phoenix does ship with a JavaScript API for dealing with presence, it is also possible to extend the `HelloWeb.Presence` module to support [LiveView](https://hexdocs.pm/phoenix_live_view).\n\nOne thing to keep in mind when dealing with LiveView, is that each LiveView is a stateful process, so if we keep the presence state in the LiveView, each LiveView process will contain the full list of online users in memory. Instead, we can keep track of the online users within the `Presence` process, and pass separate events to the LiveView, which can use a stream to update the online list.\n\nTo start with, we need to update the `lib/hello_web/channels/presence.ex` file to add some optional callbacks to the `HelloWeb.Presence` module.\n\nFirstly, we add the `init/1` callback. This allows us to keep track of the presence state within the process.\n\n```elixir\n  def init(_opts) do\n    {:ok, %{}}\n  end\n```\n\nThe presence module also allows a `fetch/2` callback, this allows the data fetched from the presence to be modified, allowing us to define the shape of the response. In this case we are adding an `id` and a `user` map.\n\n```elixir\n  def fetch(_topic, presences) do\n    for {key, %{metas: [meta | metas]}} <- presences, into: %{} do\n      # user can be populated here from the database here we populate\n      # the name for demonstration purposes\n      {key, %{metas: [meta | metas], id: meta.id, user: %{name: meta.id}}}\n    end\n  end\n```\n\nThe final thing to add is the `handle_metas/4` callback. This callback updates the state that we keep track of in `HelloWeb.Presence` based on the user leaves and joins.\n\n```elixir\n  def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do\n    for {user_id, presence} <- joins do\n      user_data = %{id: user_id, user: presence.user, metas: Map.fetch!(presences, user_id)}\n      msg = {__MODULE__, {:join, user_data}}\n      Phoenix.PubSub.local_broadcast(Hello.PubSub, \"proxy:#{topic}\", msg)\n    end\n\n    for {user_id, presence} <- leaves do\n      metas =\n        case Map.fetch(presences, user_id) do\n          {:ok, presence_metas} -> presence_metas\n          :error -> []\n        end\n\n      user_data = %{id: user_id, user: presence.user, metas: metas}\n      msg = {__MODULE__, {:leave, user_data}}\n      Phoenix.PubSub.local_broadcast(Hello.PubSub, \"proxy:#{topic}\", msg)\n    end\n\n    {:ok, state}\n  end\n```\n\nYou can see that we are broadcasting events for the joins and leaves. These will be listened to by the LiveView process. You'll also see that we use \"proxy\" channel when broadcasting the joins and leaves. This is because we don't want our LiveView process to receive the presence events directly. We can add a few helper functions so that this particular implementation detail is abstracted from the LiveView module.\n\n```elixir\n  def list_online_users(), do: list(\"online_users\") |> Enum.map(fn {_id, presence} -> presence end)\n\n  def track_user(name, params), do: track(self(), \"online_users\", name, params)\n\n  def subscribe(), do: Phoenix.PubSub.subscribe(Hello.PubSub, \"proxy:online_users\")\n```\n\nNow that we have our presence module set up and broadcasting events, we can create a LiveView. Create a new file `lib/hello_web/live/online/index.ex` with the following contents:\n\n```elixir\ndefmodule HelloWeb.OnlineLive do\n  use HelloWeb, :live_view\n\n  def mount(params, _session, socket) do\n    socket = stream(socket, :presences, [])\n    socket =\n    if connected?(socket) do\n      HelloWeb.Presence.track_user(params[\"name\"], %{id: params[\"name\"]})\n      HelloWeb.Presence.subscribe()\n      stream(socket, :presences, HelloWeb.Presence.list_online_users())\n    else\n       socket\n    end\n\n    {:ok, socket}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <ul id=\"online_users\" phx-update=\"stream\">\n      <li :for={{dom_id, %{id: id, metas: metas}} <- @streams.presences} id={dom_id}>{id} ({length(metas)})</li>\n    </ul>\n    \"\"\"\n  end\n\n  def handle_info({HelloWeb.Presence, {:join, presence}}, socket) do\n    {:noreply, stream_insert(socket, :presences, presence)}\n  end\n\n  def handle_info({HelloWeb.Presence, {:leave, presence}}, socket) do\n    if presence.metas == [] do\n      {:noreply, stream_delete(socket, :presences, presence)}\n    else\n      {:noreply, stream_insert(socket, :presences, presence)}\n    end\n  end\nend\n```\n\nIf we add this route to the `lib/hello_web/router.ex`:\n\n```elixir\n    live \"/online/:name\", OnlineLive, :index\n```\n\nThen we can navigate to http://localhost:4000/online/Alice in one tab, and http://localhost:4000/online/Bob in another, you'll see that the presences are tracked, along with the number of presences per user. Opening and closing tabs with various users will update the presence list in real-time.\n"
  },
  {
    "path": "guides/request_lifecycle.md",
    "content": "# Request life-cycle\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\nThe goal of this guide is to talk about Phoenix's request life-cycle. This guide will take a practical approach where we will learn by doing: we will add two new pages to our Phoenix project and comment on how the pieces fit together along the way.\n\nLet's get on with our first new Phoenix page!\n\n## Adding a new page\n\nWhen your browser accesses [http://localhost:4000/](http://localhost:4000/), it sends an HTTP request to whatever service is running on that address, in this case our Phoenix application. The HTTP request is made of a verb and a path. For example, the following browser requests translate into:\n\n| Browser address bar                 | Verb | Path          |\n|:------------------------------------|:-----|:--------------|\n| <http://localhost:4000/>            | GET  | /             |\n| <http://localhost:4000/hello>       | GET  | /hello        |\n| <http://localhost:4000/hello/world> | GET  | /hello/world  |\n\nThere are other HTTP verbs. For example, submitting a form typically uses the POST verb.\n\nWeb applications typically handle requests by mapping each verb/path pair onto a specific part of your application. In Phoenix, this mapping is done by the router. For example, we may map \"/articles\" to a portion of our application that shows all articles. Therefore, to add a new page, our first task is to add a new route.\n\n### A new route\n\nThe router maps unique HTTP verb/path pairs to controller/action pairs which will handle them. Controllers in Phoenix are simply Elixir modules. Actions are functions that are defined within these controllers.\n\nPhoenix generates a router file for us in new applications at `lib/hello_web/router.ex`. This is where we will be working for this section.\n\nThe route for our \"Welcome to Phoenix!\" page from the previous [Up And Running Guide](up_and_running.html) looks like this:\n\n```elixir\nget \"/\", PageController, :home\n```\n\nLet's digest what this route is telling us. Visiting [http://localhost:4000/](http://localhost:4000/) issues an HTTP `GET` request to the root path. All requests like this will be handled by the `home/2` function in the `HelloWeb.PageController` module defined in `lib/hello_web/controllers/page_controller.ex`.\n\nThe page we are going to build will say \"Hello World, from Phoenix!\" when we point our browser to [http://localhost:4000/hello](http://localhost:4000/hello).\n\nThe first thing we need to do is to create the page route for a new page. Let's open up `lib/hello_web/router.ex` in a text editor. For a brand new application, it looks like this:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.Layouts, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end\n\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :home\n  end\n\n  # Other scopes may use custom stacks.\n  # scope \"/api\", HelloWeb do\n  #   pipe_through :api\n  # end\n\n  # ...\nend\n```\n\nFor now, we'll ignore the pipelines and the use of `scope` here and just focus on adding a route. We will discuss those in the [Routing guide](routing.html).\n\nLet's add a new route to the router that maps a `GET` request for `/hello` to the `index` action of a soon-to-be-created `HelloWeb.HelloController` inside the `scope \"/\" do` block of the router:\n\n```elixir\nscope \"/\", HelloWeb do\n  pipe_through :browser\n\n  get \"/\", PageController, :home\n  get \"/hello\", HelloController, :index\nend\n```\n\n### A new controller\n\nControllers are Elixir modules, and actions are Elixir functions defined in them. The purpose of actions is to gather the data and perform the tasks needed for rendering. Our route specifies that we need a `HelloWeb.HelloController` module with an `index/2` function.\n\nTo make the `index` action happen, let's create a new `lib/hello_web/controllers/hello_controller.ex` file, and make it look like the following:\n\n```elixir\ndefmodule HelloWeb.HelloController do\n  use HelloWeb, :controller\n\n  def index(conn, _params) do\n    render(conn, :index)\n  end\nend\n```\n\nWe'll save a discussion of `use HelloWeb, :controller` for the [Controllers guide](controllers.html). For now, let's focus on the `index` action.\n\nAll controller actions take two arguments. The first is `conn`, a struct which holds a ton of data about the request. The second is `params`, which are the request parameters. Here, we are not using `params`, and we avoid compiler warnings by prefixing it with `_`.\n\nThe core of this action is `render(conn, :index)`. It tells Phoenix to render the `index` template. The modules responsible for rendering are called views. By default, Phoenix views are named after the controller (`HelloController`) and format (`HTML` in this case), so Phoenix is expecting a `HelloWeb.HelloHTML` module to exist and define an `index/1` function.\n\n### A new view\n\nPhoenix views act as the presentation layer. For example, we expect the output of rendering `index` to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages.\n\nLet's create a new view. Create `lib/hello_web/controllers/hello_html.ex` and make it look like this:\n\n```elixir\ndefmodule HelloWeb.HelloHTML do\n  use HelloWeb, :html\nend\n```\n\nTo add templates to this view, we can define them as functions in the module or in separate files.\n\nLet's start by defining a function:\n\n```elixir\ndefmodule HelloWeb.HelloHTML do\n  use HelloWeb, :html\n\n  def index(assigns) do\n    ~H\"\"\"\n    Hello!\n    \"\"\"\n  end\nend\n```\n\nWe defined a function that receives `assigns` as arguments and used [the `~H` sigil](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html#sigil_H/2) to specify the content we want to render. Inside the `~H` sigil, we used a templating language called HEEx, which stands for \"HTML+EEx\". `EEx` is a library for embedding Elixir that ships as part of Elixir itself. \"HTML+EEx\" is a Phoenix extension of EEx that is HTML aware, with support for HTML validation, components, and automatic escaping of values. The latter protects you from security vulnerabilities like Cross-Site Scripting with no extra work on your part. We say that any function that receives `assigns` and returns templates to be a **function component**.\n\nA template file works in the same way. Let's give it a try by defining a template in its own file. First, delete our `def index(assigns)` function from above and replace it with an `embed_templates` declaration:\n\n```elixir\ndefmodule HelloWeb.HelloHTML do\n  use HelloWeb, :html\n\n  embed_templates \"hello_html/*\"\nend\n```\n\nHere we are saying we want to embed all `.heex` templates found in the sibling `hello_html` directory into our module as function components.\n\nNext, we need to add files to the `lib/hello_web/controllers/hello_html` directory. A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, let's create an `index.html.heex` file at `lib/hello_web/controllers/hello_html/index.html.heex`:\n\n```heex\n<section>\n  <h2>Hello World, from Phoenix!</h2>\n</section>\n```\n\nPhoenix will see the template file and compile it into an `index(assigns)` function, similar as before. There is no runtime or performance difference between the two styles.\n\nAlso note the controller name (`HelloController`) and the view name (`HelloHTML`) and their respective files, `hello_controller.ex` and `hello_html.ex` all follow the same naming convention, and are named after each other. You could name the directory anything you want, as long as you update the `embed_templates` setting accordingly, but it's best to follow conventions.\n\n```console\nlib/hello_web\n├── controllers\n│   ├── hello_controller.ex\n│   ├── hello_html.ex\n│   ├── hello_html\n|       ├── index.html.heex   (NEW FILE!)\n```\n\nNow that we've got the route, controller, view, and template, we should be able to point our browser at [http://localhost:4000/hello](http://localhost:4000/hello) and see our greeting from Phoenix!\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/images/hello-from-phoenix-dark.png\" />\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"assets/images/hello-from-phoenix.png\" />\n  <img src=\"assets/images/hello-from-phoenix.png\" alt=\"Phoenix Greets Us\" />\n</picture>\n\nIn case you stopped the server along the way, the task to restart it is `mix phx.server`. If you didn't stop it, everything should update on the fly: Phoenix has hot code reloading!\n\n## Layouts\n\nEven though our `index.html.heex` file consists of only a single `section` tag, the page we get is a full HTML document. Our index template is actually rendered into a separate layout: `lib/hello_web/components/layouts/root.html.heex`, which contains the basic HTML skeleton of the page. If you open this file, you'll see a line that looks like this at the bottom:\n\n```heex\n{@inner_content}\n```\n\nThis line injects our template into the layout before the HTML is sent off to the browser. The root layout, as the name implies, is a barebone layout with mostly the `<head>` tag and a structure that is shared across **all of your pages**. Richer features, such as sidebar, menus, etc. are part of your application layout, which we will explore in a couple sections below.\n\n## From endpoint to views\n\nHaving built our first page, we're beginning to understand how the request life-cycle is put together. Now let's take a more holistic look at it.\n\nAll HTTP requests start in our application endpoint. You can find it as a module named `HelloWeb.Endpoint` in `lib/hello_web/endpoint.ex`. Once you open up the endpoint file, you will see that, similar to the router, the endpoint has many calls to `plug`. `Plug` is a library and a specification for stitching web applications together. It is an essential part of how Phoenix handles requests and we will discuss it in detail in the [Plug guide](plug.html) coming next.\n\nFor now, it suffices to say that each plug defines a slice of request processing. In the endpoint you will find a skeleton roughly like this:\n\n```elixir\ndefmodule HelloWeb.Endpoint do\n  use Phoenix.Endpoint, otp_app: :hello\n\n  plug Plug.Static, ...\n  plug Plug.RequestId\n  plug Plug.Telemetry, ...\n  plug Plug.Parsers, ...\n  plug Plug.MethodOverride\n  plug Plug.Head\n  plug Plug.Session, ...\n  plug HelloWeb.Router\nend\n```\n\nEach of these plugs have a specific responsibility that we will learn later. The last plug is precisely the `HelloWeb.Router` module. This allows the endpoint to delegate all further request processing to the router. As we now know, its main responsibility is to map verb/path pairs to controllers. The controller then tells a view to render a template.\n\nAt this moment, you may be thinking this can be a lot of steps to simply render a page. However, as our application grows in complexity, we will see that each layer serves a distinct purpose:\n\n  * endpoint (`Phoenix.Endpoint`) - the endpoint contains the common and initial path that all requests go through. If you want something to happen on all requests, it goes in the endpoint.\n\n  * router (`Phoenix.Router`) - the router is responsible for dispatching verb/path pairs to controllers. The router also allows us to scope functionality. For example, some pages in your application may require user authentication, others may not.\n\n  * controller (`Phoenix.Controller`) - the job of the controller is to retrieve request information, talk to your business domain, and prepare data for the presentation layer.\n\n  * view - the view handles the structured data from the controller and converts it to a presentation to be shown to users. Views are often named after the content format they are rendering.\n\nLet's do a quick recap on how the last three components work together by adding another page. This time, we will use some additional features, such as layout components and content interpolation.\n\n## Another new page\n\nLet's add just a little complexity to our application. We're going to add a new page that will recognize a piece of the URL, label it as a \"messenger\" and pass it through the controller into the template so our messenger can say hello.\n\nAs we did last time, the first thing we'll do is create a new route.\n\n### Another new route\n\nFor this exercise, we're going to reuse `HelloController` created at the [previous step](request_lifecycle.html#a-new-controller) and add a new `show` action. We'll add a line just below our last route, like this:\n\n```elixir\nscope \"/\", HelloWeb do\n  pipe_through :browser\n\n  get \"/\", PageController, :home\n  get \"/hello\", HelloController, :index\n  get \"/hello/:messenger\", HelloController, :show\nend\n```\n\nNotice that we use the `:messenger` syntax in the path. Phoenix will take whatever value that appears in that position in the URL and convert it into a parameter. For example, if we point the browser at: `http://localhost:4000/hello/Frank`, the value of `\"messenger\"` will be `\"Frank\"`.\n\n### Another new action\n\nRequests to our new route will be handled by the `HelloWeb.HelloController` `show` action. We already have the controller at `lib/hello_web/controllers/hello_controller.ex`, so all we need to do is edit that controller and add a `show` action to it. This time, we'll need to extract the messenger from the parameters so that we can pass it (the messenger) to the template. To do that, we add this show function to the controller:\n\n```elixir\ndef show(conn, %{\"messenger\" => messenger}) do\n  render(conn, :show, messenger: messenger)\nend\n```\n\nWithin the body of the `show` action, we also pass a third argument to the render function, a key-value pair where `:messenger` is the key, and the `messenger` variable is passed as the value.\n\nIf the body of the action needs access to the full map of parameters bound to the `params` variable, in addition to the bound messenger variable, we could define `show/2` like this:\n\n```elixir\ndef show(conn, %{\"messenger\" => messenger} = params) do\n  ...\nend\n```\n\nIt's good to remember that the keys of the `params` map will always be strings, and that the equals sign does not represent assignment, but is instead a [pattern match](https://hexdocs.pm/elixir/pattern-matching.html) assertion.\n\n### Another new template\n\nFor the last piece of this puzzle, we'll need a new template. Since it is for the `show` action of `HelloController`, it will go into the `lib/hello_web/controllers/hello_html` directory and be called `show.html.heex`. It will look surprisingly like our `index.html.heex` template, except that we will need to display the name of our messenger. Let's write the new template down and then explain what it does:\n\n```heex\n<Layouts.app flash={@flash}>\n  <section>\n    <h2>Hello World, from {@messenger}!</h2>\n  </section>\n</Layouts.app>\n```\n\nIf you point your browser to [http://localhost:4000/hello/Frank](http://localhost:4000/hello/Frank), you should see a page that looks like this:\n\n<picture>\n  <source media=\"(prefers-color-scheme: dark)\" srcset=\"assets/images/hello-world-from-frank-dark.png\" />\n  <source media=\"(prefers-color-scheme: light)\" srcset=\"assets/images/hello-world-from-frank.png\" />\n  <img src=\"assets/images/hello-world-from-frank.png\" alt=\"Frank Greets Us from Phoenix\" />\n</picture>\n\nLet's break what the template does into parts. This template has the `.heex` extension which stands for HTML + Embedded Elixir. There are three features from HEEx we are using in the template above:\n\n  * Assigns, such as `@messenger` and `@flash` - values we pass to the view from the controller are collectively called our \"assigns\". We could access our messenger value via `assigns.messenger` and `assigns.flash`, but Phoenix gives us the much cleaner `@` syntax for use in templates.\n\n  * Content interpolation, such as `{@messenger}` - any Elixir code that goes between `{...}` will be executed, and the resulting value will replace the tag in the HTML output\n\n  * Function component tags, as in `<Layouts.app>` - the reason templates are called function components is because we can compose them! In this case, there is an `HelloWeb.Layouts.app` function, that defines our application layout, which we invoke with our custom content\n\nAlso note how these three features compose: we are passing the `@flash` assign as an Elixir value to the `<Layouts.app>` component. As we will learn later, flash messages are used to display temporary messages to the user, such as success or error messages. We will learn more about them in the [Components and HEEx templates](components.html) guide.\n\nWe are done! Feel free to play around a bit. Whatever you put after `/hello/` will appear on the page as your messenger.\n"
  },
  {
    "path": "guides/routing.md",
    "content": "# Routing\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Request life-cycle guide](request_lifecycle.html).\n\nRouters are the main hubs of Phoenix applications. They match HTTP requests to controller actions, wire up real-time channel handlers, and define a series of pipeline transformations scoped to a set of routes.\n\nThe router file that Phoenix generates, `lib/hello_web/router.ex`, will look something like this one:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.Layouts, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end\n\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :home\n  end\n\n  # Other scopes may use custom stacks.\n  # scope \"/api\", HelloWeb do\n  #   pipe_through :api\n  # end\n\n  # ...\nend\n```\n\nBoth the router and controller module names will be prefixed with the name you gave your application suffixed with `Web`.\n\nThe first line of this module, `use HelloWeb, :router`, simply makes Phoenix router functions available in our particular router.\n\nScopes have their own section in this guide, so we won't spend time on the `scope \"/\", HelloWeb do` block here. The `pipe_through :browser` line will get a full treatment in the \"Pipelines\" section of this guide. For now, you only need to know that pipelines allow a set of plugs to be applied to different sets of routes.\n\nInside the scope block, however, we have our first actual route:\n\n```elixir\nget \"/\", PageController, :home\n```\n\n`get` is a Phoenix macro that corresponds to the HTTP verb GET. Similar macros exist for other HTTP verbs, including POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE, and HEAD.\n\n> #### Why the macros? {: .info}\n>\n> Phoenix does its best to keep the usage of macros low. You may have noticed, however, that the `Phoenix.Router` relies heavily on macros. Why is that?\n>\n> We use `get`, `post`, `put`, and `delete` to define your routes. We use macros for two purposes:\n>\n>   * They define the routing engine, used on every request, to choose which controller to dispatch the request to. Thanks to macros, Phoenix compiles all of your routes to a huge case-statement with pattern matching rules, which is heavily optimized by the Erlang VM\n>\n>   * For each route you define, we also define metadata to implement `Phoenix.VerifiedRoutes`. As we will soon learn, verified routes allow us to reference any route as if it were a plain looking string, except that it is verified by the compiler to be valid (making it much harder to ship broken links, forms, mails, etc to production)\n>\n> In other words, the router relies on macros to build applications that are faster and safer. Also remember that macros in Elixir are compile-time only, which gives plenty of stability after the code is compiled. As we will learn next, Phoenix also provides introspection for all defined routes via `mix phx.routes`.\n\n## Examining routes\n\nPhoenix provides an excellent tool for investigating routes in an application: `mix phx.routes`.\n\nLet's see how this works. Go to the root of a newly-generated Phoenix application and run `mix phx.routes`. You should see something like the following, generated with all routes you currently have:\n\n```console\n$ mix phx.routes\nGET  /  HelloWeb.PageController :home\n...\n```\n\nThe route above tells us that any HTTP GET request for the root of the application will be handled by the `home` action of the `HelloWeb.PageController`.\n\n## Resources\n\nThe router supports other macros besides those for HTTP verbs like [`get`](`Phoenix.Router.get/3`), [`post`](`Phoenix.Router.post/3`), and [`put`](`Phoenix.Router.put/3`). The most important among them is [`resources`](`Phoenix.Router.resources/4`). Let's add a resource to our `lib/hello_web/router.ex` file like this:\n\n```elixir\nscope \"/\", HelloWeb do\n  pipe_through :browser\n\n  get \"/\", PageController, :home\n  resources \"/users\", UserController\n  ...\nend\n```\n\nFor now it doesn't matter that we don't actually have a `HelloWeb.UserController`.\n\nRun `mix phx.routes` once again at the root of your project. You should see something like the following:\n\n```console\n...\nGET     /users           HelloWeb.UserController :index\nGET     /users/:id/edit  HelloWeb.UserController :edit\nGET     /users/new       HelloWeb.UserController :new\nGET     /users/:id       HelloWeb.UserController :show\nPOST    /users           HelloWeb.UserController :create\nPATCH   /users/:id       HelloWeb.UserController :update\nPUT     /users/:id       HelloWeb.UserController :update\nDELETE  /users/:id       HelloWeb.UserController :delete\n...\n```\n\nThis is the standard matrix of HTTP verbs, paths, and controller actions. For a while, this was known as RESTful routes, but most consider this a misnomer nowadays. Let's look at them individually.\n\n- A GET request to `/users` will invoke the `index` action to show all the users.\n- A GET request to `/users/:id/edit` will invoke the `edit` action with an ID to retrieve an individual user from the data store and present the information in a form for editing.\n- A GET request to `/users/new` will invoke the `new` action to present a form for creating a new user.\n- A GET request to `/users/:id` will invoke the `show` action with an id to show an individual user identified by that ID.\n- A POST request to `/users` will invoke the `create` action to save a new user to the data store.\n- A PATCH request to `/users/:id` will invoke the `update` action with an ID to save the updated user to the data store.\n- A PUT request to `/users/:id` will also invoke the `update` action with an ID to save the updated user to the data store.\n- A DELETE request to `/users/:id` will invoke the `delete` action with an ID to remove the individual user from the data store.\n\nIf we don't need all these routes, we can be selective using the `:only` and `:except` options to filter specific actions.\n\nLet's say we have a read-only posts resource. We could define it like this:\n\n```elixir\nresources \"/posts\", PostController, only: [:index, :show]\n```\n\nRunning `mix phx.routes` shows that we now only have the routes to the index and show actions defined.\n\n```console\nGET     /posts      HelloWeb.PostController :index\nGET     /posts/:id  HelloWeb.PostController :show\n```\n\nSimilarly, if we have a comments resource, and we don't want to provide a route to delete one, we could define a route like this.\n\n```elixir\nresources \"/comments\", CommentController, except: [:delete]\n```\n\nRunning `mix phx.routes` now shows that we have all the routes except the DELETE request to the delete action.\n\n```console\nGET    /comments           HelloWeb.CommentController :index\nGET    /comments/:id/edit  HelloWeb.CommentController :edit\nGET    /comments/new       HelloWeb.CommentController :new\nGET    /comments/:id       HelloWeb.CommentController :show\nPOST   /comments           HelloWeb.CommentController :create\nPATCH  /comments/:id       HelloWeb.CommentController :update\nPUT    /comments/:id       HelloWeb.CommentController :update\n```\n\nThe `Phoenix.Router.resources/4` macro describes additional options for customizing resource routes.\n\n## Verified Routes\n\nPhoenix includes `Phoenix.VerifiedRoutes` module which provides compile-time checks of router paths against your router by using the `~p` sigil. For example, you can write paths in controllers, tests, and templates and the compiler will make sure those actually match routes defined in your router.\n\nLet's see it in action. Run `iex -S mix` at the root of the project. We'll define a throwaway example module that builds a couple `~p` route paths.\n\n```elixir\niex> defmodule RouteExample do\n...>   use HelloWeb, :verified_routes\n...>\n...>   def example do\n...>     ~p\"/comments\"\n...>     ~p\"/unknown/123\"\n...>   end\n...> end\nwarning: no route path for HelloWeb.Router matches \"/unknown/123\"\n  iex:5: RouteExample.example/0\n\n{:module, RouteExample, ...}\niex>\n```\n\nNotice how the first call to an existing route, `~p\"/comments\"` gave no warning, but a bad route path `~p\"/unknown/123\"` produced a compiler warning, just as it should. This is significant because it allows us to write otherwise hard-coded paths in our application and the compiler will let us know whenever we write a bad route or change our routing structure.\n\nPhoenix projects are set up out of the box to allow use of verified routes throughout your web layer, including tests. For example in your templates you can render `~p` links:\n\n```heex\n<a href={~p\"/\"}>Welcome Page!</a>\n<a href={~p\"/comments\"}>View Comments</a>\n```\n\nOr in a controller, issue a redirect:\n\n```elixir\nredirect(conn, to: ~p\"/comments/#{comment}\")\n```\n\nUsing `~p` for route paths ensures our application paths and URLs stay up to date with the router definitions. The compiler will catch bugs for us, and let us know when we change routes that are referenced elsewhere in our application.\n\n### More on verified routes\n\nWhat about paths with query strings? You can add query string key values directly, as a keyword list or map of values, for example:\n\n```elixir\n~p\"/users/17?admin=true&active=false\"\n\"/users/17?admin=true&active=false\"\n\n~p\"/users/17?#{[admin: true]}\"\n\"/users/17?admin=true\"\n\n~p\"/users/17?#{%{admin: true}}\"\n\"/users/17?admin=true\"\n```\n\nWhat if we need a full URL instead of a path? Just wrap your path with a call to `Phoenix.VerifiedRoutes.url/1`, which is imported everywhere that `~p` is available:\n\n```elixir\nurl(~p\"/users\")\n\"http://localhost:4000/users\"\n```\n\nThe `url` calls will get the host, port, proxy port, and SSL information needed to construct the full URL from the configuration parameters set for each environment. We'll talk about configuration in more detail in its own guide. For now, you can take a look at `config/dev.exs` file in your own project to see those values.\n\n## Nested resources\n\nIt is also possible to nest resources in a Phoenix router. Let's say we also have a `posts` resource that has a many-to-one relationship with `users`. That is to say, a user can create many posts, and an individual post belongs to only one user. We can represent that by adding a nested route in `lib/hello_web/router.ex` like this:\n\n```elixir\nresources \"/users\", UserController do\n  resources \"/posts\", PostController\nend\n```\n\nWhen we run `mix phx.routes` now, in addition to the routes we saw for `users` above, we get the following set of routes:\n\n```console\n...\nGET     /users/:user_id/posts           HelloWeb.PostController :index\nGET     /users/:user_id/posts/:id/edit  HelloWeb.PostController :edit\nGET     /users/:user_id/posts/new       HelloWeb.PostController :new\nGET     /users/:user_id/posts/:id       HelloWeb.PostController :show\nPOST    /users/:user_id/posts           HelloWeb.PostController :create\nPATCH   /users/:user_id/posts/:id       HelloWeb.PostController :update\nPUT     /users/:user_id/posts/:id       HelloWeb.PostController :update\nDELETE  /users/:user_id/posts/:id       HelloWeb.PostController :delete\n...\n```\n\nWe see that each of these routes scopes the posts to a user ID. For the first one, we will invoke `PostController`'s `index` action, but we will pass in a `user_id`. This implies that we would display all the posts for that individual user only. The same scoping applies for all these routes.\n\nWhen building paths for nested routes, we will need to interpolate the IDs where they belong in route definition. For the following `show` route, `42` is the `user_id`, and `17` is the `post_id`.\n\n```elixir\nuser_id = 42\npost_id = 17\n~p\"/users/#{user_id}/posts/#{post_id}\"\n\"/users/42/posts/17\"\n```\n\nVerified routes also support the `Phoenix.Param` protocol, but we don't need to concern ourselves with Elixir protocols just yet. Just know that once we start building our application with structs like `%User{}` and `%Post{}`, we'll be able to interpolate those data structures directly into our `~p` paths and Phoenix will pluck out the correct fields to use in the route.\n\n```elixir\n~p\"/users/#{user}/posts/#{post}\"\n\"/users/42/posts/17\"\n```\n\nNotice how we didn't need to interpolate `user.id` or `post.id`? This is particularly nice if we decide later we want to make our URLs a little nicer and start using slugs instead. We don't need to change any of our `~p`'s!\n\n## Scoped routes\n\nScopes are a way to group routes under a common path prefix and scoped set of plugs. We might want to do this for admin functionality, APIs, and especially for versioned APIs. Let's say we have user-generated reviews on a site, and that those reviews first need to be approved by an administrator. The semantics of these resources are quite different, and they might not share the same controller. Scopes enable us to segregate these routes.\n\nThe paths to the user-facing reviews would look like a standard resource.\n\n```console\n/reviews\n/reviews/1234\n/reviews/1234/edit\n...\n```\n\nThe administration review paths can be prefixed with `/admin`.\n\n```console\n/admin/reviews\n/admin/reviews/1234\n/admin/reviews/1234/edit\n...\n```\n\nWe accomplish this with a scoped route that sets a path option to `/admin` like this one. We can nest this scope inside another scope, but instead, let's set it by itself at the root, by adding to `lib/hello_web/router.ex` the following:\n\n```elixir\nscope \"/admin\", HelloWeb.Admin do\n  pipe_through :browser\n\n  resources \"/reviews\", ReviewController\nend\n```\n\nWe define a new scope where all routes are prefixed with `/admin` and all controllers are under the `HelloWeb.Admin` namespace.\n\nRunning `mix phx.routes` again, in addition to the previous set of routes we get the following:\n\n```console\n...\nGET     /admin/reviews           HelloWeb.Admin.ReviewController :index\nGET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit\nGET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new\nGET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show\nPOST    /admin/reviews           HelloWeb.Admin.ReviewController :create\nPATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nPUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nDELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete\n...\n```\n\nThis looks good, but there is a problem here. Remember that we wanted both user-facing review routes `/reviews` and the admin ones `/admin/reviews`. If we now include the user-facing reviews in our router under the root scope like this:\n\n```elixir\nscope \"/\", HelloWeb do\n  pipe_through :browser\n\n  ...\n  resources \"/reviews\", ReviewController\nend\n\nscope \"/admin\", HelloWeb.Admin do\n  pipe_through :browser\n\n  resources \"/reviews\", ReviewController\nend\n```\n\nand we run `mix phx.routes`, we get output for each scoped route:\n\n```console\n...\nGET     /reviews                 HelloWeb.ReviewController :index\nGET     /reviews/:id/edit        HelloWeb.ReviewController :edit\nGET     /reviews/new             HelloWeb.ReviewController :new\nGET     /reviews/:id             HelloWeb.ReviewController :show\nPOST    /reviews                 HelloWeb.ReviewController :create\nPATCH   /reviews/:id             HelloWeb.ReviewController :update\nPUT     /reviews/:id             HelloWeb.ReviewController :update\nDELETE  /reviews/:id             HelloWeb.ReviewController :delete\n...\nGET     /admin/reviews           HelloWeb.Admin.ReviewController :index\nGET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit\nGET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new\nGET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show\nPOST    /admin/reviews           HelloWeb.Admin.ReviewController :create\nPATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nPUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nDELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete\n```\n\nWhat if we had a number of resources that were all handled by admins? We could put all of them inside the same scope like this:\n\n```elixir\nscope \"/admin\", HelloWeb.Admin do\n  pipe_through :browser\n\n  resources \"/images\",  ImageController\n  resources \"/reviews\", ReviewController\n  resources \"/users\",   UserController\nend\n```\n\nHere's what `mix phx.routes` tells us:\n\n```console\n...\nGET     /admin/images            HelloWeb.Admin.ImageController :index\nGET     /admin/images/:id/edit   HelloWeb.Admin.ImageController :edit\nGET     /admin/images/new        HelloWeb.Admin.ImageController :new\nGET     /admin/images/:id        HelloWeb.Admin.ImageController :show\nPOST    /admin/images            HelloWeb.Admin.ImageController :create\nPATCH   /admin/images/:id        HelloWeb.Admin.ImageController :update\nPUT     /admin/images/:id        HelloWeb.Admin.ImageController :update\nDELETE  /admin/images/:id        HelloWeb.Admin.ImageController :delete\nGET     /admin/reviews           HelloWeb.Admin.ReviewController :index\nGET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit\nGET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new\nGET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show\nPOST    /admin/reviews           HelloWeb.Admin.ReviewController :create\nPATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nPUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update\nDELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete\nGET     /admin/users             HelloWeb.Admin.UserController :index\nGET     /admin/users/:id/edit    HelloWeb.Admin.UserController :edit\nGET     /admin/users/new         HelloWeb.Admin.UserController :new\nGET     /admin/users/:id         HelloWeb.Admin.UserController :show\nPOST    /admin/users             HelloWeb.Admin.UserController :create\nPATCH   /admin/users/:id         HelloWeb.Admin.UserController :update\nPUT     /admin/users/:id         HelloWeb.Admin.UserController :update\nDELETE  /admin/users/:id         HelloWeb.Admin.UserController :delete\n```\n\nThis is great, exactly what we want. Note how every route and controller is properly namespaced.\n\nScopes can also be arbitrarily nested, but you should do it carefully as nesting can sometimes make our code confusing and less clear. With that said, suppose that we had a versioned API with resources defined for images, reviews, and users. Then technically, we could set up routes for the versioned API like this:\n\n```elixir\nscope \"/api\", HelloWeb.Api do\n  pipe_through :api\n\n  scope \"/v1\", V1 do\n    resources \"/images\",  ImageController\n    resources \"/reviews\", ReviewController\n    resources \"/users\",   UserController\n  end\nend\n```\n\nYou can run `mix phx.routes` to see how these definitions will look like.\n\nInterestingly, we can use multiple scopes with the same path as long as we are careful not to duplicate routes. The following router is perfectly fine with two scopes defined for the same path:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use Phoenix.Router\n  ...\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    resources \"/users\", UserController\n  end\n\n  scope \"/\", AnotherAppWeb do\n    pipe_through :browser\n\n    resources \"/posts\", PostController\n  end\n  ...\nend\n```\n\nIf we do duplicate a route — which means two routes having the same path — we'll get this familiar warning:\n\n```console\nwarning: this clause cannot match because a previous clause at line 16 always matches\n```\n\n## Pipelines\n\nWe have come quite a long way in this guide without talking about one of the first lines we saw in the router: `pipe_through :browser`. It's time to fix that.\n\nPipelines are a series of plugs that can be attached to specific scopes. If you are not familiar with plugs, we have an [in-depth guide about them](plug.html).\n\nRoutes are defined inside scopes and scopes may pipe through multiple pipelines. Once a route matches, Phoenix invokes all plugs defined in all pipelines associated to that route. For example, accessing `/` will pipe through the `:browser` pipeline, consequently invoking all of its plugs.\n\nPhoenix defines two pipelines by default, `:browser` and `:api`, which can be used for a number of common tasks. In turn we can customize them as well as create new pipelines to meet our needs.\n\n### The `:browser` and `:api` pipelines\n\nAs their names suggest, the `:browser` pipeline prepares for routes which render requests for a browser, and the `:api` pipeline prepares for routes which produce data for an API.\n\nThe `:browser` pipeline has six plugs: The `plug :accepts, [\"html\"]` defines the accepted request format or formats. `:fetch_session`, which, naturally, fetches the session data and makes it available in the connection. `:fetch_live_flash`, which fetches any flash messages from LiveView and merges them with the controller flash messages. Then, the plug `:put_root_layout` will store the root layout for rendering purposes. Later `:protect_from_forgery` and `:put_secure_browser_headers`, protects form posts from cross-site forgery.\n\nCurrently, the `:api` pipeline only defines `plug :accepts, [\"json\"]`.\n\nThe router invokes a pipeline on a route defined within a scope. Routes outside of a scope have no pipelines. Although the use of nested scopes is discouraged (see above the versioned API example), if we call `pipe_through` within a nested scope, the router will invoke all `pipe_through`'s from parent scopes, followed by the nested one.\n\nThose are a lot of words bunched up together. Let's take a look at some examples to untangle their meaning.\n\nHere's another look at the router from a newly generated Phoenix application, this time with the `/api` scope uncommented back in and a route added.\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.Layouts, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end\n\n  scope \"/\", HelloWeb do\n    pipe_through :browser\n\n    get \"/\", PageController, :home\n  end\n\n  # Other scopes may use custom stacks.\n  scope \"/api\", HelloWeb do\n    pipe_through :api\n\n    resources \"/reviews\", ReviewController\n  end\n  # ...\nend\n```\n\nWhen the server accepts a request, the request will always first pass through the plugs in our endpoint, after which it will attempt to match on the path and HTTP verb.\n\nLet's say that the request matches our first route: a GET to `/`. The router will first pipe that request through the `:browser` pipeline - which will fetch the session data, fetch the flash, and execute forgery protection - before it dispatches the request to `PageController`'s `home` action.\n\nConversely, suppose the request matches any of the routes defined by the [`resources/2`](`Phoenix.Router.resources/2`) macro. In that case, the router will pipe it through the `:api` pipeline — which currently only performs content negotiation — before it dispatches further to the correct action of the `HelloWeb.ReviewController`.\n\nIf no route matches, no pipeline is invoked and a 404 error is raised.\n\n### Creating new pipelines\n\nPhoenix allows us to create our own custom pipelines anywhere in the router. To do so, we call the [`pipeline/2`](`Phoenix.Router.pipeline/2`) macro with these arguments: an atom for the name of our new pipeline and a block with all the plugs we want in it.\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {HelloWeb.Layouts, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end\n\n  pipeline :auth do\n    plug HelloWeb.Authentication\n  end\n\n  scope \"/reviews\", HelloWeb do\n    pipe_through [:browser, :auth]\n\n    resources \"/\", ReviewController\n  end\nend\n```\n\nThe above assumes there is a plug called `HelloWeb.Authentication` that performs authentication and is now part of the `:auth` pipeline.\n\nNote that pipelines themselves are plugs, so we can plug a pipeline inside another pipeline. For example, we could rewrite the `auth` pipeline above to automatically invoke `browser`, simplifying the downstream pipeline call:\n\n```elixir\n  pipeline :auth do\n    plug :browser\n    plug :ensure_authenticated_user\n    plug :ensure_user_owns_review\n  end\n\n  scope \"/reviews\", HelloWeb do\n    pipe_through :auth\n\n    resources \"/\", ReviewController\n  end\n```\n\n## How to organize my routes?\n\nIn Phoenix, we tend to define several pipelines, that provide specific functionality. For example, the `:browser` and `:api` pipelines are meant to be accessed by specific clients, browsers and http clients respectively.\n\nPerhaps more importantly, it is also very common to define pipelines specific to authentication and authorization. For example, you might have a pipeline that requires all users are authenticated. Another pipeline may enforce only admin users can access certain routes.\n\nOnce your pipelines are defined, you reuse the pipelines in the desired scopes, grouping your routes around their pipelines. For example, going back to our reviews example. Let's say anyone can read a review, but only authenticated users can create them. Your routes could look like this:\n\n```elixir\npipeline :browser do\n  ...\nend\n\npipeline :auth do\n  plug HelloWeb.Authentication\nend\n\nscope \"/\" do\n  pipe_through [:browser]\n\n  get \"/reviews\", PostController, :index\n  get \"/reviews/:id\", PostController, :show\nend\n\nscope \"/\" do\n  pipe_through [:browser, :auth]\n\n  get \"/reviews/new\", PostController, :new\n  post \"/reviews\", PostController, :create\nend\n```\n\nNote in the above how the routes are split across different scopes. While the separation can be confusing at first, it has one big upside: it is very easy to inspect your routes and see all routes that, for example, require authentication and which ones do not. This helps with auditing and making sure your routes have the proper scope.\n\nYou can create as few or as many scopes as you want. Because pipelines are reusable across scopes, they help encapsulate common functionality and you can compose them as necessary on each scope you define.\n\n## Forward\n\nThe `Phoenix.Router.forward/4` macro can be used to send all requests that start with a particular path to a particular plug. Let's say we have a part of our system that is responsible (it could even be a separate application or library) for running jobs in the background, it could have its own web interface for checking the status of the jobs. We can forward to this admin interface using:\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  ...\n\n  scope \"/\", HelloWeb do\n    ...\n  end\n\n  forward \"/jobs\", BackgroundJob.Plug\nend\n```\n\nThis means that all routes starting with `/jobs` will be sent to the `BackgroundJob.Plug` module. Inside the plug, you can match on subroutes, such as `/pending` and `/active` that shows the status of certain jobs.\n\nWe can even mix the [`forward/4`](`Phoenix.Router.forward/4`) macro with pipelines. If we wanted to ensure that the user was authenticated and was an administrator in order to see the jobs page, we could use the following in our router.\n\n```elixir\ndefmodule HelloWeb.Router do\n  use HelloWeb, :router\n\n  ...\n\n  scope \"/\" do\n    pipe_through [:authenticate_user, :ensure_admin]\n    forward \"/jobs\", BackgroundJob.Plug\n  end\nend\n```\n\nThis means the plugs in the `authenticate_user` and `ensure_admin` pipelines will be called before the `BackgroundJob.Plug` allowing them to send an appropriate response and halt the request accordingly.\n\n`BackgroundJob.Plug` can be implemented as any \"Module Plug\" discussed in the [Plug guide](plug.html). Note though it is not advised to forward to another Phoenix endpoint. This is because plugs defined by your app and the forwarded endpoint would be invoked twice, which may lead to errors.\n\n## Summary\n\nRouting is a big topic, and we have covered a lot of ground here. The important points to take away from this guide are:\n\n- Routes which begin with an HTTP verb name expand to a single clause of the match function.\n- Routes declared with `resources` expand to 8 clauses of the match function.\n- Resources may restrict the number of match function clauses by using the `only:` or `except:` options.\n- Any of these routes may be nested.\n- Any of these routes may be scoped to a given path.\n- Using verified routes with `~p` for compile-time route checks\n"
  },
  {
    "path": "guides/security.md",
    "content": "# Security \n\nAll software exposed to the public internet will be attacked. Most Phoenix applications fall into this category, so it is useful to understand common types of web application security vulnerabilities, the impact of each, and how to avoid them. Consider the following scenario:\n\nYou are responsible for a banking application which handles transactions via a Phoenix backend. A new code change introduces a remote code execution (RCE) vulnerability, granting an attacker production SSH access to the server. This results in the database being exfiltrated, user accounts being compromised, and customer funds being stolen. \n\nWith the rise of generative AI coding tools, proper judgement on the security of code has become more important than ever. This document will detail how severe vulnerabilities can occur in a Phoenix project and secure coding best practices to help avoid an incident.  \n\n## Remote Code Execution (RCE)\n\nA remote code execution (RCE) vulnerability grants an attacker the equivalent of production SSH access to your web server. This type of vulnerability is often considered the worst possible case, because it does not require user interaction and allows an attacker to bypass all security controls in your application.\n\nYou should never pass untrusted user input to the following functions:\n\n```\n# Code functions\nCode.eval_string/3\nCode.eval_file/2\nCode.eval_quoted/3\n\n# EEX functions\neval_string/3\neval_file/3\n\n# Command injection functions\n:os.cmd/2\nSystem.cmd/3\nSystem.shell/2\n```\n\nAll of these functions execute arbitrary code on your server if passed external input. The risk here is obvious to most programmers, so it is rare to find a Phoenix application vulnerable in this way. \n\nThe more common and often unexpected way a Phoenix application is vulnerable to RCE is via the Erlang function `binary_to_term`. From [the Erlang docs:](https://www.erlang.org/doc/apps/erts/erlang.html#binary_to_term/2)\n\n> When decoding binaries from untrusted sources, the untrusted source may submit data in a way to create resources, such as atoms and remote references, that cannot be garbage collected and lead to a Denial of Service (DoS) attack. In such cases, use `binary_to_term/2` with the `safe` option.\n\nThis warning is confusing, because the `safe` option mentioned above only prevents the creation of new atoms at runtime. It does not prevent the creation of executable terms via malicious user input, which is a much greater risk. \n\n```\n# Not safe\n:erlang.binary_to_term(user_input, [:safe])\n\n# Safe\nPlug.Crypto.non_executable_binary_to_term(user_input, [:safe])\n```\n\nThe function `Plug.Crypto.non_executable_binary_to_term` prevents the creation of executable terms at runtime. If you are curious how this vulnerability can occur in the real world, see the writeup on [CVE-2020-15150 in the library Paginator.](https://www.alphabot.com/security/blog/2020/elixir/Remote-code-execution-vulnerability-in-Elixir-based-Paginator-project.html)\n\n\n## SQL Injection \n\nSQL injection is on par with RCE as a highly severe vulnerability that leads to your entire database being leaked, and can even be leveraged to achieve code execution in some contexts. The good news for Phoenix developers is the majority of applications use Ecto, which is the interface to a dedicated database, typically PostgreSQL or MySQL. The Ecto query syntax protects against SQL injection by default:\n\n```\n# Safe, using query syntax \ndef a_get_fruit(min_q) do\n  from(\n    f in Fruit,\n    where:\n      f.quantity >= ^min_q and\n        f.secret == false\n  )\n  |> Repo.all()\nend\n```\n\nThe `fragment` function seems to be a vector for SQL injection:\n\n```\n# Fails to compile \ndef b_get_fruit(min_q) do\n  from(\n    f in Fruit,\n    where: fragment(\"f0.quantity >= #{min_q} AND f0.secret = FALSE\")\n  )\n  |> Repo.all()\nend\n```\n\nYet if you try to compile the above code it fails:\n\n```text\nCompiling 1 file (.ex)\n\n== Compilation error in file lib/basket/goods.ex ==\n** (Ecto.Query.CompileError) to prevent SQL injection attacks, fragment(...) \ndoes not allow strings to be interpolated as the first argument via the `^` \noperator, got: `\"f0.quantity >= #{min_q} AND f0.secret = FALSE\"`\n```\n\nWhat about passing arguments via fragment?\n\n```\n# Safe\ndef c_get_fruit(min_q) do\n  min_q = String.to_integer(min_q)\n  from(\n    f in Fruit,\n    where: fragment(\"f0.quantity >= ? AND f0.secret = FALSE\", ^min_q)\n  )\n  |> Repo.all()\nend\n```\n\nThe above code is safe because the external user input is safely parameterized into the query. What if you decide to pass arguments directly into raw SQL?\n\n```\n# Safe\ndef d_get_fruit(min_q) do\n  q = \"\"\"\n  SELECT f.id, f.name, f.quantity, f.secret\n  FROM fruits AS f\n  WHERE f.quantity > $1 AND f.secret = FALSE\n  \"\"\"\n  {:ok, %{rows: rows}} =\n    Ecto.Adapters.SQL.query(Repo, q, [String.to_integer(min_q)])\nend\n```\n\nThe above code is safe, similar to the fragment example, because the user input is being safely parameterized. \n\nConstructing a SQL query string via user input, then passing it directly to the query function does lead to SQL injection: \n\n```\n# Vulnerable to SQL injection\ndef e_get_fruit(min_q) do\n  q = \"\"\"\n  SELECT f.id, f.name, f.quantity, f.secret\n  FROM fruits AS f\n  WHERE f.quantity > #{min_q} AND f.secret = FALSE\n  \"\"\"\n  {:ok, %{rows: rows}} =\n    Ecto.Adapters.SQL.query(Repo, q)\nend\n```\n\nIf you find yourself writing raw SQL, take care not to interpolate directly into the string, but rather use parameters for external input into the query.\n\n## Server Side Request Forgery (SSRF)\n\nServer Side Request Forgery (SSRF) is a critical vulnerability that has been the root cause of major data breaches. The problem is untrusted user input being used to make outbound HTTP requests, which leads to the exploitation of services reachable from your Phoenix application. \n\nWhen you create a server in most cloud providers today, for example AWS EC2, there will be a [metadata service](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html) exposed on a private IP address. In the case of AWS it's on `169.254.169.254`. If you send an HTTP request to:\n\n`http://169.254.169.254/iam/security-credentials`\n\nFrom the server, it will return credentials for the AWS account. If you write a web application (for example in Phoenix) where a user can enter a URL, and then view the response of an HTTP request sent to that URL, that functionality is potentially vulnerable to SSRF. For a real world incident see [the Capital One breach.](https://dl.acm.org/doi/pdf/10.1145/3546068)\n\nYour reaction to this may be \"This seems like an unsafe feature, considering they named an entire vulnerability class after it.\" You are not alone in this opinion, AWS introduced some SSRF specific mitigations in [2019 via IMDSv2](https://aws.amazon.com/blogs/security/defense-in-depth-open-firewalls-reverse-proxies-ssrf-vulnerabilities-ec2-instance-metadata-service/), requiring HTTP requests to the metadata endpoint to have session authentication. \n\nThe instance metadata service of cloud servers is not the only target for SSRF: services such as PostgreSQL, MySQL, Redis, Elasticsearch, even custom micro-services that you believe are not exposed to the public internet may be vulnerable to SSRF. Consider an internal server with vulnerabilities that can be exploited via HTTP requests, not directly exposed to the public internet. Yet if that network also has a Phoenix application vulnerable to SSRF, an attacker can go through the Phoenix server to attack the private server. \n\nTo summarize, using an HTTP client does not necessarily mean your application is vulnerable to SSRF. Rather the following conditions must be met:\n\n1. You are using external user input to construct URLs\n2. An attacker is able to send HTTP requests to some vulnerable server on your network \n\nFor an Elixir specific example consider the Req library:\n\n```\nreq = Req.new(base_url: \"https://api.github.com\")\n\nReq.get!(req, url: \"/repos/sneako/finch\").body[\"description\"]\n#=> \"Elixir HTTP client, focused on performance\"\n```\n\nIt is possible to override the `base_url` value:\n\n```\nreq = Req.new(base_url: \"https://elixir-lang.org/\")\nuser_input = \"https://dashbit.co/blog\"\nReq.get!(req, url: user_input) \n```\n\nThe above code sends a request to `https://dashbit.co/blog`, NOT `https://elixir-lang.org/`. \n\nConsider a similar example in Tesla:\n\n```\nplug Tesla.Middleware.BaseUrl, \"https://example.com/foo\"\n\nMyClient.get(\"http://example.com/bar\") # equals to GET http://example.com/bar\n```\n\nEven if you are setting a base URL in your application, don't treat it as a security barrier, because as these examples show it can be overwritten. Take care to avoid sending HTTP requests based on user input if you can avoid it, and be mindful of services on the same network as your Phoenix application that could be exploited via SSRF.\n\n## Cross Origin Resource Sharing (CORS) Misconfiguration \n\nCross Origin Resource Sharing (CORS) is a feature in modern web browsers which can be used to bypass the same origin policy, which is useful because your application can now load resources from a different site in the user's web browser. This is normally restricted by default. \n\nConsider a Phoenix API with a single page application (SPA) on a different domain:\n\n`app.example.com`  - React frontend \n\n`api.example.com`  - Backend in Elixir/Phoenix \n\nThe frontend `(app.example.com)` needs to fetch information about the current user, however the origins of these projects are different. The user's web browser will block the request unless CORS is enabled between the sites.\n\nSetting the following in the Phoenix application:\n\n```\nplug CORSPlug, origin: [\"https://app.example.com\"]\n```\n\nIs the correct way to do this. However it is also possible to set a policy that is far too broad, for example:\n\n```\nplug CORSPlug, origin: ~r/^http.*/\n```\n\nThis is a major security risk, because now a malicious site loaded by a user who is logged into the application can read sensitive data, for example API keys. Take care to ensure only trusted sites are allowed here. \n\n## Broken Access Control\n\nIn Phoenix controllers the standard way to handle authorization is putting the current user in the assigns, and then doing:\n\n```\nuser = conn.assigns.current_user\n```\n\nMany projects add this as a third argument in the controller actions:\n\n```\ndef action(conn, _) do\n  args = [conn, conn.params, conn.assigns.current_user]\n  apply(__MODULE__, action_name(conn), args)\nend\n```\n\nThis is the correct approach. The wrong approach is to accept arbitrary user input when making authorization decisions, for example:\n\n```\n# Not safe\ndef index(conn, %{\"user\" => user_email})\n  user = Accounts.get_user_by_email(user_email)\n```\n\nIn the above example an attacker can simply change the submitted `user_email` string to an arbitrary value to perform an action as a different user. Using `conn.assigns.current_user` avoids this problem. \n\nRelated to the above concept, the design of Ecto in Phoenix takes the risk of mass assignment into consideration, because you have to explicitly define what parameters are allowed to be set from user supplied data.  Consider a simple users schema:\n\n```\n  schema \"users\" do\n    field :email, :string\n    field :password, :string, virtual: true, redact: true\n    field :hashed_password, :string, redact: true\n    field :confirmed_at, :naive_datetime\n    field :is_admin, :boolean \n\n    timestamps(type: :utc_datetime)\n  end\n\n\n  def registration_changeset(user, attrs, opts \\\\ []) do\n    user\n    |> cast(attrs, [:email, :password, :is_admin])\n    |> validate_email()\n    |> validate_password(opts)\n  end\n```\n\nAssume that the corresponding signup form is exposed to the public internet. Can you spot the vulnerability? The problem is that `:is_admin` should never be set via external user input. Anyone on the public internet can now create a user where `:is_admin` is set to true in the database, which is likely not the intent of the developer.\n\n## Cross Site Scripting (XSS) \n\nBy default, user input in Phoenix is escaped so that:\n\n```\n<%= \"<hello>\" %>\n```\n\nis shown in your browser as:\n\n```text\n&lt;hello&gt;\n```\n\nNote that this looks like a normal string of `<hello>` from an end user perspective, the `&lt;` and `&gt;` are only visible if you inspect the page with your browser tools. \n\nIt is possible to bypass this protection via the `raw/1` function. For example, consider the string `<b>hello</b>`. \n\n```\n# This will render the literal string <b>hello</b>\n<%= \"<b>hello</b>\" %>\n\n# This will render a bold hello \n<%= raw \"<b>hello</b>\" %>\n```\n\nWith the ability to inject script tags, an attacker now has the ability to execute JavaScript in a victim's browser, for example by submitting a string such as: \n\n```html\n<script>alert(1)</script>\n```\n\nIf someone submits the above input to your application, and it is displayed to logged in users, you have a serious problem. See the [2005 MySpace worm](https://en.wikipedia.org/wiki/Samy_(computer_worm)) for a real world example of this. \n\nUser input should never be passed into the `raw/1` function. There are some additional vectors for XSS in Phoenix applications, for example consider the following controller functions: \n\n\n```\ndef html_resp(conn, %{\"i\" => i}) do\n  html(conn, \"<html><head>#{i}</head></html>\")\nend\n```\n\nBecause the HTML is being generated from user input, the function is vulnerable to XSS. This can also happen with `put_resp_content_type`:\n\n```\ndef send_resp_html(conn, %{\"i\" => i}) do\n  conn\n  |> put_resp_content_type(\"text/html\")\n  |> send_resp(200, \"#{i}\")\nend\n```\n\nFile uploads can also lead to XSS.\n\n```\ndef new_upload(conn, _params) do\n  render(conn, \"new_upload.html\")\nend\n\ndef upload(conn, %{\"upload\" => upload}) do\n  %Plug.Upload{content_type: content_type, filename: filename, path: path} = upload\n  ImgServer.put(filename, %{content_type: content_type, bin: File.read!(path)})\n  redirect(conn, to: Routes.page_path(conn, :view_photo, filename))\nend\n\ndef view_photo(conn, %{\"filename\" => filename}) do\n  case ImgServer.get(filename) do\n    %{content_type: content_type, bin: bin} ->\n      conn\n      |> put_resp_content_type(content_type)\n      |> send_resp(200, bin)\n    _ ->\n      conn\n      |> put_resp_content_type(\"text/html\")\n      |> send_resp(404, \"Not Found\")\n  end\nend\n```\n\nUser input determines the `content-type` of the file. There is no validation on the type of file being uploaded, meaning `content-type` can be set so an HTML page is rendered. This is the source of the vulnerability. Consider the file `xss.html`, with the contents:\n\n```html\n<html><script>alert(1)</script></html>\n```\n\nThis will result in JavaScript being executed in the browser of the victim who views the image. Restricting the `put_resp_content_type` argument to only image files would fix this vulnerability. \n\n\n## Cross Site Request Forgery (CSRF)\n\n### Standard CSRF in HTML forms\n\nCross site request forgery (CSRF) is a vulnerability that exists due to a quirk in how web browsers work. Consider a social media website that is vulnerable to CSRF. An attacker creates a malicious website aimed at legitimate users. When a victim visits the malicious site, it triggers a POST request in the victim’s browser, sending a message that was written by the attacker. This results in the victim’s account making a post written by the attacker.\n\nBut why is this possible? Shouldn't the web browser block POST requests initiated by a different website? There is a cookie feature called SameSite that addresses this problem, however it's good to understand what CSRF is and why Phoenix has built in protections. \n\nConsider the following form:\n\n```html\n<form action=\"/posts\" method=\"post\">\n\n  <label for=\"post_title\">Title</label>\n  <input id=\"post_title\" name=\"post[title]\" type=\"text\">\n\n  <label for=\"post_body\">Body</label>\n  <textarea id=\"post_body\" name=\"post[body]\">\n  </textarea>\n\n  <div>\n    <button type=\"submit\">Save</button>\n  </div>\n</form>\n\n```\n\nThis maps to the following HTTP request:\n\n```http_request_and_response\nPOST /posts HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 53\n\npost[title]=My+Title&post[body]=This+is+the+body\n```\n\nAn attacker can embed the following form on `attacker.com`:\n\n```html\n<form action=\"https://example.com/posts\" method=\"post\">\n\n  <label for=\"post_title\">Title</label>\n  <input id=\"post_title\" name=\"post[title]\" type=\"text\" value=\"Attacker post\">\n\n  <label for=\"post_body\">Body</label>\n  <textarea id=\"post_body\" name=\"post[body]\">This post was written by the bad guy</textarea>\n\n  <div>\n    <button type=\"submit\">Save</button>\n  </div>\n</form>\n```\n\nNote that this form does not even have to be visible to the victim user. When the user visits `attacker.com` the form will automatically submit a POST request on behalf of the victim, with the victim's current session cookie, to the vulnerable site. \n\nThe way most web frameworks, including Phoenix, mitigate this vulnerability is by requiring a CSRF token when submitting a form.\n\n```html\n<!-- A typical CSRF token seen in a Phoenix form -->\n<input name=\"_csrf_token\" type=\"hidden\" hidden=\"\" \n  value=\"WUZXJh07BhAIJ24jP1d-KQEpLwYmMDwQ0-2eYNLH_x8oHoO_qv_HJDqZ\">\n```\n\nThis changes the previous HTTP request to:\n\n```http_request_and_response\nPOST /posts HTTP/1.1\nHost: example.com\nContent-Type: application/x-www-form-urlencoded\nContent-Length: 53\n\npost[title]=My+Title&post[body]=This+is+the+body&post[_csrf_token]=WUZXJh07BhAIJ24jP1d-KQEpLwYmMDwQ0-2eYNLH_x8oHoO_qv_HJDqZ\n```\n\nBecause the token is randomly generated, an attacker cannot predict what the value will be. The application is safe against CSRF attacks because Phoenix checks the value of this token by default via the `:protect_from_forgery` plug, which is included in the default `:browser` pipeline of a new Phoenix project:\n\n```\n# router.ex\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, {CarafeWeb.LayoutView, :root}\n    plug :protect_from_forgery      # <-- Checks the CSRF token value\n    plug :put_secure_browser_headers\n    plug :fetch_current_user\n  end\n```\n\nCSRF protections are included in Phoenix by default, so your application is most likely not vulnerable if you are using the default settings. \n\n### Action Re-Use CSRF \n\nMost descriptions of CSRF focus on state changing POST requests and the need for random tokens, as was just covered. Is CSRF possible with a GET request? Yes, GET requests should never be used to perform state changing actions in a web application (place an order, transfer money, etc) because they cannot be protected against CSRF in the same way POST requests can. \n\nConsider the following form:\n\n```heex\n<.form :let={f} for={@bio_changeset} action={~p\"/users/settings/edit_bio\"} method=\"post\" id=\"edit_bio\">\n  <div class=\"pt-4\">\n    <%= label f, :bio %>\n    <%= textarea f, :bio, required: true, class: \"\" %>\n    <%= error_tag f, :bio %>\n  </div>\n\n  <div>\n    <%= submit \"Update Bio\", class: \"\" %>\n  </div>\n</.form>\n```\n\nSubmitting this form triggers a POST request:\n\n```text\n[info] POST /users/settings/edit_bio\n[debug] Processing with CarafeWeb.UserSettingsController.edit_bio/2\n  Parameters: %{\"_csrf_token\" => \"cigMDlcdAnxbWW88OAAiBHoZZ3cmCW0l1refzZ7D666RWvjCLi02UZXq\", \"user\" => %{\"bio\" => \"My bio here\"}}\n  Pipelines: [:browser, :require_authenticated_user]\n```\n\nThe important point is that the above parameters match the route and controller action:\n\n```\n# router.ex\npost \"/users/settings/edit_bio\", UserSettingsController, :edit_bio\n\n# user_settings_controller.ex\ndef edit_bio(conn, %{\"user\" => params}) do\n  case Accounts.update_user_bio(conn.assigns.current_user, params) do\n    {:ok, _bio} ->\n      conn\n      |> put_flash(:info, \"Bio Update Successful\")\n      |> redirect(to: Routes.user_settings_path(conn, :edit))\n    {:error, changeset} ->\n      render(conn, \"edit.html\", bio_changeset: changeset)\n  end\nend\n```\n\nThe parameters:\n\n```\n%{\"_csrf_token\" => \"cigMDlcdAnxbWW88OAAiBHoZZ3cmCW0l1refzZ7D666RWvjCLi02UZXq\", \"user\" => %{\"bio\" => \"My bio here\"}}\n```\n\nMatch the `edit_bio/2` action. Note that the `_csrf_token` is processed in the router by the plug `:protect_from_forgery` built into Phoenix. \n\nNow consider the following update to the router:\n\n```\n# router.ex\nget \"/users/settings/edit_bio\", UserSettingsController, :edit_bio\npost \"/users/settings/edit_bio\", UserSettingsController, :edit_bio\n```\n\nAllowing access to the `edit_bio/2` controller action via a GET request introduces an action re-use CSRF vulnerability. If the victim visits the following URL:\n\n`/users/settings/edit_bio?user%5Bbio%5D=Hacked+LOL`\n\nIt will trigger the following:\n\n```text\n[info] GET /users/settings/edit_bio\n[debug] Processing with CarafeWeb.UserSettingsController.edit_bio/2\n  Parameters: %{\"user\" => %{\"bio\" => \"Hacked LOL\"}}\n  Pipelines: [:browser, :require_authenticated_user]\n```\n\nThese parameters will match the `edit_bio/2` controller action, updating the user biography. The key lesson here is that state changing actions (transferring money, creating a post, updating account information) should occur via a POST request with proper CSRF protections, never via a GET request. \n\n## Further Reading\n\nThe Erlang Ecosystem Foundation also publishes in-depth documents which are relevant for Erlang, Elixir, and Phoenix developers. These include:\n\n  * [Web Application Security Best Practices for BEAM languages](https://security.erlef.org/web_app_security_best_practices_beam/)\n\n  * [Secure Coding and Deployment Hardening Guidelines](https://security.erlef.org/secure_coding_and_deployment_hardening/)\n"
  },
  {
    "path": "guides/telemetry.md",
    "content": "# Telemetry\n\nIn this guide, we will show you how to instrument and report\non `:telemetry` events in your Phoenix application.\n\n> `te·lem·e·try` - the process of recording and transmitting\nthe readings of an instrument.\n\nAs you follow along with this guide, we will introduce you to\nthe core concepts of Telemetry, you will initialize a\nreporter to capture your application's events as they occur,\nand we will guide you through the steps to properly\ninstrument your own functions using `:telemetry`. Let's take\na closer look at how Telemetry works in your application.\n\n## Overview\n\nThe `:telemetry` library allows you to emit events at various stages of an application's lifecycle. You can then respond to these events by, among other things, aggregating them as metrics and sending the metrics data to a reporting destination.\n\nTelemetry stores events by their name in an ETS table, along with the handler for each event. Then, when a given event is executed, Telemetry looks up its handler and invokes it.\n\nPhoenix's Telemetry tooling provides you with a supervisor that uses `Telemetry.Metrics` to define the list of Telemetry events to handle and how to handle those events, i.e. how to structure them as a certain type of metric. This supervisor works together with Telemetry reporters to respond to the specified Telemetry events by aggregating them as the appropriate metric and sending them to the correct reporting destination.\n\n## The Telemetry supervisor\n\nSince v1.5, new Phoenix applications are generated with a\nTelemetry supervisor. This module is responsible for\nmanaging the lifecycle of your Telemetry processes. It also\ndefines a `metrics/0` function, which returns a list of\n[`Telemetry.Metrics`](https://hexdocs.pm/telemetry_metrics)\nthat you define for your application.\n\nBy default, the supervisor also starts\n[`:telemetry_poller`](https://hexdocs.pm/telemetry_poller).\nBy simply adding `:telemetry_poller` as a dependency, you\ncan receive VM-related events on a specified interval.\n\nIf you are coming from an older version of Phoenix, install\nthe `:telemetry_metrics` and `:telemetry_poller` packages:\n\n```elixir\n{:telemetry_metrics, \"~> 1.0\"},\n{:telemetry_poller, \"~> 1.0\"}\n```\n\nand create your Telemetry supervisor at\n`lib/my_app_web/telemetry.ex`:\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndefmodule MyAppWeb.Telemetry do\n  use Supervisor\n  import Telemetry.Metrics\n\n  def start_link(arg) do\n    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)\n  end\n\n  def init(_arg) do\n    children = [\n      {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}\n      # Add reporters as children of your supervision tree.\n      # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  def metrics do\n    [\n      # Phoenix Metrics\n      summary(\"phoenix.endpoint.stop.duration\",\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.router_dispatch.stop.duration\",\n        tags: [:route],\n        unit: {:native, :millisecond}\n      ),\n      # VM Metrics\n      summary(\"vm.memory.total\", unit: {:byte, :kilobyte}),\n      summary(\"vm.total_run_queue_lengths.total\"),\n      summary(\"vm.total_run_queue_lengths.cpu\"),\n      summary(\"vm.total_run_queue_lengths.io\")\n    ]\n  end\n\n  defp periodic_measurements do\n    [\n      # A module, function and arguments to be invoked periodically.\n      # This function must call :telemetry.execute/3 and a metric must be added above.\n      # {MyApp, :count_users, []}\n    ]\n  end\nend\n```\n\nMake sure to replace MyApp by your actual application name.\n\nThen add to your main application's supervision tree\n(usually in `lib/my_app/application.ex`):\n\n```elixir\nchildren = [\n  MyAppWeb.Telemetry,\n  MyApp.Repo,\n  MyAppWeb.Endpoint,\n  ...\n]\n```\n\n## Telemetry Events\n\nMany Elixir libraries (including Phoenix) are already using\nthe [`:telemetry`](https://hexdocs.pm/telemetry) package as a\nway to give users more insight into the behavior of their\napplications, by emitting events at key moments in the\napplication lifecycle.\n\nA Telemetry event is made up of the following:\n\n  * `name` - A string (e.g. `\"my_app.worker.stop\"`) or a\n    list of atoms that uniquely identifies the event.\n\n  * `measurements` - A map of atom keys (e.g. `:duration`)\n    and numeric values.\n\n  * `metadata` - A map of key-value pairs that can be used\n    for tagging metrics.\n\n### A Phoenix Example\n\nHere is an example of an event from your endpoint:\n\n* `[:phoenix, :endpoint, :stop]` - dispatched by\n  `Plug.Telemetry`, one of the default plugs in your endpoint, whenever the response is\n  sent\n\n  * Measurement: `%{duration: native_time}`\n\n  * Metadata: `%{conn: Plug.Conn.t}`\n\nThis means that after each request, `Plug`, via `:telemetry`,\nwill emit a \"stop\" event, with a measurement of how long it\ntook to get the response:\n\n```elixir\n:telemetry.execute([:phoenix, :endpoint, :stop], %{duration: duration}, %{conn: conn})\n```\n\n### Phoenix Telemetry Events\n\nA full list of all Phoenix telemetry events can be found in `Phoenix.Logger`\n\n## Metrics\n\n> Metrics are aggregations of Telemetry events with a\n> specific name, providing a view of the system's behaviour\n> over time.\n>\n> ― `Telemetry.Metrics`\n\nThe Telemetry.Metrics package provides a common interface\nfor defining metrics. It exposes a set of [five metric type functions](https://hexdocs.pm/telemetry_metrics/Telemetry.Metrics.html#module-metrics) that are responsible for structuring a given Telemetry event as a particular measurement.\n\nThe package does not perform any aggregation of the measurements itself. Instead, it provides a reporter with the Telemetry event-as-measurement definition and the reporter uses that definition to perform aggregations and report them.\n\nWe will discuss\nreporters in the next section.\n\nLet's take a look at some examples.\n\nUsing `Telemetry.Metrics`, you can define a counter metric,\nwhich counts how many HTTP requests were completed:\n\n```elixir\nTelemetry.Metrics.counter(\"phoenix.endpoint.stop.duration\")\n```\n\nor you could use a distribution metric to see how many\nrequests were completed in particular time buckets:\n\n```elixir\nTelemetry.Metrics.distribution(\"phoenix.endpoint.stop.duration\")\n```\n\nThis ability to introspect HTTP requests is really powerful --\nand this is but one of _many_ telemetry events emitted by\nthe Phoenix framework! We'll discuss more of these events,\nas well as specific patterns for extracting valuable data\nfrom Phoenix/Plug events in the\n[Phoenix Metrics](#phoenix-metrics) section later in this\nguide.\n\n> The full list of `:telemetry` events emitted from Phoenix,\nalong with their measurements and metadata, is available in\nthe \"Instrumentation\" section of the `Phoenix.Logger` module\ndocumentation.\n\n### An Ecto Example\n\nLike Phoenix, Ecto ships with built-in Telemetry events.\nThis means that you can gain introspection into your web\nand database layers using the same tools.\n\nHere is an example of a Telemetry event executed by Ecto when an Ecto repository starts:\n\n* `[:ecto, :repo, :init]` - dispatched by `Ecto.Repo`\n\n  * Measurement: `%{system_time: native_time}`\n\n  * Metadata: `%{repo: Ecto.Repo, opts: Keyword.t()}`\n\nThis means that whenever the `Ecto.Repo` starts, it will emit an event, via `:telemetry`,\nwith a measurement of the time at start-up.\n\n```elixir\n:telemetry.execute([:ecto, :repo, :init], %{system_time: System.system_time()}, %{repo: repo, opts: opts})\n```\n\nAdditional Telemetry events are executed by Ecto adapters.\n\nOne such adapter-specific event is the `[:my_app, :repo, :query]` event.\nFor instance, if you want to graph query execution time, you can use the `Telemetry.Metrics.summary/2` function to instruct your reporter to calculate statistics of the `[:my_app, :repo, :query]` event, like maximum, mean, percentiles etc.:\n\n```elixir\nTelemetry.Metrics.summary(\"my_app.repo.query.query_time\",\n  unit: {:native, :millisecond}\n)\n```\n\nOr you could use the `Telemetry.Metrics.distribution/2` function to define a histogram for another adapter-specific event: `[:my_app, :repo, :query, :queue_time]`, thus visualizing how long queries spend queued:\n\n```elixir\nTelemetry.Metrics.distribution(\"my_app.repo.query.queue_time\",\n  unit: {:native, :millisecond}\n)\n```\n\n> You can learn more about Ecto Telemetry in the \"Telemetry\nEvents\" section of the\n[`Ecto.Repo`](https://hexdocs.pm/ecto/Ecto.Repo.html) module\ndocumentation.\n\nSo far we have seen some of the Telemetry events common to\nPhoenix applications, along with some examples of their\nvarious measurements and metadata. With all of this data\njust waiting to be consumed, let's talk about reporters.\n\n## Reporters\n\nReporters subscribe to Telemetry events using the common\ninterface provided by `Telemetry.Metrics`. They then\naggregate the measurements (data) into metrics to provide\nmeaningful information about your application.\n\nFor example, if the following `Telemetry.Metrics.summary/2` call is added to the `metrics/0` function of your Telemetry supervisor:\n\n```elixir\nsummary(\"phoenix.endpoint.stop.duration\",\n  unit: {:native, :millisecond}\n)\n```\n\nThen the reporter will attach a listener for the `\"phoenix.endpoint.stop.duration\"` event and will respond to this event by calculating a summary metric with the given event metadata and reporting on that metric to the appropriate source.\n\n### Phoenix.LiveDashboard\n\nFor developers interested in real-time visualizations for\ntheir Telemetry metrics, you may be interested in installing\n[`LiveDashboard`](https://hexdocs.pm/phoenix_live_dashboard).\nLiveDashboard acts as a Telemetry.Metrics reporter to render\nyour data as beautiful, real-time charts on the dashboard.\n\n### Telemetry.Metrics.ConsoleReporter\n\n`Telemetry.Metrics` ships with a `ConsoleReporter` that can\nbe used to print events and metrics to the terminal. You can\nuse this reporter to experiment with the metrics discussed in\nthis guide.\n\nUncomment or add the following to this list of children in\nyour Telemetry supervision tree (usually in\n`lib/my_app_web/telemetry.ex`):\n\n```elixir\n{Telemetry.Metrics.ConsoleReporter, metrics: metrics()}\n```\n\n> There are numerous reporters available, for services like\n> StatsD, Prometheus, and more. You can find them by\n> searching for \"telemetry_metrics\" on [hex.pm](https://hex.pm/packages?search=telemetry_metrics).\n\n## Phoenix Metrics\n\nEarlier we looked at the \"stop\" event emitted by\n`Plug.Telemetry`, and used it to count the number of HTTP\nrequests. In reality, it's only somewhat helpful to be\nable to see just the total number of requests. What if you\nwanted to see the number of requests per route, or per route\n_and_ method?\n\nLet's take a look at another event emitted during the HTTP\nrequest lifecycle, this time from `Phoenix.Router`:\n\n* `[:phoenix, :router_dispatch, :stop]` - dispatched by\n  Phoenix.Router after successfully dispatching to a matched\n  route\n\n  * Measurement: `%{duration: native_time}`\n\n  * Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom]}`\n\nLet's start by grouping these events by route. Add the\nfollowing (if it does not already exist) to the `metrics/0`\nfunction of your Telemetry supervisor (usually in\n`lib/my_app_web/telemetry.ex`):\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndef metrics do\n  [\n    ...metrics...\n    summary(\"phoenix.router_dispatch.stop.duration\",\n      tags: [:route],\n      unit: {:native, :millisecond}\n    )\n  ]\nend\n```\n\nRestart your server, and then make requests to a page or two.\nIn your terminal, you should see the ConsoleReporter print\nlogs for the Telemetry events it received as a result of\nthe metrics definitions you provided.\n\nThe log line for each request contains the specific route\nfor that request. This is due to specifying the `:tags`\noption for the summary metric, which takes care of our first\nrequirement; we can use `:tags` to group metrics by route.\nNote that reporters will necessarily handle tags differently\ndepending on the underlying service in use.\n\nLooking more closely at the Router \"stop\" event, you can see\nthat the `Plug.Conn` struct representing the request is\npresent in the metadata, but how do you access the\nproperties in `conn`?\n\nFortunately, `Telemetry.Metrics` provides the following\noptions to help you classify your events:\n\n* `:tags` - A list of metadata keys for grouping;\n\n* `:tag_values` - A function which transforms the metadata\n  into the desired shape; Note that this function is called\n  for each event, so it's important to keep it fast if the\n  rate of events is high.\n\n> Learn about all the available metrics options in the\n`Telemetry.Metrics` module documentation.\n\nLet's find out how to extract more tags from events that\ninclude a `conn` in their metadata.\n\n### Extracting tag values from Plug.Conn\n\nLet's add another metric for the route event, this time to\ngroup by route and method:\n\n```elixir\nsummary(\"phoenix.router_dispatch.stop.duration\",\n  tags: [:method, :route],\n  tag_values: &get_and_put_http_method/1,\n  unit: {:native, :millisecond}\n)\n```\n\nWe've introduced the `:tag_values` option here, because we\nneed to perform a transformation on the event metadata in\norder to get to the values we need.\n\nAdd the following private function to your Telemetry module\nto lift the `:method` value from the `Plug.Conn` struct:\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndefp get_and_put_http_method(%{conn: %{method: method}} = metadata) do\n  Map.put(metadata, :method, method)\nend\n```\n\nRestart your server and make some more requests. You should\nbegin to see logs with tags for both the HTTP method and the\nroute.\n\nNote the `:tags` and `:tag_values` options can be applied to\nall `Telemetry.Metrics` types.\n\n### Renaming value labels using tag values\n\nSometimes when displaying a metric, the value label may need to be transformed\nto improve readability. Take for example the following metric that displays the\nduration of the each LiveView's `mount/3` callback by `connected?` status.\n\n```elixir\nsummary(\"phoenix.live_view.mount.stop.duration\",\n  unit: {:native, :millisecond},\n  tags: [:view, :connected?],\n  tag_values: &live_view_metric_tag_values/1\n)\n```\n\nThe following function lifts `metadata.socket.view` and\n`metadata.socket.connected?` to be top-level keys on `metadata`, as we did in\nthe previous example.\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndefp live_view_metric_tag_values(metadata) do\n  metadata\n  |> Map.put(:view, metadata.socket.view)\n  |> Map.put(:connected?, Phoenix.LiveView.connected?(metadata.socket))\nend\n```\n\nHowever, when rendering these metrics in LiveDashboard, the value label is\noutput as `\"Elixir.Phoenix.LiveDashboard.MetricsLive true\"`.\n\nTo make the value label easier to read, we can update our private function to\ngenerate more user friendly names. We'll run the value of the `:view` through\n`inspect/1` to remove the `Elixir.` prefix and call another private function to\nconvert the `connected?` boolean into human readable text.\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndefp live_view_metric_tag_values(metadata) do\n  metadata\n  |> Map.put(:view, inspect(metadata.socket.view))\n  |> Map.put(:connected?, get_connection_status(Phoenix.LiveView.connected?(metadata.socket)))\nend\n\ndefp get_connection_status(true), do: \"Connected\"\ndefp get_connection_status(false), do: \"Disconnected\"\n```\n\nNow the value label will be rendered like `\"Phoenix.LiveDashboard.MetricsLive\nConnected\"`.\n\nHopefully, this gives you some inspiration on how to use the `:tag_values`\noption. Just remember to keep this function fast since it is called on every\nevent.\n\n## Periodic measurements\n\nYou might want to periodically measure key-value pairs within\nyour application. Fortunately the\n[`:telemetry_poller`](https://hexdocs.pm/telemetry_poller)\npackage provides a mechanism for custom measurements,\nwhich is useful for retrieving process information or for\nperforming custom measurements periodically.\n\nAdd the following to the list in your Telemetry supervisor's\n`periodic_measurements/0` function, which is a private\nfunction that returns a list of measurements to take on a\nspecified interval.\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndefp periodic_measurements do\n  [\n    {MyApp, :measure_users, []},\n    {:process_info,\n      event: [:my_app, :my_server],\n      name: MyApp.MyServer,\n      keys: [:message_queue_len, :memory]}\n  ]\nend\n```\n\nwhere `MyApp.measure_users/0` could be written like this:\n\n```elixir\n# lib/my_app.ex\ndefmodule MyApp do\n  def measure_users do\n    :telemetry.execute([:my_app, :users], %{total: MyApp.users_count()}, %{})\n  end\nend\n```\n\nNow with measurements in place, you can define the metrics for the\nevents above:\n\n```elixir\n# lib/my_app_web/telemetry.ex\ndef metrics do\n  [\n    ...metrics...\n    # MyApp Metrics\n    last_value(\"my_app.users.total\"),\n    last_value(\"my_app.my_server.memory\", unit: :byte),\n    last_value(\"my_app.my_server.message_queue_len\")\n    summary(\"my_app.my_server.call.stop.duration\"),\n    counter(\"my_app.my_server.call.exception\")\n  ]\nend\n```\n\n> You will implement MyApp.MyServer in the\n[Custom Events](#custom-events) section.\n\n## Libraries using Telemetry\n\nTelemetry is quickly becoming the de-facto standard for\npackage instrumentation in Elixir. Here is a list of\nlibraries currently emitting `:telemetry` events.\n\nLibrary authors are actively encouraged to send a PR adding\ntheir own (in alphabetical order, please):\n\n* [Absinthe](https://hexdocs.pm/absinthe) - [Events](https://hexdocs.pm/absinthe/telemetry.html)\n* [Ash Framework](https://hexdocs.pm/ash) - [Events](https://hexdocs.pm/ash/monitoring.html)\n* [Broadway](https://hexdocs.pm/broadway) - [Events](https://hexdocs.pm/broadway/Broadway.html#module-telemetry)\n* [Ecto](https://hexdocs.pm/ecto) - [Events](https://hexdocs.pm/ecto/Ecto.Repo.html#module-telemetry-events)\n* [Oban](https://hexdocs.pm/oban) - [Events](https://hexdocs.pm/oban/Oban.Telemetry.html)\n* [Phoenix](https://hexdocs.pm/phoenix) - [Events](https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-instrumentation)\n* [Plug](https://hexdocs.pm/plug) - [Events](https://hexdocs.pm/plug/Plug.Telemetry.html)\n* [Tesla](https://hexdocs.pm/tesla) - [Events](https://hexdocs.pm/tesla/Tesla.Middleware.Telemetry.html)\n\n## Custom Events\n\nIf you need custom metrics and instrumentation in your\napplication, you can utilize the `:telemetry` package\n(<https://hexdocs.pm/telemetry>) just like your favorite\nframeworks and libraries.\n\nHere is an example of a simple GenServer that emits telemetry\nevents. Create this file in your app at\n`lib/my_app/my_server.ex`:\n\n```elixir\n# lib/my_app/my_server.ex\ndefmodule MyApp.MyServer do\n  @moduledoc \"\"\"\n  An example GenServer that runs arbitrary functions and emits telemetry events when called.\n  \"\"\"\n  use GenServer\n\n  # A common prefix for :telemetry events\n  @prefix [:my_app, :my_server, :call]\n\n  def start_link(fun) do\n    GenServer.start_link(__MODULE__, fun, name: __MODULE__)\n  end\n\n  @doc \"\"\"\n  Runs the function contained within this server.\n\n  ## Events\n\n  The following events may be emitted:\n\n    * `[:my_app, :my_server, :call, :start]` - Dispatched\n      immediately before invoking the function. This event\n      is always emitted.\n\n      * Measurement: `%{system_time: system_time}`\n\n      * Metadata: `%{}`\n\n    * `[:my_app, :my_server, :call, :stop]` - Dispatched\n      immediately after successfully invoking the function.\n\n      * Measurement: `%{duration: native_time}`\n\n      * Metadata: `%{}`\n\n    * `[:my_app, :my_server, :call, :exception]` - Dispatched\n      immediately after invoking the function, in the event\n      the function throws or raises.\n\n      * Measurement: `%{duration: native_time}`\n\n      * Metadata: `%{kind: kind, reason: reason, stacktrace: stacktrace}`\n  \"\"\"\n  def call!, do: GenServer.call(__MODULE__, :called)\n\n  @impl true\n  def init(fun) when is_function(fun, 0), do: {:ok, fun}\n\n  @impl true\n  def handle_call(:called, _from, fun) do\n    # Wrap the function invocation in a \"span\"\n    result = telemetry_span(fun)\n\n    {:reply, result, fun}\n  end\n\n  # Emits telemetry events related to invoking the function\n  defp telemetry_span(fun) do\n    start_time = emit_start()\n\n    try do\n      fun.()\n    catch\n      kind, reason ->\n        stacktrace = System.stacktrace()\n        duration = System.monotonic_time() - start_time\n        emit_exception(duration, kind, reason, stacktrace)\n        :erlang.raise(kind, reason, stacktrace)\n    else\n      result ->\n        duration = System.monotonic_time() - start_time\n        emit_stop(duration)\n        result\n    end\n  end\n\n  defp emit_start do\n    start_time_mono = System.monotonic_time()\n\n    :telemetry.execute(\n      @prefix ++ [:start],\n      %{system_time: System.system_time()},\n      %{}\n    )\n\n    start_time_mono\n  end\n\n  defp emit_stop(duration) do\n    :telemetry.execute(\n      @prefix ++ [:stop],\n      %{duration: duration},\n      %{}\n    )\n  end\n\n  defp emit_exception(duration, kind, reason, stacktrace) do\n    :telemetry.execute(\n      @prefix ++ [:exception],\n      %{duration: duration},\n      %{\n        kind: kind,\n        reason: reason,\n        stacktrace: stacktrace\n      }\n    )\n  end\nend\n```\n\nand add it to your application's supervisor tree (usually in\n`lib/my_app/application.ex`), giving it a function to invoke\nwhen called:\n\n```elixir\n# lib/my_app/application.ex\nchildren = [\n  # Start a server that greets the world\n  {MyApp.MyServer, fn -> \"Hello, world!\" end},\n]\n```\n\nNow start an IEx session and call the server:\n\n```elixir\niex> MyApp.MyServer.call!\n```\n\nand you should see something like the following output:\n\n```text\n[Telemetry.Metrics.ConsoleReporter] Got new event!\nEvent name: my_app.my_server.call.stop\nAll measurements: %{duration: 4000}\nAll metadata: %{}\n\nMetric measurement: #Function<2.111777250/1 in Telemetry.Metrics.maybe_convert_measurement/2> (summary)\nWith value: 0.004 millisecond\nTag values: %{}\n\n\"Hello, world!\"\n```\n"
  },
  {
    "path": "guides/testing/testing.md",
    "content": "# Introduction to Testing\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\nTesting has become integral to the software development process, and the ability to easily write meaningful tests is an indispensable feature for any modern web framework. Phoenix takes this seriously, providing support files to make all the major components of the framework easy to test. It also generates test modules with real-world examples alongside any generated modules to help get us going.\n\nElixir ships with a built-in testing framework called [ExUnit](https://hexdocs.pm/ex_unit). ExUnit strives to be clear and explicit, keeping magic to a minimum. Phoenix uses ExUnit for all of its testing, and we will use it here as well.\n\n## Running tests\n\nWhen Phoenix generates a web application for us, it also includes tests. To run them, simply type `mix test`:\n\n```console\n$ mix test\n....\n\nFinished in 0.09 seconds\n5 tests, 0 failures\n\nRandomized with seed 652656\n```\n\nWe already have five tests!\n\nIn fact, we already have a directory structure completely set up for testing, including a test helper and support files.\n\n```console\ntest\n├── hello_web\n│   └── controllers\n│       ├── error_html_test.exs\n│       ├── error_json_test.exs\n│       └── page_controller_test.exs\n├── support\n│   ├── conn_case.ex\n│   └── data_case.ex\n└── test_helper.exs\n```\n\nThe test cases we get for free include those from `test/hello_web/controllers/`. They are testing our controllers and views. If you haven't read the guides for controllers and views, now is a good time.\n\n## Understanding test modules\n\nWe are going to use the next sections to get acquainted with Phoenix testing structure. We will start with the three test files generated by Phoenix.\n\nThe first test file we'll look at is `test/hello_web/controllers/page_controller_test.exs`.\n\n```elixir\ndefmodule HelloWeb.PageControllerTest do\n  use HelloWeb.ConnCase\n\n  test \"GET /\", %{conn: conn} do\n    conn = get(conn, ~p\"/\")\n    assert html_response(conn, 200) =~ \"Peace of mind from prototype to production\"\n  end\nend\n```\n\nThere are a couple of interesting things happening here.\n\nOur test files simply define modules. At the top of each module, you will find a line such as:\n\n```elixir\nuse HelloWeb.ConnCase\n```\n\nIf you were to write an Elixir library, outside of Phoenix, instead of `use HelloWeb.ConnCase` you would write `use ExUnit.Case`. However, Phoenix already ships with a bunch of functionality for testing controllers and `HelloWeb.ConnCase` builds on top of `ExUnit.Case` to bring these functionalities in. We will explore the `HelloWeb.ConnCase` module soon.\n\nThen we define each test using the `test/3` macro. The `test/3` macro receives three arguments: the test name, the testing context that we are pattern matching on, and the contents of the test. In this test, we access the root page of our application by a \"GET\" HTTP request on the path \"/\" with the `get/2` macro. Then we **assert** that the rendered page contains the string \"Peace of mind from prototype to production\".\n\nWhen writing tests in Elixir, we use assertions to check that something is true. In our case, `assert html_response(conn, 200) =~ \"Peace of mind from prototype to production\"` is doing a couple things:\n\n  * It asserts that `conn` has rendered a response\n  * It asserts that the response has the 200 status code (which means OK in HTTP parlance)\n  * It asserts that the type of the response is HTML\n  * It asserts that the result of `html_response(conn, 200)`, which is an HTML response, has the string \"Peace of mind from prototype to production\" in it\n\nHowever, from where does the `conn` we use on `get` and `html_response` come from? To answer this question, let's take a look at `HelloWeb.ConnCase`.\n\n## The ConnCase\n\nIf you open up `test/support/conn_case.ex`, you will find this (with comments removed):\n\n```elixir\ndefmodule HelloWeb.ConnCase do\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # The default endpoint for testing\n      @endpoint HelloWeb.Endpoint\n\n      use HelloWeb, :verified_routes\n\n      # Import conveniences for testing with connections\n      import Plug.Conn\n      import Phoenix.ConnTest\n      import HelloWeb.ConnCase\n    end\n  end\n  \n  setup tags do\n    Hello.DataCase.setup_sandbox(tags)\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end\nend\n```\n\nThere is a lot to unpack here.\n\nThe second line says this is a case template. This is a ExUnit feature that allows developers to replace the built-in `use ExUnit.Case` by their own case. This line is pretty much what allows us to write `use HelloWeb.ConnCase` at the top of our controller tests.\n\nNow that we have made this module a case template, we can define callbacks that are invoked on certain occasions. The `using` callback defines code to be injected on every module that calls `use HelloWeb.ConnCase`. In this case, it starts by setting the `@endpoint` module attribute with the name of our endpoint.\n\nNext, it wires up `:verified_routes` to allow us to use `~p` based paths in our test just like we do in the rest of our application to easily generate paths and URLs in our tests.\n\nFinally, we import [`Plug.Conn`](https://hexdocs.pm/plug/Plug.Conn.html), so all of the connection helpers available in controllers are also available in tests, and then imports [`Phoenix.ConnTest`](https://hexdocs.pm/phoenix/Phoenix.ConnTest.html). You can consult these modules to learn all functionality available.\n\nThen our case template defines a `setup` block. The `setup` block will be called before test. Most of the setup block is on setting up the SQL Sandbox, which we will talk about later. In the last line of the `setup` block, we will find this:\n\n```elixir\n{:ok, conn: Phoenix.ConnTest.build_conn()}\n```\n\nThe last line of `setup` can return test metadata that will be available in each test. The metadata we are passing forward here is a newly built `Plug.Conn`. In our test, we extract the connection out of this metadata at the very beginning of our test:\n\n```elixir\ntest \"GET /\", %{conn: conn} do\n```\n\nAnd that's where the connection comes from! At first, the testing structure does come with a bit of indirection, but this indirection pays off as our test suite grows, since it allows us to cut down the amount of boilerplate.\n\n## View tests\n\nThe other test files in our application are responsible for testing our views.\n\nThe error view test case, `test/hello_web/controllers/error_html_test.exs`, illustrates a few interesting things of its own.\n\n```elixir\ndefmodule HelloWeb.ErrorHTMLTest do\n  use HelloWeb.ConnCase, async: true\n\n  # Bring render_to_string/4 for testing custom views\n  import Phoenix.Template\n\n  test \"renders 404.html\" do\n    assert render_to_string(HelloWeb.ErrorHTML, \"404\", \"html\", []) == \"Not Found\"\n  end\n\n  test \"renders 500.html\" do\n    assert render_to_string(HelloWeb.ErrorHTML, \"500\", \"html\", []) == \"Internal Server Error\"\n  end\nend\n```\n\n`HelloWeb.ErrorHTMLTest` sets `async: true` which means that this test case will be run in parallel with other test cases. While individual tests within the case still run serially, this can greatly increase overall test speeds.\n\nIt also imports `Phoenix.Template` in order to use the `render_to_string/4` function. With that, all the assertions can be simple string equality tests.\n\n## Running tests per directory/file\n\nNow that we have an idea what our tests are doing, let's look at different ways to run them.\n\nAs we saw near the beginning of this guide, we can run our entire suite of tests with `mix test`.\n\n```console\n$ mix test\n....\n\nFinished in 0.2 seconds\n5 tests, 0 failures\n\nRandomized with seed 540755\n```\n\nIf we would like to run all the tests in a given directory, `test/hello_web/controllers` for instance, we can pass the path to that directory to `mix test`.\n\n```console\n$ mix test test/hello_web/controllers/\n.\n\nFinished in 0.2 seconds\n5 tests, 0 failures\n\nRandomized with seed 652376\n```\n\nIn order to run all the tests in a specific file, we can pass the path to that file into `mix test`.\n\n```console\n$ mix test test/hello_web/controllers/error_html_test.exs\n...\n\nFinished in 0.2 seconds\n2 tests, 0 failures\n\nRandomized with seed 220535\n```\n\nAnd we can run a single test in a file by appending a colon and a line number to the filename.\n\nLet's say we only wanted to run the test for the way `HelloWeb.ErrorHTML` renders `500.html`. The test begins on line 11 of the file, so this is how we would do it.\n\n```console\n$ mix test test/hello_web/controllers/error_html_test.exs:11\nIncluding tags: [line: \"11\"]\nExcluding tags: [:test]\n\n.\n\nFinished in 0.1 seconds\n2 tests, 0 failures, 1 excluded\n\nRandomized with seed 288117\n```\n\nWe chose to run this specifying the first line of the test, but actually, any line of that test will do. These line numbers would all work - `:11`, `:12`, or `:13`.\n\n## Running tests using tags\n\nExUnit allows us to tag our tests individually or for the whole module. We can then choose to run only the tests with a specific tag, or we can exclude tests with that tag and run everything else.\n\nLet's experiment with how this works.\n\nFirst, we'll add a `@moduletag` to `test/hello_web/controllers/error_html_test.exs`.\n\n```elixir\ndefmodule HelloWeb.ErrorHTMLTest do\n  use HelloWeb.ConnCase, async: true\n\n  @moduletag :error_view_case\n  ...\nend\n```\n\nIf we use only an atom for our module tag, ExUnit assumes that it has a value of `true`. We could also specify a different value if we wanted.\n\n```elixir\ndefmodule HelloWeb.ErrorHTMLTest do\n  use HelloWeb.ConnCase, async: true\n\n  @moduletag error_view_case: \"some_interesting_value\"\n  ...\nend\n```\n\nFor now, let's leave it as a simple atom `@moduletag :error_view_case`.\n\nWe can run only the tests from the error view case by passing `--only error_view_case` into `mix test`.\n\n```console\n$ mix test --only error_view_case\nIncluding tags: [:error_view_case]\nExcluding tags: [:test]\n\n...\n\nFinished in 0.1 seconds\n5 tests, 0 failures, 3 excluded\n\nRandomized with seed 125659\n```\n\n> Note: ExUnit tells us exactly which tags it is including and excluding for each test run. If we look back to the previous section on running tests, we'll see that line numbers specified for individual tests are actually treated as tags.\n\n```console\n$ mix test test/hello_web/controllers/error_html_test.exs:11\nIncluding tags: [line: \"11\"]\nExcluding tags: [:test]\n\n.\n\nFinished in 0.2 seconds\n2 tests, 0 failures, 1 excluded\n\nRandomized with seed 364723\n```\n\nSpecifying a value of `true` for `error_view_case` yields the same results.\n\n```console\n$ mix test --only error_view_case:true\nIncluding tags: [error_view_case: \"true\"]\nExcluding tags: [:test]\n\n...\n\nFinished in 0.1 seconds\n5 tests, 0 failures, 3 excluded\n\nRandomized with seed 833356\n```\n\nSpecifying `false` as the value for `error_view_case`, however, will not run any tests because no tags in our system match `error_view_case: false`.\n\n```console\n$ mix test --only error_view_case:false\nIncluding tags: [error_view_case: \"false\"]\nExcluding tags: [:test]\n\n\n\nFinished in 0.1 seconds\n5 tests, 0 failures, 5 excluded\n\nRandomized with seed 622422\nThe --only option was given to \"mix test\" but no test executed\n```\n\nWe can use the `--exclude` flag in a similar way. This will run all of the tests except those in the error view case.\n\n```console\n$ mix test --exclude error_view_case\nExcluding tags: [:error_view_case]\n\n.\n\nFinished in 0.2 seconds\n5 tests, 0 failures, 2 excluded\n\nRandomized with seed 682868\n```\n\nSpecifying values for a tag works the same way for `--exclude` as it does for `--only`.\n\nWe can tag individual tests as well as full test cases. Let's tag a few tests in the error view case to see how this works.\n\n```elixir\ndefmodule HelloWeb.ErrorHTMLTest do\n  use HelloWeb.ConnCase, async: true\n\n  @moduletag :error_view_case\n\n  # Bring render/4 and render_to_string/4 for testing custom views\n  import Phoenix.Template\n\n  @tag individual_test: \"yup\"\n  test \"renders 404.html\" do\n    assert render_to_string(HelloWeb.ErrorView, \"404\", \"html\", []) ==\n           \"Not Found\"\n  end\n\n  @tag individual_test: \"nope\"\n  test \"renders 500.html\" do\n    assert render_to_string(HelloWeb.ErrorView, \"500\", \"html\", []) ==\n           \"Internal Server Error\"\n  end\nend\n```\n\nIf we would like to run only tests tagged as `individual_test`, regardless of their value, this will work.\n\n```console\n$ mix test --only individual_test\nIncluding tags: [:individual_test]\nExcluding tags: [:test]\n\n..\n\nFinished in 0.1 seconds\n5 tests, 0 failures, 3 excluded\n\nRandomized with seed 813729\n```\n\nWe can also specify a value and run only tests with that.\n\n```console\n$ mix test --only individual_test:yup\nIncluding tags: [individual_test: \"yup\"]\nExcluding tags: [:test]\n\n.\n\nFinished in 0.1 seconds\n5 tests, 0 failures, 4 excluded\n\nRandomized with seed 770938\n```\n\nSimilarly, we can run all tests except for those tagged with a given value.\n\n```console\n$ mix test --exclude individual_test:nope\nExcluding tags: [individual_test: \"nope\"]\n\n...\n\nFinished in 0.2 seconds\n5 tests, 0 failures, 1 excluded\n\nRandomized with seed 539324\n```\n\nWe can be more specific and exclude all the tests from the error view case except the one tagged with `individual_test` that has the value \"yup\".\n\n```console\n$ mix test --exclude error_view_case --include individual_test:yup\nIncluding tags: [individual_test: \"yup\"]\nExcluding tags: [:error_view_case]\n\n..\n\nFinished in 0.2 seconds\n5 tests, 0 failures, 1 excluded\n\nRandomized with seed 61472\n```\n\nFinally, we can configure ExUnit to exclude tags by default. The default ExUnit configuration is done in the `test/test_helper.exs` file:\n\n```elixir\nExUnit.start(exclude: [error_view_case: true])\n\nEcto.Adapters.SQL.Sandbox.mode(Hello.Repo, :manual)\n```\n\nNow when we run `mix test`, it only runs the specs from our `page_controller_test.exs` and `error_json_test.exs`.\n\n```console\n$ mix test\nExcluding tags: [error_view_case: true]\n\n.\n\nFinished in 0.2 seconds\n5 tests, 0 failures, 2 excluded\n\nRandomized with seed 186055\n```\n\nWe can override this behavior with the `--include` flag, telling `mix test` to include tests tagged with `error_view_case`.\n\n```console\n$ mix test --include error_view_case\nIncluding tags: [:error_view_case]\nExcluding tags: [error_view_case: true]\n\n....\n\nFinished in 0.2 seconds\n5 tests, 0 failures\n\nRandomized with seed 748424\n```\n\nThis technique can be very useful to control very long running tests, which you may only want to run in CI or in specific scenarios.\n\n## Randomization\n\nRunning tests in random order is a good way to ensure that our tests are truly isolated. If we notice that we get sporadic failures for a given test, it may be because a previous test changes the state of the system in ways that aren't cleaned up afterward, thereby affecting the tests which follow. Those failures might only present themselves if the tests are run in a specific order.\n\nExUnit will randomize the order tests run in by default, using an integer to seed the randomization. If we notice that a specific random seed triggers our intermittent failure, we can re-run the tests with that same seed to reliably recreate that test sequence in order to help us figure out what the problem is.\n\n```console\n$ mix test --seed 401472\n....\n\nFinished in 0.2 seconds\n5 tests, 0 failures\n\nRandomized with seed 401472\n```\n\n## Concurrency and partitioning\n\nAs we have seen, ExUnit allows developers to run tests concurrently. This allows developers to use all of the power in their machine to run their test suites as fast as possible. Couple this with Phoenix performance, most test suites compile and run in a fraction of the time compared to other frameworks.\n\nWhile developers usually have powerful machines available to them during development, this may not always be the case in your Continuous Integration servers. For this reason, ExUnit also supports out of the box test partitioning in test environments. If you open up your `config/test.exs`, you will find the database name set to:\n\n```elixir\ndatabase: \"hello_test#{System.get_env(\"MIX_TEST_PARTITION\")}\",\n```\n\nBy default, the `MIX_TEST_PARTITION` environment variable has no value, and therefore it has no effect. But in your CI server, you can, for example, split your test suite across machines by using four distinct commands:\n\n```console\n$ MIX_TEST_PARTITION=1 mix test --partitions 4\n$ MIX_TEST_PARTITION=2 mix test --partitions 4\n$ MIX_TEST_PARTITION=3 mix test --partitions 4\n$ MIX_TEST_PARTITION=4 mix test --partitions 4\n```\n\nThat's all you need to do and ExUnit and Phoenix will take care of all rest, including setting up the database for each distinct partition with a distinct name.\n\n## Going further\n\nWhile ExUnit is a simple test framework, it provides a really flexible and robust test runner through the `mix test` command. We recommend you to run `mix help test` or [read the docs online](https://hexdocs.pm/mix/Mix.Tasks.Test.html)\n\nWe've seen what Phoenix gives us with a newly generated app. Furthermore, whenever you generate a new resource, Phoenix will generate all appropriate tests for that resource too. For example, you can create a complete scaffold with schema, context, controllers, and views by running the following command at the root of your application:\n\n```console\n$ mix phx.gen.html Blog Post posts title body:text\n* creating lib/hello_web/controllers/post_controller.ex\n* creating lib/hello_web/controllers/post_html/edit.html.heex\n* creating lib/hello_web/controllers/post_html/index.html.heex\n* creating lib/hello_web/controllers/post_html/new.html.heex\n* creating lib/hello_web/controllers/post_html/show.html.heex\n* creating lib/hello_web/controllers/post_html/post_form.html.heex\n* creating lib/hello_web/controllers/post_html.ex\n* creating test/hello_web/controllers/post_controller_test.exs\n* creating lib/hello/blog/post.ex\n* creating priv/repo/migrations/20211001233016_create_posts.exs\n* creating lib/hello/blog.ex\n* injecting lib/hello/blog.ex\n* creating test/hello/blog_test.exs\n* injecting test/hello/blog_test.exs\n* creating test/support/fixtures/blog_fixtures.ex\n* injecting test/support/fixtures/blog_fixtures.ex\n\nAdd the resource to your browser scope in lib/demo_web/router.ex:\n\n    resources \"/posts\", PostController\n\n\nRemember to update your repository by running migrations:\n\n    $ mix ecto.migrate\n\n```\n\nNow let's follow the directions and add the new resources route to our `lib/hello_web/router.ex` file and run the migrations.\n\nWhen we run `mix test` again, we see that we now have twenty-one tests!\n\n```console\n$ mix test\n................\n\nFinished in 0.1 seconds\n21 tests, 0 failures\n\nRandomized with seed 537537\n```\n\nAt this point, we are at a great place to transition to the rest of the testing guides, in which we'll examine these tests in much more detail, and add some of our own.\n"
  },
  {
    "path": "guides/testing/testing_channels.md",
    "content": "# Testing Channels\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).\n\n> **Requirement**: This guide expects that you have gone through the [Channels guide](channels.html).\n\nIn the Channels guide, we saw that a \"Channel\" is a layered system with different components. Given this, there would be cases when writing unit tests for our Channel functions may not be enough. We may want to verify that its different moving parts are working together as we expect. This integration testing would assure us that we correctly defined our channel route, the channel module, and its callbacks; and that the lower-level layers such as the PubSub and Transport are configured correctly and are working as intended.\n\n## Generating channels\n\nAs we progress through this guide, it would help to have a concrete example we could work off of. Phoenix comes with a Mix task for generating a basic channel and tests. These generated files serve as a good reference for writing channels and their corresponding tests. Let's go ahead and generate our Channel:\n\n```console\n$ mix phx.gen.channel Room\n* creating lib/hello_web/channels/room_channel.ex\n* creating test/hello_web/channels/room_channel_test.exs\n* creating test/support/channel_case.ex\n\nThe default socket handler - HelloWeb.UserSocket - was not found.\n\nDo you want to create it? [Yn]  \n* creating lib/hello_web/channels/user_socket.ex\n* creating assets/js/user_socket.js\n\nAdd the socket handler to your `lib/hello_web/endpoint.ex`, for example:\n\n    socket \"/socket\", HelloWeb.UserSocket,\n      websocket: true,\n      longpoll: false\n\nFor the front-end integration, you need to import the `user_socket.js`\nin your `assets/js/app.js` file:\n\n    import \"./user_socket.js\"\n```\n\nThis creates a channel, its test and instructs us to add a channel route in `lib/hello_web/channels/user_socket.ex`. It is important to add the channel route or our channel won't function at all!\n\n## The ChannelCase\n\nOpen up `test/hello_web/channels/room_channel_test.exs` and you will find this:\n\n```elixir\ndefmodule HelloWeb.RoomChannelTest do\n  use HelloWeb.ChannelCase\n```\n\nSimilar to `ConnCase` and `DataCase`, we now have a `ChannelCase`. All three of them have been generated for us when we started our Phoenix application. Let's take a look at it. Open up `test/support/channel_case.ex`:\n\n```elixir\ndefmodule HelloWeb.ChannelCase do\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with channels\n      import Phoenix.ChannelTest\n      import HelloWeb.ChannelCase\n\n      # The default endpoint for testing\n      @endpoint HelloWeb.Endpoint\n    end\n  end\n\n  setup _tags do\n    Hello.DataCase.setup_sandbox(tags)\n    :ok\n  end\nend\n```\n\nIt is very straight-forward. It sets up a case template that imports all of `Phoenix.ChannelTest` on use. In the `setup` block, it starts the SQL Sandbox, which we discussed in the [Testing contexts guide](testing_contexts.html).\n\n## Subscribe and joining\n\nNow that we know that Phoenix provides with a custom Test Case just for channels and what it\nprovides, we can move on to understanding the rest of `test/hello_web/channels/room_channel_test.exs`.\n\nFirst off, is the setup block:\n\n```elixir\nsetup do\n  {:ok, _, socket} =\n    HelloWeb.UserSocket\n    |> socket(\"user_id\", %{some: :assign})\n    |> subscribe_and_join(HelloWeb.RoomChannel, \"room:lobby\")\n\n  %{socket: socket}\nend\n```\n\nThe `setup` block sets up a `Phoenix.Socket` based on the `UserSocket` module, which you can find at `lib/hello_web/channels/user_socket.ex`. Then it says we want to subscribe and join the `RoomChannel`, accessible as `\"room:lobby\"` in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can reuse it on every test.\n\nIn a nutshell, `subscribe_and_join/3` emulates the client joining a channel and subscribes the test process to the given topic. This is a necessary step since clients need to join a channel before they can send and receive events on that channel.\n\n## Testing a synchronous reply\n\nThe first test block in our generated channel test looks like:\n\n```elixir\ntest \"ping replies with status ok\", %{socket: socket} do\n  ref = push(socket, \"ping\", %{\"hello\" => \"there\"})\n  assert_reply ref, :ok, %{\"hello\" => \"there\"}\nend\n```\n\nThis tests the following code in our `HelloWeb.RoomChannel`:\n\n```elixir\n# Channels can be used in a request/response fashion\n# by sending replies to requests from the client\ndef handle_in(\"ping\", payload, socket) do\n  {:reply, {:ok, payload}, socket}\nend\n```\n\nAs is stated in the comment above, we see that a `reply` is synchronous since it mimics the request/response pattern we are familiar with in HTTP. This synchronous reply is best used when we only want to send an event back to the client when we are done processing the message on the server. For example, when we save something to the database and then send a message to the client only once that's done.\n\nIn the `test \"ping replies with status ok\", %{socket: socket} do` line, we see that we have the map `%{socket: socket}`. This gives us access to the `socket` in the setup block.\n\nWe emulate the client pushing a message to the channel with `push/3`. In the line `ref = push(socket, \"ping\", %{\"hello\" => \"there\"})`, we push the event `\"ping\"` with the payload `%{\"hello\" => \"there\"}` to the channel. This triggers the `handle_in/3` callback we have for the `\"ping\"` event in our channel. Note that we store the `ref` since we need that on the next line for asserting the reply. With `assert_reply ref, :ok, %{\"hello\" => \"there\"}`, we assert that the server sends a synchronous reply `:ok, %{\"hello\" => \"there\"}`. This is how we check that the `handle_in/3` callback for the `\"ping\"` was triggered.\n\n### Testing a Broadcast\n\nIt is common to receive messages from the client and broadcast to everyone subscribed to a current topic. This common pattern is simple to express in Phoenix and is one of the generated `handle_in/3` callbacks in our `HelloWeb.RoomChannel`.\n\n```elixir\ndef handle_in(\"shout\", payload, socket) do\n  broadcast(socket, \"shout\", payload)\n  {:noreply, socket}\nend\n```\n\nIts corresponding test looks like:\n\n```elixir\ntest \"shout broadcasts to room:lobby\", %{socket: socket} do\n  push(socket, \"shout\", %{\"hello\" => \"all\"})\n  assert_broadcast \"shout\", %{\"hello\" => \"all\"}\nend\n```\n\nWe notice that we access the same `socket` that is from the setup block. How handy! We also do the same `push/3` as we did in the synchronous reply test. So we `push` the `\"shout\"` event with the payload `%{\"hello\" => \"all\"}`.\n\nSince the `handle_in/3` callback for the `\"shout\"` event just broadcasts the same event and payload, all subscribers in the `\"room:lobby\"` should receive the message. To check that, we do `assert_broadcast \"shout\", %{\"hello\" => \"all\"}`.\n\n**NOTE:** `assert_broadcast/3` tests that the message was broadcast in the PubSub system. For testing if a client receives a message, use `assert_push/3`.\n\n### Testing an asynchronous push from the server\n\nThe last test in our `HelloWeb.RoomChannelTest` verifies that broadcasts from the server are pushed to the client. Unlike the previous tests discussed, we are indirectly testing that the channel's `handle_out/3` callback is triggered. By default, `handle_out/3` is implemented for us and simply pushes the message on to the client.\n\nSince the `handle_out/3` event is only triggered when we call `broadcast/3` from our channel, we will need to emulate that in our test. We do that by calling `broadcast_from` or `broadcast_from!`. Both serve the same purpose with the only difference of `broadcast_from!` raising an error when broadcast fails.\n\nThe line `broadcast_from!(socket, \"broadcast\", %{\"some\" => \"data\"})` will trigger the `handle_out/3` callback which pushes the same event and payload back to the client. To test this, we do `assert_push \"broadcast\", %{\"some\" => \"data\"}`.\n\nThat's it. Now you are ready to develop and fully test real-time applications. To learn more about other functionality provided when testing channels, check out the documentation for [`Phoenix.ChannelTest`](https://hexdocs.pm/phoenix/Phoenix.ChannelTest.html).\n"
  },
  {
    "path": "guides/testing/testing_contexts.md",
    "content": "# Testing Contexts\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).\n\n> **Requirement**: This guide expects that you have gone through the [Contexts guides](contexts.html).\n\nAt the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command:\n\n```console\n$ mix phx.gen.html Blog Post posts title body:text\n```\n\nThis gave us a number of modules for free, including a Blog context and a Post schema, alongside their respective test files. As we have learned in the Context guide, the Blog context is simply a module with functions to a particular area of our business domain, while Post schema maps to a particular table in our database.\n\nIn this guide, we are going to explore the tests generated for our contexts and schemas. Before we do anything else, let's run `mix test` to make sure our test suite runs cleanly.\n\n```console\n$ mix test\n................\n\nFinished in 0.6 seconds\n21 tests, 0 failures\n\nRandomized with seed 638414\n```\n\nGreat. We've got twenty-one tests and they are all passing!\n\n## Testing posts\n\nIf you open up `test/hello/blog_test.exs`, you will see a file with the following:\n\n```elixir\ndefmodule Hello.BlogTest do\n  use Hello.DataCase\n\n  alias Hello.Blog\n\n  describe \"posts\" do\n    alias Hello.Blog.Post\n\n    import Hello.BlogFixtures\n\n    @invalid_attrs %{body: nil, title: nil}\n\n    test \"list_posts/0 returns all posts\" do\n      post = post_fixture()\n      assert Blog.list_posts() == [post]\n    end\n\n    ...\n```\n\nAs the top of the file we import `Hello.DataCase`, which as we will see soon, it is similar to `HelloWeb.ConnCase`. While `HelloWeb.ConnCase` sets up helpers for working with connections, which is useful when testing controllers and views, `Hello.DataCase` provides functionality for working with contexts and schemas.\n\nNext, we define an alias, so we can refer to `Hello.Blog` simply as `Blog`.\n\nThen we start a `describe \"posts\"` block. A `describe` block is a feature in ExUnit that allows us to group similar tests. The reason why we have grouped all post related tests together is because contexts in Phoenix are capable of grouping multiple schemas together. For example, if we ran this command:\n\n```console\n$ mix phx.gen.html Blog Comment comments post_id:references:posts body:text\n```\n\nWe will get a bunch of new functions in the `Hello.Blog` context, plus a whole new `describe \"comments\"` block in our test file.\n\nThe tests defined for our context are very straight-forward. They call the functions in our context and assert on their results. As you can see, some of those tests even create entries in the database:\n\n```elixir\ntest \"create_post/1 with valid data creates a post\" do\n  valid_attrs = %{body: \"some body\", title: \"some title\"}\n\n  assert {:ok, %Post{} = post} = Blog.create_post(valid_attrs)\n  assert post.body == \"some body\"\n  assert post.title == \"some title\"\nend\n```\n\nAt this point, you may wonder: how can Phoenix make sure the data created in one of the tests do not affect other tests? We are glad you asked. To answer this question, let's talk about the `DataCase`.\n\n## The DataCase\n\nIf you open up `test/support/data_case.ex`, you will find the following:\n\n```elixir\ndefmodule Hello.DataCase do\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      alias Hello.Repo\n\n      import Ecto\n      import Ecto.Changeset\n      import Ecto.Query\n      import Hello.DataCase\n    end\n  end\n\n  setup tags do\n    Hello.DataCase.setup_sandbox(tags)\n    :ok\n  end\n\n  def setup_sandbox(tags) do\n    pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Hello.Repo, shared: not tags[:async])\n    on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)\n  end\n\n  def errors_on(changeset) do\n    ...\n  end\nend\n```\n\n`Hello.DataCase` is another `ExUnit.CaseTemplate`. In the `using` block, we can see all of the aliases and imports `DataCase` brings into our tests. The `setup` chunk for `DataCase` is very similar to the one from `ConnCase`. As we can see, most of the `setup` block revolves around setting up a SQL Sandbox.\n\nThe SQL Sandbox is precisely what allows our tests to write to the database without affecting any of the other tests. In a nutshell, at the beginning of every test, we start a transaction in the database. When the test is over, we automatically rollback the transaction, effectively erasing all of the data created in the test.\n\nFurthermore, the SQL Sandbox allows multiple tests to run concurrently, even if they talk to the database. This feature is provided for PostgreSQL databases and it can be used to further speed up your contexts and controllers tests by adding a `async: true` flag when using them:\n\n```elixir\nuse Hello.DataCase, async: true\n```\n\nThere are some considerations you need to have in mind when running asynchronous tests with the sandbox, so please refer to the [`Ecto.Adapters.SQL.Sandbox`](https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html) for more information.\n\nFinally, at the end of the `DataCase` module we can find a function named `errors_on` with some examples of how to use it. This function is used for testing any validation we may want to add to our schemas. Let's give it a try by adding our own validations and then testing them.\n\n## Testing schemas\n\nWhen we generate our HTML Post resource, Phoenix generated a Blog context and a Post schema. It generated a test file for the context, but no test file for the schema. However, this doesn't mean we don't need to test the schema, it just means we did not have to test the schema so far.\n\nYou may be wondering then: when do we test the context directly and when do we test the schema directly? The answer to this question is the same answer to the question of when do we add code to a context and when do we add it to the schema?\n\nThe general guideline is to keep all side-effect free code in the schema. In other words, if you are simply working with data structures, schemas and changesets, put it in the schema. The context will typically have the code that creates and updates schemas and then write them to a database or an API.\n\nWe'll be adding additional validations to the schema module, so that's a great opportunity to write some schema specific tests. Open up `lib/hello/blog/post.ex` and add the following validation to `def changeset`:\n\n```elixir\ndef changeset(post, attrs) do\n  post\n  |> cast(attrs, [:title, :body])\n  |> validate_required([:title, :body])\n  |> validate_length(:title, min: 2)\nend\n```\n\nThe new validation says the title needs to have at least 2 characters. Let's write a test for this. Create a new file at `test/hello/blog/post_test.exs` with this:\n\n```elixir\ndefmodule Hello.Blog.PostTest do\n  use Hello.DataCase, async: true\n  alias Hello.Blog.Post\n\n  test \"title must be at least two characters long\" do\n    changeset = Post.changeset(%Post{}, %{title: \"I\"})\n    assert %{title: [\"should be at least 2 character(s)\"]} = errors_on(changeset)\n  end\nend\n```\n\nAnd that's it. As our business domain grows, we have well-defined places to test our contexts and schemas.\n"
  },
  {
    "path": "guides/testing/testing_controllers.md",
    "content": "# Testing Controllers\n\n> **Requirement**: This guide expects that you have gone through the [introductory guides](installation.html) and got a Phoenix application [up and running](up_and_running.html).\n\n> **Requirement**: This guide expects that you have gone through the [Introduction to Testing guide](testing.html).\n\nAt the end of the Introduction to Testing guide, we generated an HTML resource for posts using the following command:\n\n```console\n$ mix phx.gen.html Blog Post posts title body:text\n```\n\nThis gave us a number of modules for free, including a PostController and the associated tests. We are going to explore those tests to learn more about testing controllers in general. At the end of the guide, we will generate a JSON resource, and explore how our API tests look like.\n\n## HTML controller tests\n\nIf you open up `test/hello_web/controllers/post_controller_test.exs`, you will find the following:\n\n```elixir\ndefmodule HelloWeb.PostControllerTest do\n  use HelloWeb.ConnCase\n\n  import Hello.BlogFixtures\n\n  @create_attrs %{body: \"some body\", title: \"some title\"}\n  @update_attrs %{body: \"some updated body\", title: \"some updated title\"}\n  @invalid_attrs %{body: nil, title: nil}\n  \n  describe \"index\" do\n    test \"lists all posts\", %{conn: conn} do\n      conn = get(conn, ~p\"/posts\")\n      assert html_response(conn, 200) =~ \"Listing Posts\"\n    end\n  end\n\n  ...\n```\n\nSimilar to the `PageControllerTest` that ships with our application, this controller tests uses `use HelloWeb.ConnCase` to setup the testing structure. Then, as usual, it defines some aliases, some module attributes to use throughout testing, and then it starts a series of `describe` blocks, each of them to test a different controller action.\n\n### The index action\n\nThe first describe block is for the `index` action. The action itself is implemented like this in `lib/hello_web/controllers/post_controller.ex`:\n\n```elixir\ndef index(conn, _params) do\n  posts = Blog.list_posts()\n  render(conn, :index, posts: posts)\nend\n```\n\nIt gets all posts and renders the \"index.html\" template. The template can be found in `lib/hello_web/controllers/post_html/index.html.heex`.\n\nThe test looks like this:\n\n```elixir\ndescribe \"index\" do\n  test \"lists all posts\", %{conn: conn} do\n    conn = get(conn, ~p\"/posts\")\n    assert html_response(conn, 200) =~ \"Listing Posts\"\n  end\nend\n```\n\nThe test for the `index` page is quite straight-forward. It uses the `get/2` helper to make a request to the `\"/posts\"` page, which is verified against our router in the test thanks to `~p`, then we assert we got a successful HTML response and match on its contents.\n\n### The create action\n\nThe next test we will look at is the one for the `create` action. The `create` action implementation is this:\n\n```elixir\ndef create(conn, %{\"post\" => post_params}) do\n  case Blog.create_post(post_params) do\n    {:ok, post} ->\n      conn\n      |> put_flash(:info, \"Post created successfully.\")\n      |> redirect(to: ~p\"/posts/#{post}\")\n\n    {:error, %Ecto.Changeset{} = changeset} ->\n      render(conn, :new, changeset: changeset)\n  end\nend\n```\n\nSince there are two possible outcomes for the `create`, we will have at least two tests:\n\n```elixir\ndescribe \"create post\" do\n  test \"redirects to show when data is valid\", %{conn: conn} do\n    conn = post(conn, ~p\"/posts\", post: @create_attrs)\n\n    assert %{id: id} = redirected_params(conn)\n    assert redirected_to(conn) == ~p\"/posts/#{id}\"\n\n    conn = get(conn, ~p\"/posts/#{id}\")\n    assert html_response(conn, 200) =~ \"Post #{id}\"\n  end\n\n  test \"renders errors when data is invalid\", %{conn: conn} do\n    conn = post(conn, ~p\"/posts\", post: @invalid_attrs)\n    assert html_response(conn, 200) =~ \"New Post\"\n  end\nend\n```\n\nThe first test starts with a `post/2` request. That's because once the form in the `/posts/new` page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like `/posts/ID`, where ID is the identifier of the post in the database.\n\nWe then use `redirected_params(conn)` to get the ID of the post and then match that we indeed redirected to the show action. Finally, we do a `get` request to the page we redirected to, allowing us to verify that the post was indeed created.\n\nFor the second test, we simply test the failure scenario. If any invalid attribute is given, it should re-render the \"New Post\" page.\n\nOne common question is: how many failure scenarios do you test at the controller level? For example, in the [Testing Contexts](testing_contexts.html) guide, we introduced a validation to the `title` field of the post:\n\n```elixir\ndef changeset(post, attrs) do\n  post\n  |> cast(attrs, [:title, :body])\n  |> validate_required([:title, :body])\n  |> validate_length(:title, min: 2)\nend\n```\n\nIn other words, creating a post can fail for the following reasons:\n\n  * the title is missing\n  * the body is missing\n  * the title is present but is less than 2 characters\n\nShould we test all of these possible outcomes in our controller tests?\n\nThe answer is no. All of the different rules and outcomes should be verified in your context and schema tests. The controller works as the integration layer. In the controller tests we simply want to verify, in broad strokes, that we handle both success and failure scenarios.\n\nThe test for `update` follows a similar structure as `create`, so let's skip to the `delete` test.\n\n### The delete action\n\nThe `delete` action looks like this:\n\n```elixir\ndef delete(conn, %{\"id\" => id}) do\n  post = Blog.get_post!(id)\n  {:ok, _post} = Blog.delete_post(post)\n\n  conn\n  |> put_flash(:info, \"Post deleted successfully.\")\n  |> redirect(to: ~p\"/posts\")\nend\n```\n\nThe test is written like this:\n\n```elixir\n  describe \"delete post\" do\n    setup [:create_post]\n\n    test \"deletes chosen post\", %{conn: conn, post: post} do\n      conn = delete(conn, ~p\"/posts/#{post}\")\n      assert redirected_to(conn) == ~p\"/posts\"\n\n      assert_error_sent 404, fn ->\n        get(conn, ~p\"/posts/#{post}\")\n      end\n    end\n  end\n\n  defp create_post(_) do\n    post = post_fixture()\n    %{post: post}\n  end\n```\n\nFirst of all, `setup` is used to declare that the `create_post` function should run before every test in this `describe` block. The `create_post` function simply creates a post and stores it in the test metadata. This allows us to, in the first line of the test, match on both the post and the connection:\n\n```elixir\ntest \"deletes chosen post\", %{conn: conn, post: post} do\n```\n\nThe test uses `delete/2` to delete the post and then asserts that we redirected to the index page. Finally, we check that it is no longer possible to access the show page of the deleted post:\n\n```elixir\nassert_error_sent 404, fn ->\n  get(conn, ~p\"/posts/#{post}\")\nend\n```\n\n`assert_error_sent` is a testing helper provided by `Phoenix.ConnTest`. In this case, it verifies that:\n\n  1. An exception was raised\n  2. The exception has a status code equivalent to 404 (which stands for Not Found)\n\nThis pretty much mimics how Phoenix handles exceptions. For example, when we access `/posts/12345` where `12345` is an ID that does not exist, we will invoke our `show` action:\n\n```elixir\ndef show(conn, %{\"id\" => id}) do\n  post = Blog.get_post!(id)\n  render(conn, :show, post: post)\nend\n```\n\nWhen an unknown post ID is given to `Blog.get_post!/1`, it raises an `Ecto.NotFoundError`. If your application raises any exception during a web request, Phoenix translates those requests into proper HTTP response codes. In this case, 404.\n\nWe could, for example, have written this test as:\n\n```elixir\nassert_raise Ecto.NotFoundError, fn ->\n  get(conn, ~p\"/posts/#{post}\")\nend\n```\n\nHowever, you may prefer the implementation Phoenix generates by default as it ignores the specific details of the failure, and instead verifies what the browser would actually receive.\n\nThe tests for `new`, `edit`, and `show` actions are simpler variations of the tests we have seen so far. You can check the action implementation and their respective tests yourself. Now we are ready to move to JSON controller tests.\n\n## JSON controller tests\n\nSo far we have been working with a generated HTML resource. However, let's take a look at how our tests look like when we generate a JSON resource.\n\nFirst of all, run this command:\n\n```console\n$ mix phx.gen.json News Article articles title body\n```\n\nWe chose a very similar concept to the Blog context <-> Post schema, except we are using a different name, so we can study these concepts in isolation.\n\nAfter you run the command above, do not forget to follow the final steps output by the generator. Once all is done, we should run `mix test` and now have 35 passing tests:\n\n```console\n$ mix test\n................\n\nFinished in 0.6 seconds\n35 tests, 0 failures\n\nRandomized with seed 618478\n```\n\nYou may have noticed that this time the scaffold controller has generated fewer tests. Previously it generated 16 (we went from 5 to 21) and now it generated 14 (we went from 21 to 35). That's because JSON APIs do not need to expose the `new` and `edit` actions. We can see this is the case in the resource we have added to the router at the end of the `mix phx.gen.json` command:\n\n```elixir\nresources \"/articles\", ArticleController, except: [:new, :edit]\n```\n\n`new` and `edit` are only necessary for HTML because they basically exist to assist users in creating and updating resources. Besides having less actions, we will notice the controller and view tests and implementations for JSON are drastically different from the HTML ones.\n\nThe only thing that is pretty much the same between HTML and JSON is the contexts and the schema, which, once you think about it, it makes total sense. After all, your business logic should remain the same, regardless if you are exposing it as HTML or JSON.\n\nWith the differences in hand, let's take a look at the controller tests.\n\n### The index action\n\nOpen up `test/hello_web/controllers/article_controller_test.exs`. The initial structure is quite similar to `post_controller_test.exs`. So let's take a look at the tests for the `index` action. The `index` action itself is implemented in `lib/hello_web/controllers/article_controller.ex` like this:\n\n```elixir\ndef index(conn, _params) do\n  articles = News.list_articles()\n  render(conn, :index, articles: articles)\nend\n```\n\nThe action gets all articles and renders the index template. Since we are talking about JSON, we don't have a `index.json.heex` template. Instead, the code that converts `articles` into JSON can be found directly in the ArticleJSON module, defined at `lib/hello_web/controllers/article_json.ex` like this:\n\n```elixir\ndefmodule HelloWeb.ArticleJSON do\n  alias Hello.News.Article\n\n  def index(%{articles: articles}) do\n    %{data: for(article <- articles, do: data(article))}\n  end\n\n  def show(%{article: article}) do\n    %{data: data(article)}\n  end\n\n  defp data(%Article{} = article) do\n    %{\n      id: article.id,\n      title: article.title,\n      body: article.body\n    }\n  end\nend\n```\n\nSince a controller render is a regular function call, we don't need any extra features to render JSON. We simply define functions for our `index` and `show` actions that return the map of JSON for articles.\n\nLet's take a look at the test for the `index` action then:\n\n```elixir\ndescribe \"index\" do\n  test \"lists all articles\", %{conn: conn} do\n    conn = get(conn, ~p\"/api/articles\")\n    assert json_response(conn, 200)[\"data\"] == []\n  end\nend\n```\n\nIt simply accesses the `index` path, asserts we got a JSON response with status 200 and that it contains a \"data\" key with an empty list, as we have no articles to return.\n\nThat was quite boring. Let's look at something more interesting.\n\n### The `create` action\n\nThe `create` action is defined like this:\n\n```elixir\ndef create(conn, %{\"article\" => article_params}) do\n  with {:ok, %Article{} = article} <- News.create_article(article_params) do\n    conn\n    |> put_status(:created)\n    |> put_resp_header(\"location\", ~p\"/api/articles/#{article}\")\n    |> render(:show, article: article)\n  end\nend\n```\n\nAs we can see, it checks if an article could be created. If so, it sets the status code to `:created` (which translates to 201), it sets a \"location\" header with the location of the article, and then renders \"show.json\" with the article.\n\nThis is precisely what the first test for the `create` action verifies:\n\n```elixir\ndescribe \"create article\" do\n  test \"renders article when data is valid\", %{conn: conn} do\n    conn = post(conn, ~p\"/articles\", article: @create_attrs)\n    assert %{\"id\" => id} = json_response(conn, 201)[\"data\"]\n\n    conn = get(conn, ~p\"/api/articles/#{id}\")\n\n    assert %{\n             \"id\" => ^id,\n             \"body\" => \"some body\",\n             \"title\" => \"some title\"\n           } = json_response(conn, 200)[\"data\"]\n  end\n```\n\nThe test uses `post/2` to create a new article and then we verify that the article returned a JSON response, with status 201, and that it had a \"data\" key in it. We pattern match the \"data\" on `%{\"id\" => id}`, which allows us to extract the ID of the new article. Then we perform a `get/2` request on the `show` route and verify that the article was successfully created.\n\nInside `describe \"create article\"`, we will find another test, which handles the failure scenario. Can you spot the failure scenario in the `create` action? Let's recap it:\n\n```elixir\ndef create(conn, %{\"article\" => article_params}) do\n  with {:ok, %Article{} = article} <- News.create_article(article_params) do\n```\n\nThe `with` special form that ships as part of Elixir allows us to check explicitly for the happy paths. In this case, we are interested only in the scenarios where `News.create_article(article_params)` returns `{:ok, article}`, if it returns anything else, the other value will simply be returned directly and none of the contents inside the `do/end` block will be executed. In other words, if `News.create_article/1` returns `{:error, changeset}`, we will simply return `{:error, changeset}` from the action.\n\nHowever, this introduces an issue. Our actions do not know how to handle the `{:error, changeset}` result by default. Luckily, we can teach Phoenix Controllers to handle it with the Action Fallback controller. At the top of `ArticleController`, you will find:\n\n```elixir\n  action_fallback HelloWeb.FallbackController\n```\n\nThis line says: if any action does not return a `%Plug.Conn{}`, we want to invoke `FallbackController` with the result. You will find `HelloWeb.FallbackController` at `lib/hello_web/controllers/fallback_controller.ex` and it looks like this:\n\n```elixir\ndefmodule HelloWeb.FallbackController do\n  use HelloWeb, :controller\n\n  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(json: HelloWeb.ChangesetJSON)\n    |> render(:error, changeset: changeset)\n  end\n\n  def call(conn, {:error, :not_found}) do\n    conn\n    |> put_status(:not_found)\n    |> put_view(html: HelloWeb.ErrorHTML, json: HelloWeb.ErrorJSON)\n    |> render(:\"404\")\n  end\nend\n```\n\nYou can see how the first clause of the `call/2` function handles the `{:error, changeset}` case, setting the status code to unprocessable entity (422), and then rendering \"error.json\" from the changeset view with the failed changeset.\n\nWith this in mind, let's look at our second test for `create`:\n\n```elixir\ntest \"renders errors when data is invalid\", %{conn: conn} do\n  conn = post(conn, ~p\"/api/articles\", article: @invalid_attrs)\n  assert json_response(conn, 422)[\"errors\"] != %{}\nend\n```\n\nIt simply posts to the `create` path with invalid parameters. This makes it return a JSON response, with status code 422, and a response with a non-empty \"errors\" key.\n\nThe `action_fallback` can be extremely useful to reduce boilerplate when designing APIs. You can learn more about the \"Action Fallback\" in the [Controllers guide](controllers.html).\n\n### The `delete` action\n\nFinally, the last action we will study is the `delete` action for JSON. Its implementation looks like this:\n\n```elixir\ndef delete(conn, %{\"id\" => id}) do\n  article = News.get_article!(id)\n\n  with {:ok, %Article{}} <- News.delete_article(article) do\n    send_resp(conn, :no_content, \"\")\n  end\nend\n```\n\nThe new action simply attempts to delete the article and, if it succeeds, it returns an empty response with status code `:no_content` (204).\n\nThe test looks like this:\n\n```elixir\ndescribe \"delete article\" do\n  setup [:create_article]\n\n  test \"deletes chosen article\", %{conn: conn, article: article} do\n    conn = delete(conn, ~p\"/api/articles/#{article}\")\n    assert response(conn, 204)\n\n    assert_error_sent 404, fn ->\n      get(conn, ~p\"/api/articles/#{article}\")\n    end\n  end\nend\n\ndefp create_article(_) do\n  article = article_fixture()\n  %{article: article}\nend\n```\n\nIt setups a new article, then in the test it invokes the `delete` path to delete it, asserting on a 204 response, which is neither JSON nor HTML. Then it verifies that we can no longer access said article.\n\nThat's all!\n\nNow that we understand how the scaffolded code and their tests work for both HTML and JSON APIs, we are prepared to move forward in building and maintaining our web applications!\n"
  },
  {
    "path": "installer/.gitignore",
    "content": "templates/phoenix-usage-rules/\n"
  },
  {
    "path": "installer/README.md",
    "content": "## mix phx.new\n\nProvides `phx.new` installer as an archive.\n\nTo install from Hex, run:\n\n    $ mix archive.install hex phx_new\n\nTo build and install it locally,\nensure any previous archive versions are removed:\n\n    $ mix archive.uninstall phx_new\n\nThen run:\n\n    $ cd installer\n    $ MIX_ENV=prod mix do archive.build + archive.install\n"
  },
  {
    "path": "installer/lib/mix/tasks/local.phx.ex",
    "content": "defmodule Mix.Tasks.Local.Phx do\n  use Mix.Task\n\n  @shortdoc \"Updates the Phoenix project generator locally\"\n\n  @moduledoc \"\"\"\n  Updates the Phoenix project generator locally.\n\n      $ mix local.phx\n\n  Accepts the same command line options as `archive.install hex phx_new`.\n  \"\"\"\n\n  @impl true\n  def run(args) do\n    Mix.Task.run(\"archive.install\", [\"hex\", \"phx_new\" | args])\n  end\nend\n"
  },
  {
    "path": "installer/lib/mix/tasks/phx.new.ecto.ex",
    "content": "defmodule Mix.Tasks.Phx.New.Ecto do\n  @moduledoc \"\"\"\n  Creates a new Ecto project within an umbrella project.\n\n  This task is intended to create a bare Ecto project without\n  web integration, which serves as a core application of your\n  domain for web applications and your greater umbrella\n  platform to integrate with.\n\n  It expects the name of the project as an argument.\n\n      $ cd my_umbrella/apps\n      $ mix phx.new.ecto APP [--module MODULE] [--app APP]\n\n  A project at the given APP directory will be created. The\n  application name and module name will be retrieved\n  from the application name, unless `--module` or `--app` is given.\n\n  ## Options\n\n    * `--app` - the name of the OTP application\n\n    * `--module` - the name of the base module in\n      the generated skeleton\n\n    * `--database` - specify the database adapter for Ecto. One of:\n\n        * `postgres` - via https://github.com/elixir-ecto/postgrex\n        * `mysql` - via https://github.com/elixir-ecto/myxql\n        * `mssql` - via https://github.com/livehelpnow/tds\n        * `sqlite3` - via https://github.com/elixir-sqlite/ecto_sqlite3\n\n      Please check the driver docs for more information\n      and requirements. Defaults to \"postgres\".\n\n    * `--binary-id` - use `binary_id` as primary key type\n      in Ecto schemas\n\n  ## Examples\n\n      $ mix phx.new.ecto hello_ecto\n\n  Is equivalent to:\n\n      $ mix phx.new.ecto hello_ecto --module HelloEcto\n  \"\"\"\n\n  @shortdoc \"Creates a new Ecto project within an umbrella project\"\n\n  use Mix.Task\n\n  @impl true\n  def run([]) do\n    Mix.Tasks.Help.run([\"phx.new.ecto\"])\n  end\n\n  def run([path | _] = args) do\n    unless Phx.New.Generator.in_umbrella?(path) do\n      Mix.raise(\"The ecto task can only be run within an umbrella's apps directory\")\n    end\n\n    Mix.Tasks.Phx.New.run(args ++ [\"--no-assets\", \"--ecto\"], Phx.New.Ecto, :app_path)\n  end\nend\n"
  },
  {
    "path": "installer/lib/mix/tasks/phx.new.ex",
    "content": "defmodule Mix.Tasks.Phx.New do\n  @moduledoc \"\"\"\n  Creates a new Phoenix project.\n\n  It expects the path of the project as an argument.\n\n      $ mix phx.new PATH [--module MODULE] [--app APP]\n\n  A project at the given PATH will be created. The\n  application name and module name will be retrieved\n  from the path, unless `--module` or `--app` is given.\n\n  ## Options\n\n    * `--umbrella` - generate an umbrella project,\n      with one application for your domain, and\n      a second application for the web interface.\n\n    * `--app` - the name of the OTP application\n\n    * `--module` - the name of the base module in\n      the generated skeleton\n\n    * `--database` - specify the database adapter for Ecto. One of:\n\n        * `postgres` - via https://github.com/elixir-ecto/postgrex\n        * `mysql` - via https://github.com/elixir-ecto/myxql\n        * `mssql` - via https://github.com/livehelpnow/tds\n        * `sqlite3` - via https://github.com/elixir-sqlite/ecto_sqlite3\n\n      Please check the driver docs for more information\n      and requirements. Defaults to \"postgres\".\n\n    * `--adapter` - specify the http adapter. One of:\n        * `cowboy` - via https://github.com/elixir-plug/plug_cowboy\n        * `bandit` - via https://github.com/mtrudel/bandit\n\n      Please check the adapter docs for more information\n      and requirements. Defaults to \"bandit\".\n\n    * `--no-assets` - equivalent to `--no-esbuild` and `--no-tailwind`\n\n    * `--no-dashboard` - do not include Phoenix.LiveDashboard\n\n    * `--no-ecto` - do not generate Ecto files\n\n    * `--no-esbuild` - do not include esbuild dependencies and assets.\n      We do not recommend setting this option, unless for API only\n      applications, as doing so requires you to manually add and\n      track JavaScript dependencies\n\n    * `--no-gettext` - do not generate gettext files\n\n    * `--no-html` - do not generate HTML views\n\n    * `--no-live` - comment out LiveView socket setup in your Endpoint\n      and assets/js/app.js. Automatically disabled if --no-html is given\n\n    * `--no-mailer` - do not generate Swoosh mailer files\n\n    * `--no-tailwind` - do not include tailwind dependencies and assets.\n      The generated markup will still include Tailwind CSS classes, those\n      are left-in as reference for the subsequent styling of your layout\n      and components\n\n    * `--binary-id` - use `binary_id` as primary key type in Ecto schemas\n\n    * `--verbose` - use verbose output\n\n    * `-v`, `--version` - prints the Phoenix installer version\n\n    * `--no-version-check` - skip the version check for the latest phx_new version\n\n    * `--no-agents-md` - do not generate an `AGENTS.md` file\n\n  When passing the `--no-ecto` flag, Phoenix generators such as\n  `phx.gen.html`, `phx.gen.json`, `phx.gen.live`, and `phx.gen.context`\n  may no longer work as expected as they generate context files that rely\n  on Ecto for the database access. In those cases, you can pass the\n  `--no-context` flag to generate most of the HTML and JSON files\n  but skip the context, allowing you to fill in the blanks as desired.\n\n  Similarly, if `--no-html` is given, the files generated by\n  `phx.gen.html` will no longer work, as important HTML components\n  will be missing.\n\n  ## Installation\n\n  `mix phx.new` by default prompts you to fetch and install your\n  dependencies. You can enable this behaviour by passing the\n  `--install` flag or disable it with the `--no-install` flag.\n\n  ## Examples\n\n      $ mix phx.new hello_world\n\n  Is equivalent to:\n\n      $ mix phx.new hello_world --module HelloWorld\n\n  Or without the HTML and JS bits (useful for APIs):\n\n      $ mix phx.new ~/Workspace/hello_world --no-html --no-assets\n\n  As an umbrella:\n\n      $ mix phx.new hello --umbrella\n\n  Would generate the following directory structure and modules:\n\n  ```text\n  hello_umbrella/   Hello.Umbrella\n    apps/\n      hello/        Hello\n      hello_web/    HelloWeb\n  ```\n\n  You can read more about umbrella projects in the\n  [mix documentation](https://hexdocs.pm/mix/Mix.Project.html#module-umbrella-projects)\n\n  ## `PHX_NEW_CACHE_DIR`\n\n  In rare cases, it may be useful to copy the build from a previously\n  cached build. To do this, set the `PHX_NEW_CACHE_DIR` environment\n  variable before running `mix phx.new`. For example, you could generate a\n  cache by running:\n\n  ```shell\n  mix phx.new mycache --no-install && cd mycache \\\n    && mix deps.get && mix deps.compile && mix assets.setup \\\n    && rm -rf assets config lib priv test mix.exs README.md\n  ```\n\n  Your cached build directory should contain:\n\n      _build\n      deps\n      mix.lock\n\n  Then you could run:\n\n  ```shell\n  PHX_NEW_CACHE_DIR=/path/to/mycache mix phx.new myapp\n  ```\n\n  The entire cache directory will be copied to the new project, replacing\n  any existing files where conflicts exist.\n  \"\"\"\n  use Mix.Task\n  alias Phx.New.{Generator, Project, Single, Umbrella, Web, Ecto}\n\n  @version Mix.Project.config()[:version]\n  @shortdoc \"Creates a new Phoenix v#{@version} application\"\n\n  @switches [\n    dev: :boolean,\n    assets: :boolean,\n    esbuild: :boolean,\n    tailwind: :boolean,\n    ecto: :boolean,\n    app: :string,\n    module: :string,\n    web_module: :string,\n    database: :string,\n    binary_id: :boolean,\n    html: :boolean,\n    gettext: :boolean,\n    umbrella: :boolean,\n    verbose: :boolean,\n    live: :boolean,\n    dashboard: :boolean,\n    install: :boolean,\n    prefix: :string,\n    mailer: :boolean,\n    adapter: :string,\n    inside_docker_env: :boolean,\n    from_elixir_install: :boolean,\n    version_check: :boolean,\n    agents_md: :boolean\n  ]\n\n  @reserved_app_names ~w(server table)\n\n  @impl true\n  def run([version]) when version in ~w(-v --version) do\n    Mix.shell().info(\"Phoenix installer v#{@version}\")\n  end\n\n  def run(argv) do\n    elixir_version_check!()\n\n    {opts, argv} = OptionParser.parse!(argv, strict: @switches)\n\n    version_task =\n      if Keyword.get(opts, :version_check, true) do\n        get_latest_version(\"phx_new\")\n      end\n\n    result =\n      case {opts, argv} do\n        {_opts, []} ->\n          Mix.Tasks.Help.run([\"phx.new\"])\n\n        {opts, [base_path | _]} ->\n          if opts[:umbrella] do\n            generate(base_path, Umbrella, :project_path, opts)\n          else\n            generate(base_path, Single, :base_path, opts)\n          end\n      end\n\n    if version_task do\n      try do\n        # if we get anything else than a `Version`, we'll get a MatchError\n        # and fail silently\n        %Version{} = latest_version = Task.await(version_task, 3_000)\n        maybe_warn_outdated(latest_version)\n      rescue\n        _ -> :ok\n      catch\n        :exit, _ -> :ok\n      end\n    end\n\n    result\n  end\n\n  @doc false\n  def run(argv, generator, path) do\n    elixir_version_check!()\n\n    case OptionParser.parse!(argv, strict: @switches) do\n      {_opts, []} -> Mix.Tasks.Help.run([\"phx.new\"])\n      {opts, [base_path | _]} -> generate(base_path, generator, path, opts)\n    end\n  end\n\n  defp generate(base_path, generator, path, opts) do\n    base_path\n    |> Project.new(opts)\n    |> generator.prepare_project()\n    |> Generator.put_binding()\n    |> validate_project(path)\n    |> generator.generate()\n    |> maybe_copy_cached_build(path)\n    |> maybe_init_git(path)\n    |> maybe_prompt_to_install_deps(generator, path)\n  end\n\n  defp validate_project(%Project{opts: opts} = project, path) do\n    check_app_name!(project.app, !!opts[:app])\n    check_directory_existence!(Map.fetch!(project, path))\n    check_module_name_validity!(project.root_mod)\n    check_module_name_availability!(project.root_mod)\n\n    project\n  end\n\n  defp maybe_prompt_to_install_deps(%Project{} = project, generator, path_key) do\n    # we can skip the install deps setup, even with --install, because we already copied deps\n    if project.cached_build_path do\n      project\n    else\n      prompt_to_install_deps(project, generator, path_key)\n    end\n  end\n\n  defp prompt_to_install_deps(%Project{} = project, generator, path_key) do\n    path = Map.fetch!(project, path_key)\n\n    install? =\n      Keyword.get_lazy(project.opts, :install, fn ->\n        Mix.shell().yes?(\"\\nFetch and install dependencies?\")\n      end)\n\n    cd_step = [\"$ cd #{relative_app_path(path)}\"]\n\n    maybe_cd(path, fn ->\n      mix_step = install_mix(project, install?)\n\n      if mix_step == [] do\n        builders = Keyword.fetch!(project.binding, :asset_builders)\n\n        if builders != [] do\n          Mix.shell().info([:green, \"* running \", :reset, \"mix assets.setup\"])\n\n          # First compile only builders so we can install in parallel\n          # TODO: Once we require Erlang/OTP 28, jason may no longer be required\n          cmd(project, \"mix deps.compile jason #{Enum.join(builders, \" \")}\", log: false)\n        end\n\n        tasks =\n          Enum.map(builders, fn builder ->\n            cmd = \"mix do loadpaths --no-compile --no-listeners + #{builder}.install\"\n            Task.async(fn -> cmd(project, cmd, log: false, cd: project.web_path) end)\n          end)\n\n        cmd(project, \"mix deps.compile\")\n\n        Task.await_many(tasks, :infinity)\n      end\n\n      print_missing_steps(cd_step ++ mix_step)\n\n      if Project.ecto?(project) do\n        print_ecto_info(generator)\n      end\n\n      if path_key == :web_path do\n        Mix.shell().info(\"\"\"\n        Your web app requires a PubSub server to be running.\n        The PubSub server is typically defined in a `mix phx.new.ecto` app.\n        If you don't plan to define an Ecto app, you must explicitly start\n        the PubSub in your supervision tree as:\n\n            {Phoenix.PubSub, name: #{inspect(project.app_mod)}.PubSub}\n        \"\"\")\n      end\n\n      print_mix_info(generator)\n    end)\n  end\n\n  defp maybe_cd(path, func), do: path && File.cd!(path, func)\n\n  defp install_mix(project, install?) do\n    if install? do\n      cmd(project, \"mix deps.get\")\n    else\n      [\"$ mix deps.get\"]\n    end\n  end\n\n  defp print_missing_steps(steps) do\n    Mix.shell().info(\"\"\"\n\n    We are almost there! The following steps are missing:\n\n        #{Enum.join(steps, \"\\n    \")}\n    \"\"\")\n  end\n\n  defp print_ecto_info(Web), do: :ok\n\n  defp print_ecto_info(_gen) do\n    Mix.shell().info(\"\"\"\n    Then configure your database in config/dev.exs and run:\n\n        $ mix ecto.create\n    \"\"\")\n  end\n\n  defp print_mix_info(Ecto) do\n    Mix.shell().info(\"\"\"\n    You can run your app inside IEx (Interactive Elixir) as:\n\n        $ iex -S mix\n    \"\"\")\n  end\n\n  defp print_mix_info(_gen) do\n    Mix.shell().info(\"\"\"\n    Start your Phoenix app with:\n\n        $ mix phx.server\n\n    You can also run your app inside IEx (Interactive Elixir) as:\n\n        $ iex -S mix phx.server\n    \"\"\")\n  end\n\n  defp relative_app_path(path) do\n    case Path.relative_to_cwd(path) do\n      ^path -> Path.basename(path)\n      rel -> rel\n    end\n  end\n\n  ## Helpers\n\n  defp cmd(%Project{} = project, cmd, opts \\\\ []) do\n    {log?, opts} = Keyword.pop(opts, :log, true)\n\n    if log? do\n      Mix.shell().info([:green, \"* running \", :reset, cmd])\n    end\n\n    case Mix.shell().cmd(cmd, opts ++ cmd_opts(project)) do\n      0 -> []\n      _ -> [\"$ #{cmd}\"]\n    end\n  end\n\n  defp cmd_opts(%Project{} = project) do\n    if Project.verbose?(project) do\n      []\n    else\n      [quiet: true]\n    end\n  end\n\n  defp check_app_name!(name, from_app_flag) do\n    with :ok <- validate_not_reserved(name),\n         :ok <- validate_app_name_format(name, from_app_flag) do\n      :ok\n    end\n  end\n\n  defp validate_not_reserved(name) when name in @reserved_app_names do\n    Mix.raise(\"Application name cannot be #{inspect(name)} as it is reserved\")\n  end\n\n  defp validate_not_reserved(_name), do: :ok\n\n  defp validate_app_name_format(name, from_app_flag) do\n    if name =~ ~r/^[a-z][a-z0-9_]*$/ do\n      :ok\n    else\n      extra =\n        if !from_app_flag do\n          \". The application name is inferred from the path, if you'd like to \" <>\n            \"explicitly name the application then use the `--app APP` option.\"\n        else\n          \"\"\n        end\n\n      Mix.raise(\n        \"Application name must start with a letter and have only lowercase \" <>\n          \"letters, numbers and underscore, got: #{inspect(name)}\" <> extra\n      )\n    end\n  end\n\n  defp check_module_name_validity!(name) do\n    unless inspect(name) =~ Regex.recompile!(~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/) do\n      Mix.raise(\n        \"Module name must be a valid Elixir alias (for example: Foo.Bar), got: #{inspect(name)}\"\n      )\n    end\n  end\n\n  defp check_module_name_availability!(name) do\n    [name]\n    |> Module.concat()\n    |> Module.split()\n    |> Enum.reduce([], fn name, acc ->\n      mod = Module.concat([Elixir, name | acc])\n\n      if Code.ensure_loaded?(mod) do\n        Mix.raise(\"Module name #{inspect(mod)} is already taken, please choose another name\")\n      else\n        [name | acc]\n      end\n    end)\n  end\n\n  defp check_directory_existence!(path) do\n    if File.dir?(path) and\n         not Mix.shell().yes?(\n           \"The directory #{path} already exists. Are you sure you want to continue?\"\n         ) do\n      Mix.raise(\"Please select another directory for installation.\")\n    end\n  end\n\n  defp elixir_version_check! do\n    unless Version.match?(System.version(), \"~> 1.15\") do\n      Mix.raise(\n        \"Phoenix v#{@version} requires at least Elixir v1.15\\n \" <>\n          \"You have #{System.version()}. Please update accordingly\"\n      )\n    end\n  end\n\n  defp git_available? do\n    case System.find_executable(\"git\") do\n      nil -> false\n      _path -> true\n    end\n  end\n\n  defp inside_git_repo?(path) do\n    case System.cmd(\"git\", [\"status\"], cd: path, stderr_to_stdout: true) do\n      {_output, 0} -> true\n      _ -> false\n    end\n  rescue\n    _ -> false\n  end\n\n  defp maybe_init_git(%Project{} = project, path_key) do\n    project_path = Map.fetch!(project, path_key)\n\n    if git_available?() and not inside_git_repo?(project_path) do\n      Mix.shell().info([:green, \"* initializing git repository\", :reset])\n\n      case System.cmd(\"git\", [\"init\"], cd: project_path) do\n        {_output, 0} ->\n          :ok\n\n        {output, _} ->\n          Mix.shell().error(\"Failed to initialize git repository: #{output}\")\n      end\n    end\n\n    project\n  end\n\n  defp maybe_copy_cached_build(%Project{} = project, path_key) do\n    project_path = Map.fetch!(project, path_key)\n\n    case System.fetch_env(\"PHX_NEW_CACHE_DIR\") do\n      {:ok, cache_dir} ->\n        copy_cached_build(%{project_path: project_path, cache_dir: cache_dir})\n        %{project | cached_build_path: cache_dir}\n\n      :error ->\n        project\n    end\n  end\n\n  defp copy_cached_build(%{project_path: project_path, cache_dir: cache_dir}) do\n    if File.exists?(cache_dir) do\n      Mix.shell().info(\"Copying cached build from #{cache_dir}\")\n      System.cmd(\"cp\", [\"-Rp\", Path.join(cache_dir, \".\"), project_path])\n    end\n  end\n\n  defp maybe_warn_outdated(latest_version) do\n    if Version.compare(@version, latest_version) == :lt do\n      Mix.shell().info([\n        :yellow,\n        \"A new version of phx.new is available:\",\n        :green,\n        \" v#{latest_version}\",\n        :reset,\n        \".\",\n        \"\\n\",\n        \"You are currently running \",\n        :red,\n        \"v#{@version}\",\n        :reset,\n        \".\\n\",\n        \"To update, run:\\n\\n\",\n        \"    $ mix local.phx\\n\"\n      ])\n    end\n  end\n\n  # we need to parse JSON, so we only check for new versions on Elixir 1.18+\n  if Version.match?(System.version(), \"~> 1.18\") do\n    defp get_latest_version(package) do\n      Task.async(fn ->\n        # ignore any errors to not prevent the generators from running\n        # due to any issues while checking the version\n        try do\n          with {:ok, package} <- get_package(package) do\n            versions =\n              for release <- package[\"releases\"],\n                  version = Version.parse!(release[\"version\"]),\n                  # ignore pre-releases like release candidates, etc.\n                  version.pre == [] do\n                version\n              end\n\n            Enum.max(versions, Version)\n          end\n        rescue\n          e -> {:error, e}\n        catch\n          :exit, _ -> {:error, :exit}\n        end\n      end)\n    end\n\n    defp get_package(name) do\n      http_options =\n        [\n          ssl: [\n            verify: :verify_peer,\n            cacerts: :public_key.cacerts_get(),\n            depth: 2,\n            customize_hostname_check: [\n              match_fun: :public_key.pkix_verify_hostname_match_fun(:https)\n            ],\n            versions: [:\"tlsv1.2\", :\"tlsv1.3\"]\n          ]\n        ]\n\n      options = [body_format: :binary]\n\n      case :httpc.request(\n             :get,\n             {~c\"https://hex.pm/api/packages/#{name}\",\n              [{~c\"user-agent\", ~c\"Mix.Tasks.Phx.New/#{@version}\"}]},\n             http_options,\n             options\n           ) do\n        {:ok, {{_, 200, _}, _headers, body}} ->\n          {:ok, JSON.decode!(body)}\n\n        {:ok, {{_, status, _}, _, _}} ->\n          {:error, status}\n\n        {:error, reason} ->\n          {:error, reason}\n      end\n    end\n  else\n    defp get_latest_version(_), do: nil\n  end\nend\n"
  },
  {
    "path": "installer/lib/mix/tasks/phx.new.web.ex",
    "content": "defmodule Mix.Tasks.Phx.New.Web do\n  @moduledoc \"\"\"\n  Creates a new Phoenix web project within an umbrella project.\n\n  It expects the name of the OTP app as the first argument and\n  for the command to be run inside your umbrella application's\n  apps directory:\n\n      $ cd my_umbrella/apps\n      $ mix phx.new.web APP [--module MODULE] [--app APP]\n\n  This task is intended to create a bare Phoenix project without\n  database integration, which interfaces with your greater\n  umbrella application(s).\n\n  ## Examples\n\n      $ mix phx.new.web hello_web\n\n  Is equivalent to:\n\n      $ mix phx.new.web hello_web --module HelloWeb\n\n  Supports the same options as the `phx.new` task.\n  See `Mix.Tasks.Phx.New` for details.\n  \"\"\"\n\n  @shortdoc \"Creates a new Phoenix web project within an umbrella project\"\n\n  use Mix.Task\n\n  @impl true\n  def run([]) do\n    Mix.Tasks.Help.run([\"phx.new.web\"])\n  end\n\n  def run([path | _] = args) do\n    unless Phx.New.Generator.in_umbrella?(path) do\n      Mix.raise \"The web task can only be run within an umbrella's apps directory\"\n    end\n\n    Mix.Tasks.Phx.New.run(args, Phx.New.Web, :web_path)\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/ecto.ex",
    "content": "defmodule Phx.New.Ecto do\n  @moduledoc false\n  use Phx.New.Generator\n  alias Phx.New.{Project}\n\n  @pre \"phx_umbrella/apps/app_name\"\n\n  template(:new, [\n    {:config, :project, \"#{@pre}/config/config.exs.eex\": \"config/config.exs\"},\n    {:eex, :app,\n     \"#{@pre}/lib/app_name/application.ex.eex\": \"lib/:app/application.ex\",\n     \"#{@pre}/lib/app_name.ex.eex\": \"lib/:app.ex\",\n     \"#{@pre}/test/test_helper.exs.eex\": \"test/test_helper.exs\",\n     \"#{@pre}/README.md.eex\": \"README.md\",\n     \"#{@pre}/mix.exs.eex\": \"mix.exs\",\n     \"#{@pre}/gitignore.eex\": \".gitignore\",\n     \"#{@pre}/formatter.exs.eex\": \".formatter.exs\"}\n  ])\n\n  def prepare_project(%Project{} = project) do\n    app_path = Path.expand(project.base_path)\n    project_path = Path.dirname(Path.dirname(app_path))\n\n    %{project | in_umbrella?: true, app_path: app_path, project_path: project_path}\n  end\n\n  def generate(%Project{} = project) do\n    inject_umbrella_config_defaults(project)\n    copy_from(project, __MODULE__, :new)\n    if Project.ecto?(project), do: Phx.New.Single.gen_ecto(project)\n    project\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/generator.ex",
    "content": "defmodule Phx.New.Generator do\n  @moduledoc false\n  import Mix.Generator\n  alias Phx.New.{Project}\n\n  @phoenix Path.expand(\"../..\", __DIR__)\n  @phoenix_version Version.parse!(Mix.Project.config()[:version])\n\n  @callback prepare_project(Project.t()) :: Project.t()\n  @callback generate(Project.t()) :: Project.t()\n\n  defmacro __using__(_env) do\n    quote do\n      @behaviour unquote(__MODULE__)\n      import Mix.Generator\n      import unquote(__MODULE__)\n      Module.register_attribute(__MODULE__, :templates, accumulate: true)\n      @before_compile unquote(__MODULE__)\n    end\n  end\n\n  defmacro __before_compile__(env) do\n    root = Path.expand(\"../../templates\", __DIR__)\n\n    templates_ast =\n      for {name, mappings} <- Module.get_attribute(env.module, :templates) do\n        for {format, _proj_location, files} <- mappings,\n            format != :keep,\n            {source, _target} <- files,\n            source = to_string(source) do\n          path = Path.join(root, source)\n\n          if format in [:config, :prod_config, :eex] do\n            compiled = EEx.compile_file(path)\n\n            quote do\n              @external_resource unquote(path)\n              @file unquote(path)\n              def render(unquote(name), unquote(source), var!(assigns))\n                  when is_list(var!(assigns)) do\n                var!(maybe_heex_attr_gettext) = &unquote(__MODULE__).maybe_heex_attr_gettext/2\n                _ = var!(maybe_heex_attr_gettext)\n                var!(maybe_eex_gettext) = &unquote(__MODULE__).maybe_eex_gettext/2\n                _ = var!(maybe_eex_gettext)\n                unquote(compiled)\n              end\n            end\n          else\n            quote do\n              @external_resource unquote(path)\n              def render(unquote(name), unquote(source), _assigns), do: unquote(File.read!(path))\n            end\n          end\n        end\n      end\n\n    quote do\n      unquote(templates_ast)\n      def template_files(name), do: Keyword.fetch!(@templates, name)\n    end\n  end\n\n  defmacro template(name, mappings) do\n    quote do\n      @templates {unquote(name), unquote(mappings)}\n    end\n  end\n\n  def copy_from(%Project{} = project, mod, name) when is_atom(name) do\n    mapping = mod.template_files(name)\n\n    for {format, project_location, files} <- mapping,\n        {source, target_path} <- files,\n        source = to_string(source) do\n      target = Project.join_path(project, project_location, target_path)\n\n      case format do\n        :keep ->\n          File.mkdir_p!(target)\n\n        :text ->\n          create_file(target, mod.render(name, source, project.binding))\n\n        :config ->\n          contents = mod.render(name, source, project.binding)\n          config_inject(Path.dirname(target), Path.basename(target), contents)\n\n        :prod_config ->\n          contents = mod.render(name, source, project.binding)\n          prod_only_config_inject(Path.dirname(target), Path.basename(target), contents)\n\n        :eex ->\n          contents = mod.render(name, source, project.binding)\n          create_file(target, contents)\n      end\n    end\n  end\n\n  parent_rules = Path.join(@phoenix, \"../usage-rules\")\n  # those are copied before publishing to Hex\n  copied_rules = Path.expand(\"../../templates/phoenix-usage-rules\", __DIR__)\n\n  @usage_rules_path if(File.exists?(parent_rules), do: parent_rules, else: copied_rules)\n\n  @rules_files Map.new(File.ls!(@usage_rules_path), fn file ->\n                 content = File.read!(Path.join(@usage_rules_path, file))\n                 {file, String.trim_trailing(content)}\n               end)\n\n  @new_project_rules_files Map.new(\n                             File.ls!(Path.expand(\"../../templates/usage-rules\", __DIR__)),\n                             fn file ->\n                               base_path = Path.expand(\"../../templates/usage-rules\", __DIR__)\n                               content = File.read!(Path.join(base_path, file))\n                               {file, String.trim_trailing(content)}\n                             end\n                           )\n\n  def generate_agents_md(%Project{} = project) do\n    if project.binding[:agents_md] do\n      content =\n        [\n          # rules specific to new apps\n          @new_project_rules_files[\"project.md\"],\n          @new_project_rules_files[\"phoenix.md\"],\n          # --no-assets is equivalent to --no-tailwind && --no-esbuild;\n          # we check for both here\n          project.binding[:javascript] && project.binding[:css] &&\n            @new_project_rules_files[\"assets.md\"],\n          # generic usage rules\n          \"\\n<!-- usage-rules-start -->\",\n          [\n            \"<!-- phoenix:elixir-start -->\\n\",\n            @rules_files[\"elixir.md\"],\n            \"\\n<!-- phoenix:elixir-end -->\"\n          ],\n          [\n            \"<!-- phoenix:phoenix-start -->\\n\",\n            @rules_files[\"phoenix.md\"],\n            \"\\n<!-- phoenix:phoenix-end -->\"\n          ],\n          project.binding[:ecto] &&\n            [\n              \"<!-- phoenix:ecto-start -->\\n\",\n              @rules_files[\"ecto.md\"],\n              \"\\n<!-- phoenix:ecto-end -->\"\n            ],\n          project.binding[:html] &&\n            [\n              \"<!-- phoenix:html-start -->\\n\",\n              @rules_files[\"html.md\"],\n              \"\\n<!-- phoenix:html-end -->\"\n            ],\n          project.binding[:live] &&\n            [\n              \"<!-- phoenix:liveview-start -->\\n\",\n              @rules_files[\"liveview.md\"],\n              \"\\n<!-- phoenix:liveview-end -->\"\n            ],\n          \"<!-- usage-rules-end -->\"\n        ]\n        |> Enum.reject(fn part -> part == nil or part == false end)\n        |> Enum.intersperse(\"\\n\\n\")\n\n      File.write!(Path.join(project.project_path, \"AGENTS.md\"), content)\n    end\n  end\n\n  def config_inject(path, file, to_inject) do\n    file = Path.join(path, file)\n\n    contents =\n      case File.read(file) do\n        {:ok, bin} -> bin\n        {:error, _} -> \"import Config\\n\"\n      end\n\n    case :binary.split(contents, \"import Config\") do\n      [left, right] ->\n        write_formatted!(file, [left, to_inject, right])\n\n      [_] ->\n        Mix.raise(~s[Could not find \"import Config\" in #{inspect(file)}])\n    end\n  end\n\n  def prod_only_config_inject(path, file, to_inject) do\n    file = Path.join(path, file)\n\n    contents =\n      case File.read(file) do\n        {:ok, bin} ->\n          bin\n\n        {:error, _} ->\n          \"\"\"\n          import Config\n\n          if config_env() == :prod do\n          end\n          \"\"\"\n      end\n\n    case :binary.split(contents, \"if config_env() == :prod do\") do\n      [left, right] ->\n        write_formatted!(file, [left, \"if config_env() == :prod do\\n\", to_inject, right])\n\n      [_] ->\n        Mix.raise(~s[Could not find \"if config_env() == :prod do\" in #{inspect(file)}])\n    end\n  end\n\n  defp write_formatted!(file, contents) do\n    formatted = contents |> IO.iodata_to_binary() |> Code.format_string!()\n    File.mkdir_p!(Path.dirname(file))\n    File.write!(file, [formatted, ?\\n])\n  end\n\n  def inject_umbrella_config_defaults(project) do\n    unless File.exists?(Project.join_path(project, :project, \"config/dev.exs\")) do\n      path = Project.join_path(project, :project, \"config/config.exs\")\n\n      extra =\n        Phx.New.Umbrella.render(:new, \"phx_umbrella/config/extra_config.exs.eex\", project.binding)\n\n      File.write(path, [File.read!(path), extra])\n    end\n  end\n\n  def in_umbrella?(app_path) do\n    umbrella = Path.expand(Path.join([app_path, \"..\", \"..\"]))\n    mix_path = Path.join(umbrella, \"mix.exs\")\n    apps_path = Path.join(umbrella, \"apps\")\n\n    File.exists?(mix_path) && File.exists?(apps_path)\n  end\n\n  def put_binding(%Project{opts: opts} = project) do\n    db = Keyword.get(opts, :database, \"postgres\")\n    web_adapter = Keyword.get(opts, :adapter, \"bandit\")\n    ecto = Keyword.get(opts, :ecto, true)\n    html = Keyword.get(opts, :html, true)\n    live = html && Keyword.get(opts, :live, true)\n    dashboard = Keyword.get(opts, :dashboard, true)\n    gettext = Keyword.get(opts, :gettext, true)\n    assets = Keyword.get(opts, :assets, true)\n    esbuild = Keyword.get(opts, :esbuild, assets)\n    tailwind = Keyword.get(opts, :tailwind, assets)\n    mailer = Keyword.get(opts, :mailer, true)\n    dev = Keyword.get(opts, :dev, false)\n    from_elixir_install = Keyword.get(opts, :from_elixir_install, false)\n    phoenix_path = phoenix_path(project, dev, false)\n    phoenix_path_umbrella_root = phoenix_path(project, dev, true)\n    agents_md = Keyword.get(opts, :agents_md, true)\n\n    # detect if we're inside a docker env, but if we're in github actions,\n    # we want to treat it like regular env for end-user testing purposes\n    inside_docker_env? =\n      Keyword.get_lazy(opts, :inside_docker_env, fn ->\n        if System.get_env(\"PHX_CI\") do\n          false\n        else\n          File.exists?(\"/.dockerenv\")\n        end\n      end)\n\n    # We lowercase the database name because according to the\n    # SQL spec, they are case insensitive unless quoted, which\n    # means creating a database like FoO is the same as foo in\n    # some storages.\n    {adapter_app, adapter_module, adapter_config} =\n      get_ecto_adapter(db, String.downcase(project.app), project.app_mod)\n\n    {web_adapter_app, web_adapter_vsn, web_adapter_module, web_adapter_docs} =\n      get_web_adapter(web_adapter)\n\n    pubsub_server = get_pubsub_server(project.app_mod)\n\n    adapter_config =\n      case Keyword.fetch(opts, :binary_id) do\n        {:ok, value} -> Keyword.put_new(adapter_config, :binary_id, value)\n        :error -> adapter_config\n      end\n\n    binding = [\n      app_name: project.app,\n      app_module: inspect(project.app_mod),\n      root_app_name: project.root_app,\n      root_app_module: inspect(project.root_mod),\n      lib_web_name: project.lib_web_name,\n      web_app_name: project.web_app,\n      endpoint_module: inspect(Module.concat(project.web_namespace, Endpoint)),\n      web_namespace: inspect(project.web_namespace),\n      phoenix_dep: phoenix_dep(phoenix_path),\n      phoenix_dep_umbrella_root: phoenix_dep(phoenix_path_umbrella_root),\n      phoenix_js_path: phoenix_js_path(phoenix_path),\n      phoenix_version: @phoenix_version,\n      pubsub_server: pubsub_server,\n      secret_key_base_dev: random_string(64),\n      secret_key_base_test: random_string(64),\n      signing_salt: random_string(8),\n      lv_signing_salt: random_string(8),\n      in_umbrella: project.in_umbrella?,\n      asset_builders: Enum.filter([tailwind && :tailwind, esbuild && :esbuild], & &1),\n      javascript: esbuild,\n      css: tailwind,\n      mailer: mailer,\n      ecto: ecto,\n      html: html,\n      live: live,\n      live_comment: if(live, do: nil, else: \"// \"),\n      dashboard: dashboard,\n      gettext: gettext,\n      adapter_app: adapter_app,\n      adapter_module: adapter_module,\n      adapter_config: adapter_config,\n      web_adapter_app: web_adapter_app,\n      web_adapter_module: web_adapter_module,\n      web_adapter_vsn: web_adapter_vsn,\n      web_adapter_docs: web_adapter_docs,\n      generators: nil_if_empty(project.generators ++ adapter_generators(adapter_config)),\n      namespaced?: namespaced?(project),\n      dev: dev,\n      from_elixir_install: from_elixir_install,\n      elixir_install_otp_bin_path: from_elixir_install && elixir_install_otp_bin_path(),\n      elixir_install_bin_path: from_elixir_install && elixir_install_bin_path(),\n      inside_docker_env?: inside_docker_env?,\n      agents_md: agents_md,\n      config_regex_E: Version.match?(System.version(), \"~> 1.19.3 or ~> 1.20\") && \"E\" || \"\"\n    ]\n\n    %{project | binding: binding}\n  end\n\n  def elixir_install_otp_bin_path do\n    \"erl\"\n    |> System.find_executable()\n    |> Path.split()\n    |> Enum.drop(-1)\n    |> Path.join()\n    |> Path.relative_to(System.user_home())\n  end\n\n  def elixir_install_bin_path do\n    \"elixir\"\n    |> System.find_executable()\n    |> Path.split()\n    |> Enum.drop(-1)\n    |> Path.join()\n    |> Path.relative_to(System.user_home())\n  end\n\n  defp namespaced?(project) do\n    Macro.camelize(project.app) != inspect(project.app_mod)\n  end\n\n  def gen_ecto_config(%Project{project_path: project_path, binding: binding}) do\n    adapter_config = binding[:adapter_config]\n\n    config_inject(project_path, \"config/dev.exs\", \"\"\"\n    import Config\n\n    # Configure your database\n    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config(adapter_config[:dev])}\n    \"\"\")\n\n    config_inject(project_path, \"config/test.exs\", \"\"\"\n    import Config\n\n    # Configure your database\n    #\n    # The MIX_TEST_PARTITION environment variable can be used\n    # to provide built-in test partitioning in CI environment.\n    # Run `mix help test` for more information.\n    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo#{kw_to_config(adapter_config[:test])}\n    \"\"\")\n\n    prod_only_config_inject(project_path, \"config/runtime.exs\", \"\"\"\n    #{adapter_config[:prod_variables]}\n\n    config :#{binding[:app_name]}, #{binding[:app_module]}.Repo,\n      #{adapter_config[:prod_config]}\n    \"\"\")\n  end\n\n  defp get_pubsub_server(module) do\n    module\n    |> Module.split()\n    |> hd()\n    |> Module.concat(PubSub)\n  end\n\n  defp get_ecto_adapter(\"mssql\", app, module) do\n    {:tds, Ecto.Adapters.Tds, socket_db_config(app, module, \"sa\", \"some!Password\")}\n  end\n\n  defp get_ecto_adapter(\"mysql\", app, module) do\n    {:myxql, Ecto.Adapters.MyXQL, socket_db_config(app, module, \"root\", \"\")}\n  end\n\n  defp get_ecto_adapter(\"postgres\", app, module) do\n    {:postgrex, Ecto.Adapters.Postgres, socket_db_config(app, module, \"postgres\", \"postgres\")}\n  end\n\n  defp get_ecto_adapter(\"sqlite3\", app, module) do\n    {:ecto_sqlite3, Ecto.Adapters.SQLite3, fs_db_config(app, module)}\n  end\n\n  defp get_ecto_adapter(db, _app, _mod) do\n    Mix.raise(\"Unknown database #{inspect(db)}\")\n  end\n\n  defp get_web_adapter(\"cowboy\"),\n    do:\n      {:plug_cowboy, \"~> 2.7\", Phoenix.Endpoint.Cowboy2Adapter,\n       \"https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html\"}\n\n  defp get_web_adapter(\"bandit\"),\n    do:\n      {:bandit, \"~> 1.5\", Bandit.PhoenixAdapter,\n       \"https://hexdocs.pm/bandit/Bandit.html#t:options/0\"}\n\n  defp get_web_adapter(other), do: Mix.raise(\"Unknown web adapter #{inspect(other)}\")\n\n  defp fs_db_config(app, module) do\n    [\n      dev: [\n        database: {:literal, ~s|Path.expand(\"../#{app}_dev.db\", __DIR__)|},\n        pool_size: 5,\n        stacktrace: true,\n        show_sensitive_data_on_connection_error: true\n      ],\n      test: [\n        database: {:literal, ~s|Path.expand(\"../#{app}_test.db\", __DIR__)|},\n        pool_size: 5,\n        pool: Ecto.Adapters.SQL.Sandbox\n      ],\n      test_setup_all: \"Ecto.Adapters.SQL.Sandbox.mode(#{inspect(module)}.Repo, :manual)\",\n      test_setup: \"\"\"\n          pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(module)}.Repo, shared: not tags[:async])\n          on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)\\\n      \"\"\",\n      prod_variables: \"\"\"\n      database_path =\n        System.get_env(\"DATABASE_PATH\") ||\n          raise \\\"\"\"\n          environment variable DATABASE_PATH is missing.\n          For example: /etc/#{app}/#{app}.db\n          \\\"\"\"\n      \"\"\",\n      prod_config: \"\"\"\n      database: database_path,\n      pool_size: String.to_integer(System.get_env(\"POOL_SIZE\") || \"5\")\n      \"\"\"\n    ]\n  end\n\n  defp socket_db_config(app, module, user, pass) do\n    [\n      dev: [\n        username: user,\n        password: pass,\n        hostname: \"localhost\",\n        database: \"#{app}_dev\",\n        stacktrace: true,\n        show_sensitive_data_on_connection_error: true,\n        pool_size: 10\n      ],\n      test: [\n        username: user,\n        password: pass,\n        hostname: \"localhost\",\n        database: {:literal, ~s|\"#{app}_test\\#{System.get_env(\"MIX_TEST_PARTITION\")}\"|},\n        pool: Ecto.Adapters.SQL.Sandbox,\n        pool_size: {:literal, ~s|System.schedulers_online() * 2|}\n      ],\n      test_setup_all: \"Ecto.Adapters.SQL.Sandbox.mode(#{inspect(module)}.Repo, :manual)\",\n      test_setup: \"\"\"\n          pid = Ecto.Adapters.SQL.Sandbox.start_owner!(#{inspect(module)}.Repo, shared: not tags[:async])\n          on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end)\\\n      \"\"\",\n      prod_variables: \"\"\"\n      database_url =\n        System.get_env(\"DATABASE_URL\") ||\n          raise \\\"\"\"\n          environment variable DATABASE_URL is missing.\n          For example: ecto://USER:PASS@HOST/DATABASE\n          \\\"\"\"\n\n      maybe_ipv6 = if System.get_env(\"ECTO_IPV6\") in ~w(true 1), do: [:inet6], else: []\n\n      \"\"\",\n      prod_config: \"\"\"\n      # ssl: true,\n      url: database_url,\n      pool_size: String.to_integer(System.get_env(\"POOL_SIZE\") || \"10\"),\n      # For machines with several cores, consider starting multiple pools of `pool_size`\n      # pool_count: 4,\n      socket_options: maybe_ipv6\n      \"\"\"\n    ]\n  end\n\n  defp kw_to_config(kw) do\n    Enum.map(kw, fn\n      {k, {:literal, v}} -> \",\\n  #{k}: #{v}\"\n      {k, v} -> \",\\n  #{k}: #{inspect(v)}\"\n    end)\n  end\n\n  defp adapter_generators(adapter_config) do\n    adapter_config\n    |> Keyword.take([:binary_id, :migration, :sample_binary_id])\n    |> Enum.filter(fn {_, value} -> not is_nil(value) end)\n  end\n\n  defp nil_if_empty([]), do: nil\n  defp nil_if_empty(other), do: other\n\n  defp phoenix_path(%Project{} = project, true = _dev, umbrella_root?) do\n    absolute = Path.expand(project.project_path)\n    relative = Path.relative_to(absolute, @phoenix)\n\n    if absolute == relative do\n      Mix.raise(\"--dev projects must be generated inside Phoenix directory\")\n    end\n\n    project\n    |> phoenix_path_prefix(umbrella_root?)\n    |> Path.join(relative)\n    |> Path.split()\n    |> Enum.map(fn _ -> \"..\" end)\n    |> Path.join()\n  end\n\n  defp phoenix_path(%Project{}, false = _dev, _umbrella_root?) do\n    \"deps/phoenix\"\n  end\n\n  defp phoenix_path_prefix(%Project{in_umbrella?: false}, _), do: \"..\"\n  defp phoenix_path_prefix(%Project{in_umbrella?: true}, true = _umbrella_root?), do: \"..\"\n  defp phoenix_path_prefix(%Project{in_umbrella?: true}, false = _umbrella_root?), do: \"../../../\"\n\n  case @phoenix_version do\n    %Version{pre: \"dev\"} ->\n      defp phoenix_dep(\"deps/phoenix\") do\n        ~s[{:phoenix, github: \"phoenixframework/phoenix\", override: true}]\n      end\n\n    %Version{pre: [\"rc\", _]} ->\n      defp phoenix_dep(\"deps/phoenix\") do\n        ~s[{:phoenix, \"~> #{unquote(to_string(@phoenix_version))}\", override: true}]\n      end\n\n    %Version{} ->\n      defp phoenix_dep(\"deps/phoenix\") do\n        ~s[{:phoenix, \"~> #{unquote(to_string(@phoenix_version))}\"}]\n      end\n  end\n\n  defp phoenix_dep(path),\n    do: ~s[{:phoenix, path: #{inspect(path)}, override: true}]\n\n  defp phoenix_js_path(\"deps/phoenix\"), do: \"phoenix\"\n  defp phoenix_js_path(path), do: \"../../#{path}/\"\n\n  defp random_string(length) do\n    :crypto.strong_rand_bytes(length) |> Base.encode64(padding: false) |> binary_part(0, length)\n  end\n\n  # In the context of a HEEx attribute value, transforms a given message into a\n  # dynamic `gettext` call or a fixed-value string attribute, depending on the\n  # `gettext?` parameter.\n  #\n  # ## Examples\n  #\n  #     iex> ~s|<tag attr=#{maybe_heex_attr_gettext(\"Hello\", true)} />|\n  #     ~S|<tag attr={gettext(\"Hello\")} />|\n  #\n  #     iex> ~s|<tag attr=#{maybe_heex_attr_gettext(\"Hello\", false)} />|\n  #     ~S|<tag attr=\"Hello\" />|\n  def maybe_heex_attr_gettext(message, gettext?) do\n    if gettext? do\n      ~s|{gettext(#{inspect(message)})}|\n    else\n      inspect(message)\n    end\n  end\n\n  # In the context of an EEx template, transforms a given message into a dynamic\n  # `gettext` call or the message as is, depending on the `gettext?` parameter.\n  #\n  # ## Examples\n  #\n  #     iex> ~s|<tag>#{maybe_eex_gettext(\"Hello\", true)}</tag>|\n  #     ~S|<tag>{gettext(\"Hello\")}</tag>|\n  #\n  #     iex> ~s|<tag>#{maybe_eex_gettext(\"Hello\", false)}</tag>|\n  #     ~S|<tag>Hello</tag>|\n  def maybe_eex_gettext(message, gettext?) do\n    if gettext? do\n      ~s|{gettext(#{inspect(message)})}|\n    else\n      message\n    end\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/mailer.ex",
    "content": "defmodule Phx.New.Mailer do\n  @moduledoc false\n  use Phx.New.Generator\n  alias Phx.New.{Project}\n\n  template(:new, [\n    {:eex, :app, \"phx_mailer/lib/app_name/mailer.ex.eex\": \"lib/:app/mailer.ex\"}\n  ])\n\n  def prepare_project(%Project{} = project) do\n    app_path = Path.expand(project.base_path)\n    project_path = Path.dirname(Path.dirname(app_path))\n\n    %{project | in_umbrella?: true, app_path: app_path, project_path: project_path}\n  end\n\n  def generate(%Project{} = project) do\n    inject_umbrella_config_defaults(project)\n    copy_from(project, __MODULE__, :new)\n    project\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/project.ex",
    "content": "defmodule Phx.New.Project do\n  @moduledoc false\n  alias Phx.New.Project\n\n  defstruct base_path: nil,\n            app: nil,\n            app_mod: nil,\n            app_path: nil,\n            lib_web_name: nil,\n            root_app: nil,\n            root_mod: nil,\n            project_path: nil,\n            web_app: nil,\n            web_namespace: nil,\n            web_path: nil,\n            opts: :unset,\n            in_umbrella?: false,\n            binding: [],\n            cached_build_path: nil,\n            generators: [timestamp_type: :utc_datetime]\n\n  def new(project_path, opts) do\n    project_path = Path.expand(project_path)\n    app = opts[:app] || Path.basename(project_path)\n    app_mod = Module.concat([opts[:module] || Macro.camelize(app)])\n\n    %Project{\n      base_path: project_path,\n      app: app,\n      app_mod: app_mod,\n      root_app: app,\n      root_mod: app_mod,\n      opts: opts\n    }\n  end\n\n  def ecto?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :ecto)\n  end\n\n  def html?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :html)\n  end\n\n  def gettext?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :gettext)\n  end\n\n  def live?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :live)\n  end\n\n  def dashboard?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :dashboard)\n  end\n\n  def javascript?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :javascript)\n  end\n\n  def css?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :css)\n  end\n\n  def mailer?(%Project{binding: binding}) do\n    Keyword.fetch!(binding, :mailer)\n  end\n\n  def verbose?(%Project{opts: opts}) do\n    Keyword.get(opts, :verbose, false)\n  end\n\n  def join_path(%Project{} = project, location, path)\n      when location in [:project, :app, :web] do\n    project\n    |> Map.fetch!(:\"#{location}_path\")\n    |> Path.join(path)\n    |> expand_path_with_bindings(project)\n  end\n\n  defp expand_path_with_bindings(path, %Project{} = project) do\n    Regex.replace(Regex.recompile!(~r/:[a-zA-Z0-9_]+/), path, fn \":\" <> key, _ ->\n      project |> Map.fetch!(:\"#{key}\") |> to_string()\n    end)\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/single.ex",
    "content": "defmodule Phx.New.Single do\n  @moduledoc false\n  use Phx.New.Generator\n  alias Phx.New.{Project}\n\n  template(:new, [\n    {:config, :project,\n     \"phx_single/config/config.exs.eex\": \"config/config.exs\",\n     \"phx_single/config/dev.exs.eex\": \"config/dev.exs\",\n     \"phx_single/config/prod.exs.eex\": \"config/prod.exs\",\n     \"phx_single/config/runtime.exs.eex\": \"config/runtime.exs\",\n     \"phx_single/config/test.exs.eex\": \"config/test.exs\"},\n    {:eex, :web,\n     \"phx_single/lib/app_name/application.ex.eex\": \"lib/:app/application.ex\",\n     \"phx_single/lib/app_name.ex.eex\": \"lib/:app.ex\",\n     \"phx_web/controllers/error_json.ex.eex\": \"lib/:lib_web_name/controllers/error_json.ex\",\n     \"phx_web/endpoint.ex.eex\": \"lib/:lib_web_name/endpoint.ex\",\n     \"phx_web/router.ex.eex\": \"lib/:lib_web_name/router.ex\",\n     \"phx_web/telemetry.ex.eex\": \"lib/:lib_web_name/telemetry.ex\",\n     \"phx_single/lib/app_name_web.ex.eex\": \"lib/:lib_web_name.ex\",\n     \"phx_single/mix.exs.eex\": \"mix.exs\",\n     \"phx_single/README.md.eex\": \"README.md\",\n     \"phx_single/formatter.exs.eex\": \".formatter.exs\",\n     \"phx_single/gitignore.eex\": \".gitignore\",\n     \"phx_test/support/conn_case.ex.eex\": \"test/support/conn_case.ex\",\n     \"phx_single/test/test_helper.exs.eex\": \"test/test_helper.exs\",\n     \"phx_test/controllers/error_json_test.exs.eex\":\n       \"test/:lib_web_name/controllers/error_json_test.exs\"},\n    {:keep, :web,\n     \"phx_web/controllers\": \"lib/:lib_web_name/controllers\",\n     \"phx_test/controllers\": \"test/:lib_web_name/controllers\"}\n  ])\n\n  template(:gettext, [\n    {:eex, :web,\n     \"phx_gettext/gettext.ex.eex\": \"lib/:lib_web_name/gettext.ex\",\n     \"phx_gettext/en/LC_MESSAGES/errors.po.eex\": \"priv/gettext/en/LC_MESSAGES/errors.po\",\n     \"phx_gettext/errors.pot.eex\": \"priv/gettext/errors.pot\"}\n  ])\n\n  template(:html, [\n    {:eex, :web,\n     \"phx_web/controllers/error_html.ex.eex\": \"lib/:lib_web_name/controllers/error_html.ex\",\n     \"phx_test/controllers/error_html_test.exs.eex\":\n       \"test/:lib_web_name/controllers/error_html_test.exs\",\n     \"phx_web/components/core_components.ex.eex\": \"lib/:lib_web_name/components/core_components.ex\",\n     \"phx_web/controllers/page_controller.ex.eex\": \"lib/:lib_web_name/controllers/page_controller.ex\",\n     \"phx_web/controllers/page_html.ex.eex\": \"lib/:lib_web_name/controllers/page_html.ex\",\n     \"phx_web/controllers/page_html/home.html.heex.eex\":\n       \"lib/:lib_web_name/controllers/page_html/home.html.heex\",\n     \"phx_test/controllers/page_controller_test.exs.eex\":\n       \"test/:lib_web_name/controllers/page_controller_test.exs\",\n     \"phx_web/components/layouts/root.html.heex.eex\":\n       \"lib/:lib_web_name/components/layouts/root.html.heex\",\n     \"phx_web/components/layouts.ex.eex\": \"lib/:lib_web_name/components/layouts.ex\"},\n    {:eex, :web, \"phx_assets/logo.svg.eex\": \"priv/static/images/logo.svg\"}\n  ])\n\n  template(:ecto, [\n    {:eex, :app,\n     \"phx_ecto/repo.ex.eex\": \"lib/:app/repo.ex\",\n     \"phx_ecto/formatter.exs.eex\": \"priv/repo/migrations/.formatter.exs\",\n     \"phx_ecto/seeds.exs.eex\": \"priv/repo/seeds.exs\",\n     \"phx_ecto/data_case.ex.eex\": \"test/support/data_case.ex\"},\n    {:keep, :app, \"phx_ecto/priv/repo/migrations\": \"priv/repo/migrations\"}\n  ])\n\n  template(:css, [\n    {:eex, :web,\n     \"phx_assets/app.css.eex\": \"assets/css/app.css\",\n     \"phx_assets/heroicons.js.eex\": \"assets/vendor/heroicons.js\",\n     \"phx_assets/daisyui.js.eex\": \"assets/vendor/daisyui.js\",\n     \"phx_assets/daisyui-theme.js.eex\": \"assets/vendor/daisyui-theme.js\"}\n  ])\n\n  template(:js, [\n    {:eex, :web,\n     \"phx_assets/app.js.eex\": \"assets/js/app.js\",\n     \"phx_assets/topbar.js.eex\": \"assets/vendor/topbar.js\",\n     \"phx_assets/tsconfig.json.eex\": \"assets/tsconfig.json\"}\n  ])\n\n  template(:no_js, [\n    {:text, :web, \"phx_static/app.js\": \"priv/static/assets/js/app.js\"}\n  ])\n\n  template(:no_css, [\n    {\n      :text,\n      :web,\n      # the default.css file can be re-created by using the recreate_default_css.exs file\n      # in the installer folder: `elixir installer/recreate_default_css.exs`\n      \"phx_static/app.css\": \"priv/static/assets/css/app.css\",\n      \"phx_static/default.css\": \"priv/static/assets/default.css\"\n    }\n  ])\n\n  template(:static, [\n    {:text, :web,\n     \"phx_static/robots.txt\": \"priv/static/robots.txt\",\n     \"phx_static/favicon.ico\": \"priv/static/favicon.ico\"}\n  ])\n\n  template(:mailer, [\n    {:eex, :app, \"phx_mailer/lib/app_name/mailer.ex.eex\": \"lib/:app/mailer.ex\"}\n  ])\n\n  def prepare_project(%Project{app: app, base_path: base_path} = project) when not is_nil(app) do\n    if in_umbrella?(base_path) do\n      %{project | in_umbrella?: true, project_path: Path.dirname(Path.dirname(base_path))}\n    else\n      %{project | in_umbrella?: false, project_path: base_path}\n    end\n    |> put_app()\n    |> put_root_app()\n    |> put_web_app()\n  end\n\n  defp put_app(%Project{base_path: base_path} = project) do\n    %{project | app_path: base_path}\n  end\n\n  defp put_root_app(%Project{app: app, opts: opts} = project) do\n    %{\n      project\n      | root_app: app,\n        root_mod: Module.concat([opts[:module] || Macro.camelize(app)])\n    }\n  end\n\n  defp put_web_app(%Project{app: app} = project) do\n    %{\n      project\n      | web_app: app,\n        lib_web_name: \"#{app}_web\",\n        web_namespace: Module.concat([\"#{project.root_mod}Web\"]),\n        web_path: project.base_path\n    }\n  end\n\n  def generate(%Project{} = project) do\n    copy_from(project, __MODULE__, :new)\n\n    generate_agents_md(project)\n\n    if Project.ecto?(project), do: gen_ecto(project)\n    if Project.html?(project), do: gen_html(project)\n    if Project.mailer?(project), do: gen_mailer(project)\n    if Project.gettext?(project), do: gen_gettext(project)\n\n    gen_assets(project)\n    project\n  end\n\n  def gen_html(project) do\n    copy_from(project, __MODULE__, :html)\n  end\n\n  def gen_gettext(project) do\n    copy_from(project, __MODULE__, :gettext)\n  end\n\n  def gen_ecto(project) do\n    copy_from(project, __MODULE__, :ecto)\n    gen_ecto_config(project)\n  end\n\n  def gen_assets(%Project{} = project) do\n    javascript? = Project.javascript?(project)\n    css? = Project.css?(project)\n    html? = Project.html?(project)\n\n    copy_from(project, __MODULE__, :static)\n\n    if html? or javascript? do\n      command = if javascript?, do: :js, else: :no_js\n      copy_from(project, __MODULE__, command)\n    end\n\n    if html? or css? do\n      command = if css?, do: :css, else: :no_css\n      copy_from(project, __MODULE__, command)\n    end\n  end\n\n  def gen_mailer(%Project{} = project) do\n    copy_from(project, __MODULE__, :mailer)\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/umbrella.ex",
    "content": "defmodule Phx.New.Umbrella do\n  @moduledoc false\n  use Phx.New.Generator\n  alias Phx.New.{Ecto, Web, Project, Mailer}\n\n  template(:new, [\n    {:eex, :project,\n     \"phx_umbrella/gitignore.eex\": \".gitignore\",\n     \"phx_umbrella/config/config.exs.eex\": \"config/config.exs\",\n     \"phx_umbrella/config/dev.exs.eex\": \"config/dev.exs\",\n     \"phx_umbrella/config/test.exs.eex\": \"config/test.exs\",\n     \"phx_umbrella/config/prod.exs.eex\": \"config/prod.exs\",\n     \"phx_umbrella/config/runtime.exs.eex\": \"config/runtime.exs\",\n     \"phx_umbrella/mix.exs.eex\": \"mix.exs\",\n     \"phx_umbrella/README.md.eex\": \"README.md\",\n     \"phx_umbrella/formatter.exs.eex\": \".formatter.exs\"},\n    {:config, :project, \"phx_umbrella/config/extra_config.exs.eex\": \"config/config.exs\"}\n  ])\n\n  def prepare_project(%Project{app: app} = project) when not is_nil(app) do\n    project\n    |> put_app()\n    |> put_web()\n    |> put_root_app()\n  end\n\n  defp put_app(project) do\n    project_path = Path.expand(project.base_path <> \"_umbrella\")\n    app_path = Path.join(project_path, \"apps/#{project.app}\")\n\n    %{project | in_umbrella?: true, app_path: app_path, project_path: project_path}\n  end\n\n  def put_web(%Project{app: app, opts: opts} = project) do\n    web_app = :\"#{app}_web\"\n    web_namespace = Module.concat([opts[:web_module] || \"#{project.app_mod}Web\"])\n\n    %{\n      project\n      | web_app: web_app,\n        lib_web_name: web_app,\n        web_namespace: web_namespace,\n        generators: [context_app: :\"#{app}\"],\n        web_path: Path.join(project.project_path, \"apps/#{web_app}/\")\n    }\n  end\n\n  defp put_root_app(%Project{app: app} = project) do\n    %{\n      project\n      | root_app: :\"#{app}_umbrella\",\n        root_mod: Module.concat(project.app_mod, \"Umbrella\")\n    }\n  end\n\n  def generate(%Project{} = project) do\n    if in_umbrella?(project.project_path) do\n      Mix.raise(\"Unable to nest umbrella project within apps\")\n    end\n\n    copy_from(project, __MODULE__, :new)\n\n    generate_agents_md(project)\n\n    project\n    |> Web.generate()\n    |> Ecto.generate()\n    |> maybe_generate_mailer()\n  end\n\n  defp maybe_generate_mailer(project) do\n    if Project.mailer?(project) do\n      Mailer.generate(project)\n    else\n      project\n    end\n  end\nend\n"
  },
  {
    "path": "installer/lib/phx_new/web.ex",
    "content": "defmodule Phx.New.Web do\n  @moduledoc false\n  use Phx.New.Generator\n  alias Phx.New.{Project}\n\n  @pre \"phx_umbrella/apps/app_name_web\"\n\n  template(:new, [\n    {:prod_config, :project, \"#{@pre}/config/runtime.exs.eex\": \"config/runtime.exs\"},\n    {:config, :project,\n     \"#{@pre}/config/config.exs.eex\": \"config/config.exs\",\n     \"#{@pre}/config/dev.exs.eex\": \"config/dev.exs\",\n     \"#{@pre}/config/prod.exs.eex\": \"config/prod.exs\",\n     \"#{@pre}/config/test.exs.eex\": \"config/test.exs\"},\n    {:keep, :web,\n     \"phx_web/controllers\": \"lib/:web_app/controllers\",\n     \"phx_test/channels\": \"test/:web_app/channels\",\n     \"phx_test/controllers\": \"test/:web_app/controllers\"},\n    {:eex, :web,\n     \"#{@pre}/lib/app_name.ex.eex\": \"lib/:web_app.ex\",\n     \"#{@pre}/lib/app_name/application.ex.eex\": \"lib/:web_app/application.ex\",\n     \"phx_web/endpoint.ex.eex\": \"lib/:web_app/endpoint.ex\",\n     \"phx_web/router.ex.eex\": \"lib/:web_app/router.ex\",\n     \"phx_web/telemetry.ex.eex\": \"lib/:web_app/telemetry.ex\",\n     \"phx_web/controllers/error_json.ex.eex\": \"lib/:web_app/controllers/error_json.ex\",\n     \"#{@pre}/mix.exs.eex\": \"mix.exs\",\n     \"#{@pre}/README.md.eex\": \"README.md\",\n     \"#{@pre}/gitignore.eex\": \".gitignore\",\n     \"#{@pre}/test/test_helper.exs.eex\": \"test/test_helper.exs\",\n     \"phx_test/support/conn_case.ex.eex\": \"test/support/conn_case.ex\",\n     \"phx_test/controllers/error_json_test.exs.eex\": \"test/:web_app/controllers/error_json_test.exs\",\n     \"#{@pre}/formatter.exs.eex\": \".formatter.exs\"}\n  ])\n\n  template(:gettext, [\n    {:eex, :web,\n     \"phx_gettext/gettext.ex.eex\": \"lib/:web_app/gettext.ex\",\n     \"phx_gettext/en/LC_MESSAGES/errors.po.eex\": \"priv/gettext/en/LC_MESSAGES/errors.po\",\n     \"phx_gettext/errors.pot.eex\": \"priv/gettext/errors.pot\"}\n  ])\n\n  template(:html, [\n    {:eex, :web,\n     \"phx_web/components/core_components.ex.eex\": \"lib/:web_app/components/core_components.ex\",\n     \"phx_web/components/layouts.ex.eex\": \"lib/:web_app/components/layouts.ex\",\n     \"phx_web/controllers/page_controller.ex.eex\": \"lib/:web_app/controllers/page_controller.ex\",\n     \"phx_web/controllers/error_html.ex.eex\": \"lib/:web_app/controllers/error_html.ex\",\n     \"phx_web/controllers/page_html.ex.eex\": \"lib/:web_app/controllers/page_html.ex\",\n     \"phx_web/controllers/page_html/home.html.heex.eex\":\n       \"lib/:web_app/controllers/page_html/home.html.heex\",\n     \"phx_test/controllers/page_controller_test.exs.eex\":\n       \"test/:web_app/controllers/page_controller_test.exs\",\n     \"phx_test/controllers/error_html_test.exs.eex\": \"test/:web_app/controllers/error_html_test.exs\",\n     \"phx_assets/topbar.js.eex\": \"assets/vendor/topbar.js\",\n     \"phx_web/components/layouts/root.html.heex.eex\": \"lib/:web_app/components/layouts/root.html.heex\"},\n    {:eex, :web, \"phx_assets/logo.svg.eex\": \"priv/static/images/logo.svg\"}\n  ])\n\n  def prepare_project(%Project{app: app} = project) when not is_nil(app) do\n    web_path = Path.expand(project.base_path)\n    project_path = Path.dirname(Path.dirname(web_path))\n\n    %{\n      project\n      | in_umbrella?: true,\n        project_path: project_path,\n        web_path: web_path,\n        web_app: app,\n        generators: [context_app: false],\n        web_namespace: project.app_mod\n    }\n  end\n\n  def generate(%Project{} = project) do\n    inject_umbrella_config_defaults(project)\n    copy_from(project, __MODULE__, :new)\n\n    if Project.html?(project), do: gen_html(project)\n    if Project.gettext?(project), do: gen_gettext(project)\n\n    Phx.New.Single.gen_assets(project)\n    project\n  end\n\n  defp gen_html(%Project{} = project) do\n    copy_from(project, __MODULE__, :html)\n  end\n\n  defp gen_gettext(%Project{} = project) do\n    copy_from(project, __MODULE__, :gettext)\n  end\nend\n"
  },
  {
    "path": "installer/mix.exs",
    "content": "for path <- :code.get_path(),\n    Regex.match?(~r/phx_new-[\\w\\.\\-]+\\/ebin$/, List.to_string(path)) do\n  Code.delete_path(path)\nend\n\ndefmodule Phx.New.MixProject do\n  use Mix.Project\n\n  @version \"1.8.5\"\n  @scm_url \"https://github.com/phoenixframework/phoenix\"\n\n  # If the elixir requirement is updated, we need to update:\n  #\n  #   1. all mix.exs generated by the installer\n  #   2. guides/introduction/installation.md\n  #   3. guides/deployment/releases.md\n  #   4. test/test_helper.exs at the root\n  #   5. installer/lib/mix/tasks/phx.new.ex\n  #\n  @elixir_requirement \"~> 1.15\"\n\n  def project do\n    [\n      app: :phx_new,\n      start_permanent: Mix.env() == :prod,\n      version: @version,\n      elixir: @elixir_requirement,\n      deps: deps(),\n      aliases: aliases(),\n      package: [\n        maintainers: [\n          \"Chris McCord\",\n          \"José Valim\",\n          \"Gary Rennie\",\n          \"Jason Stiebs\"\n        ],\n        licenses: [\"MIT\"],\n        links: %{\"GitHub\" => @scm_url},\n        files: ~w(lib templates mix.exs README.md)\n      ],\n      source_url: @scm_url,\n      docs: docs(),\n      homepage_url: \"https://www.phoenixframework.org\",\n      description: \"\"\"\n      Phoenix framework project generator.\n\n      Provides a `mix phx.new` task to bootstrap a new Elixir application\n      with Phoenix dependencies.\n      \"\"\"\n    ]\n  end\n\n  def cli do\n    [preferred_envs: [docs: :docs]]\n  end\n\n  def application do\n    [\n      extra_applications: [:eex, :crypto, :public_key]\n    ]\n  end\n\n  def deps do\n    [\n      {:ex_doc, \"~> 0.24\", only: :docs}\n    ]\n  end\n\n  defp docs do\n    [\n      source_url_pattern: \"#{@scm_url}/blob/v#{@version}/installer/%{path}#L%{line}\"\n    ]\n  end\n\n  defp aliases do\n    [\n      \"hex.publish\": [\n        &copy_agents_md/1,\n        \"hex.publish\"\n      ]\n    ]\n  end\n\n  defp copy_agents_md(_) do\n    File.cp_r!(\n      Path.expand(\"../usage-rules\", __DIR__),\n      Path.expand(\"./templates/phoenix-usage-rules\", __DIR__)\n    )\n  end\nend\n"
  },
  {
    "path": "installer/recreate_default_css.exs",
    "content": "File.rm_rf!(\"installer/dayzee\")\n\nshell! = fn command, opts ->\n  {_, 0} =\n    System.shell(\n      command,\n      Keyword.merge(\n        [\n          into: IO.binstream(:stdio, :line),\n          stderr_to_stdout: true\n        ],\n        opts\n      )\n    )\nend\n\nshell_in_daisy! = fn command -> shell!.(command, cd: Path.expand(\"dayzee\")) end\n\nFile.cd!(\"installer\", fn ->\n  shell!.(\"mix phx.new dayzee --dev --database sqlite3 --install\", [])\n\n  shell_in_daisy!.(\"mix phx.gen.auth Accounts User users --live\")\n  shell_in_daisy!.(\"mix deps.get\")\n  shell_in_daisy!.(\"mix phx.gen.live Blog Post posts title:string body:text\")\n  shell_in_daisy!.(\"mix tailwind dayzee\")\n\n  content = File.read!(\"dayzee/priv/static/assets/css/app.css\")\n\n  File.write!(\"templates/phx_static/default.css\", \"\"\"\n  /* These are daisyUI styles for styling the default CoreComponents and generator files\n   * included to prevent shipping a completely unstyled page, even as you selected --no-tailwind.\n   * You can safely remove the whole file and all references to \"default.css\".\n   */\n  #{content}\n  \"\"\")\nend)\n\nFile.rm_rf!(\"installer/dayzee\")\n"
  },
  {
    "path": "installer/templates/phx_assets/app.css.eex",
    "content": "/* See the Tailwind configuration guide for advanced usage\n   https://tailwindcss.com/docs/configuration */\n\n@import \"tailwindcss\" source(none);\n@source \"../css\";\n@source \"../js\";\n@source \"../../lib/<%= @lib_web_name || @app_name %>\";\n\n/* A Tailwind plugin that makes \"hero-#{ICON}\" classes available.\n   The heroicons installation itself is managed by your mix.exs */\n@plugin \"../vendor/heroicons\";\n\n/* daisyUI Tailwind Plugin. You can update this file by fetching the latest version with:\n   curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui.js\n   Make sure to look at the daisyUI changelog: https://daisyui.com/docs/changelog/ */\n@plugin \"../vendor/daisyui\" {\n  themes: false;\n}\n\n/* daisyUI theme plugin. You can update this file by fetching the latest version with:\n  curl -sLO https://github.com/saadeghi/daisyui/releases/latest/download/daisyui-theme.js\n  We ship with two themes, a light one inspired on Phoenix colors and a dark one inspired\n  on Elixir colors. Build your own at: https://daisyui.com/theme-generator/ */\n@plugin \"../vendor/daisyui-theme\" {\n  name: \"dark\";\n  default: false;\n  prefersdark: true;\n  color-scheme: \"dark\";\n  --color-base-100: oklch(30.33% 0.016 252.42);\n  --color-base-200: oklch(25.26% 0.014 253.1);\n  --color-base-300: oklch(20.15% 0.012 254.09);\n  --color-base-content: oklch(97.807% 0.029 256.847);\n  --color-primary: oklch(58% 0.233 277.117);\n  --color-primary-content: oklch(96% 0.018 272.314);\n  --color-secondary: oklch(58% 0.233 277.117);\n  --color-secondary-content: oklch(96% 0.018 272.314);\n  --color-accent: oklch(60% 0.25 292.717);\n  --color-accent-content: oklch(96% 0.016 293.756);\n  --color-neutral: oklch(37% 0.044 257.287);\n  --color-neutral-content: oklch(98% 0.003 247.858);\n  --color-info: oklch(58% 0.158 241.966);\n  --color-info-content: oklch(97% 0.013 236.62);\n  --color-success: oklch(60% 0.118 184.704);\n  --color-success-content: oklch(98% 0.014 180.72);\n  --color-warning: oklch(66% 0.179 58.318);\n  --color-warning-content: oklch(98% 0.022 95.277);\n  --color-error: oklch(58% 0.253 17.585);\n  --color-error-content: oklch(96% 0.015 12.422);\n  --radius-selector: 0.25rem;\n  --radius-field: 0.25rem;\n  --radius-box: 0.5rem;\n  --size-selector: 0.21875rem;\n  --size-field: 0.21875rem;\n  --border: 1.5px;\n  --depth: 1;\n  --noise: 0;\n}\n\n@plugin \"../vendor/daisyui-theme\" {\n  name: \"light\";\n  default: true;\n  prefersdark: false;\n  color-scheme: \"light\";\n  --color-base-100: oklch(98% 0 0);\n  --color-base-200: oklch(96% 0.001 286.375);\n  --color-base-300: oklch(92% 0.004 286.32);\n  --color-base-content: oklch(21% 0.006 285.885);\n  --color-primary: oklch(70% 0.213 47.604);\n  --color-primary-content: oklch(98% 0.016 73.684);\n  --color-secondary: oklch(55% 0.027 264.364);\n  --color-secondary-content: oklch(98% 0.002 247.839);\n  --color-accent: oklch(0% 0 0);\n  --color-accent-content: oklch(100% 0 0);\n  --color-neutral: oklch(44% 0.017 285.786);\n  --color-neutral-content: oklch(98% 0 0);\n  --color-info: oklch(62% 0.214 259.815);\n  --color-info-content: oklch(97% 0.014 254.604);\n  --color-success: oklch(70% 0.14 182.503);\n  --color-success-content: oklch(98% 0.014 180.72);\n  --color-warning: oklch(66% 0.179 58.318);\n  --color-warning-content: oklch(98% 0.022 95.277);\n  --color-error: oklch(58% 0.253 17.585);\n  --color-error-content: oklch(96% 0.015 12.422);\n  --radius-selector: 0.25rem;\n  --radius-field: 0.25rem;\n  --radius-box: 0.5rem;\n  --size-selector: 0.21875rem;\n  --size-field: 0.21875rem;\n  --border: 1.5px;\n  --depth: 1;\n  --noise: 0;\n}\n\n/* Add variants based on LiveView classes */\n@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);\n@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);\n@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);\n\n/* Use the data attribute for dark mode  */\n@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));\n\n/* Make LiveView wrapper divs transparent for layout */\n[data-phx-session], [data-phx-teleported-src] { display: contents }\n\n/* This file is for your main application CSS */\n"
  },
  {
    "path": "installer/templates/phx_assets/app.js.eex",
    "content": "// If you want to use Phoenix channels, run `mix help phx.gen.channel`\n// to get started and then uncomment the line below.\n// import \"./user_socket.js\"\n\n// You can include dependencies in two ways.\n//\n// The simplest option is to put them in assets/vendor and\n// import them using relative paths:\n//\n//     import \"../vendor/some-package.js\"\n//\n// Alternatively, you can `npm install some-package --prefix assets` and import\n// them using a path starting with the package name:\n//\n//     import \"some-package\"\n//\n// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.\n// To load it, simply add a second `<link>` to your `root.html.heex` file.\n<%= if @html do %>\n// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.\nimport \"phoenix_html\"\n// Establish Phoenix Socket and LiveView configuration.\n<%= @live_comment %>import {Socket} from \"<%= @phoenix_js_path %>\"\n<%= @live_comment %>import {LiveSocket} from \"phoenix_live_view\"\n<%= @live_comment %>import {hooks as colocatedHooks} from \"phoenix-colocated/<%= @web_app_name %>\"\n<%= @live_comment %>import topbar from \"../vendor/topbar\"\n\n<%= @live_comment %>const csrfToken = document.querySelector(\"meta[name='csrf-token']\").getAttribute(\"content\")\n<%= @live_comment %>const liveSocket = new LiveSocket(\"/live\", Socket, {\n<%= @live_comment %>  longPollFallbackMs: 2500,\n<%= @live_comment %>  params: {_csrf_token: csrfToken},\n<%= @live_comment %>  hooks: {...colocatedHooks},\n<%= @live_comment %>})\n\n// Show progress bar on live navigation and form submits\n<%= @live_comment %>topbar.config({barColors: {0: \"#29d\"}, shadowColor: \"rgba(0, 0, 0, .3)\"})\n<%= @live_comment %>window.addEventListener(\"phx:page-loading-start\", _info => topbar.show(300))\n<%= @live_comment %>window.addEventListener(\"phx:page-loading-stop\", _info => topbar.hide())\n\n// connect if there are any LiveViews on the page\n<%= @live_comment %>liveSocket.connect()\n\n// expose liveSocket on window for web console debug logs and latency simulation:\n// >> liveSocket.enableDebug()\n// >> liveSocket.enableLatencySim(1000)  // enabled for duration of browser session\n// >> liveSocket.disableLatencySim()\n<%= @live_comment %>window.liveSocket = liveSocket\n\n// The lines below enable quality of life phoenix_live_reload\n// development features:\n//\n//     1. stream server logs to the browser console\n//     2. click on elements to jump to their definitions in your code editor\n//\n<%= @live_comment %>if (process.env.NODE_ENV === \"development\") {\n<%= @live_comment %>  window.addEventListener(\"phx:live_reload:attached\", ({detail: reloader}) => {\n<%= @live_comment %>    // Enable server log streaming to client.\n<%= @live_comment %>    // Disable with reloader.disableServerLogs()\n<%= @live_comment %>    reloader.enableServerLogs()\n<%= @live_comment %>\n<%= @live_comment %>    // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component\n<%= @live_comment %>    //\n<%= @live_comment %>    //   * click with \"c\" key pressed to open at caller location\n<%= @live_comment %>    //   * click with \"d\" key pressed to open at function component definition location\n<%= @live_comment %>    let keyDown\n<%= @live_comment %>    window.addEventListener(\"keydown\", e => keyDown = e.key)\n<%= @live_comment %>    window.addEventListener(\"keyup\", _e => keyDown = null)\n<%= @live_comment %>    window.addEventListener(\"click\", e => {\n<%= @live_comment %>      if(keyDown === \"c\"){\n<%= @live_comment %>        e.preventDefault()\n<%= @live_comment %>        e.stopImmediatePropagation()\n<%= @live_comment %>        reloader.openEditorAtCaller(e.target)\n<%= @live_comment %>      } else if(keyDown === \"d\"){\n<%= @live_comment %>        e.preventDefault()\n<%= @live_comment %>        e.stopImmediatePropagation()\n<%= @live_comment %>        reloader.openEditorAtDef(e.target)\n<%= @live_comment %>      }\n<%= @live_comment %>    }, true)\n<%= @live_comment %>\n<%= @live_comment %>    window.liveReloader = reloader\n<%= @live_comment %>  })\n<%= @live_comment %>}\n\n<%= if not @live do %>\n// Handle flash close\ndocument.querySelectorAll(\"[role=alert][data-flash]\").forEach((el) => {\n  el.addEventListener(\"click\", () => {\n    el.setAttribute(\"hidden\", \"\")\n  })\n})<% end %><% end %>"
  },
  {
    "path": "installer/templates/phx_assets/daisyui-theme.js.eex",
    "content": "/** 🌼\n *  @license MIT\n *  daisyUI bundle\n *  https://daisyui.com/\n */\n\nvar __defProp = Object.defineProperty;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __moduleCache = /* @__PURE__ */ new WeakMap;\nvar __toCommonJS = (from) => {\n  var entry = __moduleCache.get(from), desc;\n  if (entry)\n    return entry;\n  entry = __defProp({}, \"__esModule\", { value: true });\n  if (from && typeof from === \"object\" || typeof from === \"function\")\n    __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {\n      get: () => from[key],\n      enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable\n    }));\n  __moduleCache.set(from, entry);\n  return entry;\n};\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, {\n      get: all[name],\n      enumerable: true,\n      configurable: true,\n      set: (newValue) => all[name] = () => newValue\n    });\n};\n\n// packages/daisyui/theme/index.js\nvar exports_theme = {};\n__export(exports_theme, {\n  default: () => theme_default\n});\nmodule.exports = __toCommonJS(exports_theme);\n\n// packages/daisyui/functions/plugin.js\nvar plugin = {\n  withOptions: (pluginFunction, configFunction = () => ({})) => {\n    const optionsFunction = (options) => {\n      const handler = pluginFunction(options);\n      const config = configFunction(options);\n      return { handler, config };\n    };\n    optionsFunction.__isOptionsFunction = true;\n    return optionsFunction;\n  }\n};\n\n// packages/daisyui/theme/object.js\nvar object_default = { cyberpunk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(94.51% 0.179 104.32)\", \"--color-base-200\": \"oklch(91.51% 0.179 104.32)\", \"--color-base-300\": \"oklch(85.51% 0.179 104.32)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(74.22% 0.209 6.35)\", \"--color-primary-content\": \"oklch(14.844% 0.041 6.35)\", \"--color-secondary\": \"oklch(83.33% 0.184 204.72)\", \"--color-secondary-content\": \"oklch(16.666% 0.036 204.72)\", \"--color-accent\": \"oklch(71.86% 0.217 310.43)\", \"--color-accent-content\": \"oklch(14.372% 0.043 310.43)\", \"--color-neutral\": \"oklch(23.04% 0.065 269.31)\", \"--color-neutral-content\": \"oklch(94.51% 0.179 104.32)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0rem\", \"--radius-box\": \"0rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, acid: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98% 0 0)\", \"--color-base-200\": \"oklch(95% 0 0)\", \"--color-base-300\": \"oklch(91% 0 0)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(71.9% 0.357 330.759)\", \"--color-primary-content\": \"oklch(14.38% 0.071 330.759)\", \"--color-secondary\": \"oklch(73.37% 0.224 48.25)\", \"--color-secondary-content\": \"oklch(14.674% 0.044 48.25)\", \"--color-accent\": \"oklch(92.78% 0.264 122.962)\", \"--color-accent-content\": \"oklch(18.556% 0.052 122.962)\", \"--color-neutral\": \"oklch(21.31% 0.128 278.68)\", \"--color-neutral-content\": \"oklch(84.262% 0.025 278.68)\", \"--color-info\": \"oklch(60.72% 0.227 252.05)\", \"--color-info-content\": \"oklch(12.144% 0.045 252.05)\", \"--color-success\": \"oklch(85.72% 0.266 158.53)\", \"--color-success-content\": \"oklch(17.144% 0.053 158.53)\", \"--color-warning\": \"oklch(91.01% 0.212 100.5)\", \"--color-warning-content\": \"oklch(18.202% 0.042 100.5)\", \"--color-error\": \"oklch(64.84% 0.293 29.349)\", \"--color-error-content\": \"oklch(12.968% 0.058 29.349)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"1rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, black: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(0% 0 0)\", \"--color-base-200\": \"oklch(19% 0 0)\", \"--color-base-300\": \"oklch(22% 0 0)\", \"--color-base-content\": \"oklch(87.609% 0 0)\", \"--color-primary\": \"oklch(35% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(35% 0 0)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(35% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(35% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(45.201% 0.313 264.052)\", \"--color-info-content\": \"oklch(89.04% 0.062 264.052)\", \"--color-success\": \"oklch(51.975% 0.176 142.495)\", \"--color-success-content\": \"oklch(90.395% 0.035 142.495)\", \"--color-warning\": \"oklch(96.798% 0.211 109.769)\", \"--color-warning-content\": \"oklch(19.359% 0.042 109.769)\", \"--color-error\": \"oklch(62.795% 0.257 29.233)\", \"--color-error-content\": \"oklch(12.559% 0.051 29.233)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0rem\", \"--radius-box\": \"0rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, dark: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(25.33% 0.016 252.42)\", \"--color-base-200\": \"oklch(23.26% 0.014 253.1)\", \"--color-base-300\": \"oklch(21.15% 0.012 254.09)\", \"--color-base-content\": \"oklch(97.807% 0.029 256.847)\", \"--color-primary\": \"oklch(58% 0.233 277.117)\", \"--color-primary-content\": \"oklch(96% 0.018 272.314)\", \"--color-secondary\": \"oklch(65% 0.241 354.308)\", \"--color-secondary-content\": \"oklch(94% 0.028 342.258)\", \"--color-accent\": \"oklch(77% 0.152 181.912)\", \"--color-accent-content\": \"oklch(38% 0.063 188.416)\", \"--color-neutral\": \"oklch(14% 0.005 285.823)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(71% 0.194 13.428)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"0.5rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, light: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(98% 0 0)\", \"--color-base-300\": \"oklch(95% 0 0)\", \"--color-base-content\": \"oklch(21% 0.006 285.885)\", \"--color-primary\": \"oklch(45% 0.24 277.023)\", \"--color-primary-content\": \"oklch(93% 0.034 272.788)\", \"--color-secondary\": \"oklch(65% 0.241 354.308)\", \"--color-secondary-content\": \"oklch(94% 0.028 342.258)\", \"--color-accent\": \"oklch(77% 0.152 181.912)\", \"--color-accent-content\": \"oklch(38% 0.063 188.416)\", \"--color-neutral\": \"oklch(14% 0.005 285.823)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(71% 0.194 13.428)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"0.5rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, luxury: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(14.076% 0.004 285.822)\", \"--color-base-200\": \"oklch(20.219% 0.004 308.229)\", \"--color-base-300\": \"oklch(23.219% 0.004 308.229)\", \"--color-base-content\": \"oklch(75.687% 0.123 76.89)\", \"--color-primary\": \"oklch(100% 0 0)\", \"--color-primary-content\": \"oklch(20% 0 0)\", \"--color-secondary\": \"oklch(27.581% 0.064 261.069)\", \"--color-secondary-content\": \"oklch(85.516% 0.012 261.069)\", \"--color-accent\": \"oklch(36.674% 0.051 338.825)\", \"--color-accent-content\": \"oklch(87.334% 0.01 338.825)\", \"--color-neutral\": \"oklch(24.27% 0.057 59.825)\", \"--color-neutral-content\": \"oklch(93.203% 0.089 90.861)\", \"--color-info\": \"oklch(79.061% 0.121 237.133)\", \"--color-info-content\": \"oklch(15.812% 0.024 237.133)\", \"--color-success\": \"oklch(78.119% 0.192 132.154)\", \"--color-success-content\": \"oklch(15.623% 0.038 132.154)\", \"--color-warning\": \"oklch(86.127% 0.136 102.891)\", \"--color-warning-content\": \"oklch(17.225% 0.027 102.891)\", \"--color-error\": \"oklch(71.753% 0.176 22.568)\", \"--color-error-content\": \"oklch(14.35% 0.035 22.568)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, dracula: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(28.822% 0.022 277.508)\", \"--color-base-200\": \"oklch(26.805% 0.02 277.508)\", \"--color-base-300\": \"oklch(24.787% 0.019 277.508)\", \"--color-base-content\": \"oklch(97.747% 0.007 106.545)\", \"--color-primary\": \"oklch(75.461% 0.183 346.812)\", \"--color-primary-content\": \"oklch(15.092% 0.036 346.812)\", \"--color-secondary\": \"oklch(74.202% 0.148 301.883)\", \"--color-secondary-content\": \"oklch(14.84% 0.029 301.883)\", \"--color-accent\": \"oklch(83.392% 0.124 66.558)\", \"--color-accent-content\": \"oklch(16.678% 0.024 66.558)\", \"--color-neutral\": \"oklch(39.445% 0.032 275.524)\", \"--color-neutral-content\": \"oklch(87.889% 0.006 275.524)\", \"--color-info\": \"oklch(88.263% 0.093 212.846)\", \"--color-info-content\": \"oklch(17.652% 0.018 212.846)\", \"--color-success\": \"oklch(87.099% 0.219 148.024)\", \"--color-success-content\": \"oklch(17.419% 0.043 148.024)\", \"--color-warning\": \"oklch(95.533% 0.134 112.757)\", \"--color-warning-content\": \"oklch(19.106% 0.026 112.757)\", \"--color-error\": \"oklch(68.22% 0.206 24.43)\", \"--color-error-content\": \"oklch(13.644% 0.041 24.43)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, retro: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(91.637% 0.034 90.515)\", \"--color-base-200\": \"oklch(88.272% 0.049 91.774)\", \"--color-base-300\": \"oklch(84.133% 0.065 90.856)\", \"--color-base-content\": \"oklch(41% 0.112 45.904)\", \"--color-primary\": \"oklch(80% 0.114 19.571)\", \"--color-primary-content\": \"oklch(39% 0.141 25.723)\", \"--color-secondary\": \"oklch(92% 0.084 155.995)\", \"--color-secondary-content\": \"oklch(44% 0.119 151.328)\", \"--color-accent\": \"oklch(68% 0.162 75.834)\", \"--color-accent-content\": \"oklch(41% 0.112 45.904)\", \"--color-neutral\": \"oklch(44% 0.011 73.639)\", \"--color-neutral-content\": \"oklch(86% 0.005 56.366)\", \"--color-info\": \"oklch(58% 0.158 241.966)\", \"--color-info-content\": \"oklch(96% 0.059 95.617)\", \"--color-success\": \"oklch(51% 0.096 186.391)\", \"--color-success-content\": \"oklch(96% 0.059 95.617)\", \"--color-warning\": \"oklch(64% 0.222 41.116)\", \"--color-warning-content\": \"oklch(96% 0.059 95.617)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(40% 0.123 38.172)\", \"--radius-selector\": \"0.25rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, lofi: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(94% 0 0)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(15.906% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(21.455% 0.001 17.278)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(26.861% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(0% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(79.54% 0.103 205.9)\", \"--color-info-content\": \"oklch(15.908% 0.02 205.9)\", \"--color-success\": \"oklch(90.13% 0.153 164.14)\", \"--color-success-content\": \"oklch(18.026% 0.03 164.14)\", \"--color-warning\": \"oklch(88.37% 0.135 79.94)\", \"--color-warning-content\": \"oklch(17.674% 0.027 79.94)\", \"--color-error\": \"oklch(78.66% 0.15 28.47)\", \"--color-error-content\": \"oklch(15.732% 0.03 28.47)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, valentine: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97% 0.014 343.198)\", \"--color-base-200\": \"oklch(94% 0.028 342.258)\", \"--color-base-300\": \"oklch(89% 0.061 343.231)\", \"--color-base-content\": \"oklch(52% 0.223 3.958)\", \"--color-primary\": \"oklch(65% 0.241 354.308)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(62% 0.265 303.9)\", \"--color-secondary-content\": \"oklch(97% 0.014 308.299)\", \"--color-accent\": \"oklch(82% 0.111 230.318)\", \"--color-accent-content\": \"oklch(39% 0.09 240.876)\", \"--color-neutral\": \"oklch(40% 0.153 2.432)\", \"--color-neutral-content\": \"oklch(89% 0.061 343.231)\", \"--color-info\": \"oklch(86% 0.127 207.078)\", \"--color-info-content\": \"oklch(44% 0.11 240.79)\", \"--color-success\": \"oklch(84% 0.143 164.978)\", \"--color-success-content\": \"oklch(43% 0.095 166.913)\", \"--color-warning\": \"oklch(75% 0.183 55.934)\", \"--color-warning-content\": \"oklch(26% 0.079 36.259)\", \"--color-error\": \"oklch(63% 0.237 25.331)\", \"--color-error-content\": \"oklch(97% 0.013 17.38)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, nord: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(95.127% 0.007 260.731)\", \"--color-base-200\": \"oklch(93.299% 0.01 261.788)\", \"--color-base-300\": \"oklch(89.925% 0.016 262.749)\", \"--color-base-content\": \"oklch(32.437% 0.022 264.182)\", \"--color-primary\": \"oklch(59.435% 0.077 254.027)\", \"--color-primary-content\": \"oklch(11.887% 0.015 254.027)\", \"--color-secondary\": \"oklch(69.651% 0.059 248.687)\", \"--color-secondary-content\": \"oklch(13.93% 0.011 248.687)\", \"--color-accent\": \"oklch(77.464% 0.062 217.469)\", \"--color-accent-content\": \"oklch(15.492% 0.012 217.469)\", \"--color-neutral\": \"oklch(45.229% 0.035 264.131)\", \"--color-neutral-content\": \"oklch(89.925% 0.016 262.749)\", \"--color-info\": \"oklch(69.207% 0.062 332.664)\", \"--color-info-content\": \"oklch(13.841% 0.012 332.664)\", \"--color-success\": \"oklch(76.827% 0.074 131.063)\", \"--color-success-content\": \"oklch(15.365% 0.014 131.063)\", \"--color-warning\": \"oklch(85.486% 0.089 84.093)\", \"--color-warning-content\": \"oklch(17.097% 0.017 84.093)\", \"--color-error\": \"oklch(60.61% 0.12 15.341)\", \"--color-error-content\": \"oklch(12.122% 0.024 15.341)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, lemonade: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98.71% 0.02 123.72)\", \"--color-base-200\": \"oklch(91.8% 0.018 123.72)\", \"--color-base-300\": \"oklch(84.89% 0.017 123.72)\", \"--color-base-content\": \"oklch(19.742% 0.004 123.72)\", \"--color-primary\": \"oklch(58.92% 0.199 134.6)\", \"--color-primary-content\": \"oklch(11.784% 0.039 134.6)\", \"--color-secondary\": \"oklch(77.75% 0.196 111.09)\", \"--color-secondary-content\": \"oklch(15.55% 0.039 111.09)\", \"--color-accent\": \"oklch(85.39% 0.201 100.73)\", \"--color-accent-content\": \"oklch(17.078% 0.04 100.73)\", \"--color-neutral\": \"oklch(30.98% 0.075 108.6)\", \"--color-neutral-content\": \"oklch(86.196% 0.015 108.6)\", \"--color-info\": \"oklch(86.19% 0.047 224.14)\", \"--color-info-content\": \"oklch(17.238% 0.009 224.14)\", \"--color-success\": \"oklch(86.19% 0.047 157.85)\", \"--color-success-content\": \"oklch(17.238% 0.009 157.85)\", \"--color-warning\": \"oklch(86.19% 0.047 102.15)\", \"--color-warning-content\": \"oklch(17.238% 0.009 102.15)\", \"--color-error\": \"oklch(86.19% 0.047 25.85)\", \"--color-error-content\": \"oklch(17.238% 0.009 25.85)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, garden: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(92.951% 0.002 17.197)\", \"--color-base-200\": \"oklch(86.445% 0.002 17.197)\", \"--color-base-300\": \"oklch(79.938% 0.001 17.197)\", \"--color-base-content\": \"oklch(16.961% 0.001 17.32)\", \"--color-primary\": \"oklch(62.45% 0.278 3.836)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(48.495% 0.11 355.095)\", \"--color-secondary-content\": \"oklch(89.699% 0.022 355.095)\", \"--color-accent\": \"oklch(56.273% 0.054 154.39)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(24.155% 0.049 89.07)\", \"--color-neutral-content\": \"oklch(92.951% 0.002 17.197)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, aqua: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(37% 0.146 265.522)\", \"--color-base-200\": \"oklch(28% 0.091 267.935)\", \"--color-base-300\": \"oklch(22% 0.091 267.935)\", \"--color-base-content\": \"oklch(90% 0.058 230.902)\", \"--color-primary\": \"oklch(85.661% 0.144 198.645)\", \"--color-primary-content\": \"oklch(40.124% 0.068 197.603)\", \"--color-secondary\": \"oklch(60.682% 0.108 309.782)\", \"--color-secondary-content\": \"oklch(96% 0.016 293.756)\", \"--color-accent\": \"oklch(93.426% 0.102 94.555)\", \"--color-accent-content\": \"oklch(18.685% 0.02 94.555)\", \"--color-neutral\": \"oklch(27% 0.146 265.522)\", \"--color-neutral-content\": \"oklch(80% 0.146 265.522)\", \"--color-info\": \"oklch(54.615% 0.215 262.88)\", \"--color-info-content\": \"oklch(90.923% 0.043 262.88)\", \"--color-success\": \"oklch(62.705% 0.169 149.213)\", \"--color-success-content\": \"oklch(12.541% 0.033 149.213)\", \"--color-warning\": \"oklch(66.584% 0.157 58.318)\", \"--color-warning-content\": \"oklch(27% 0.077 45.635)\", \"--color-error\": \"oklch(73.95% 0.19 27.33)\", \"--color-error-content\": \"oklch(14.79% 0.038 27.33)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, corporate: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(22.389% 0.031 278.072)\", \"--color-primary\": \"oklch(58% 0.158 241.966)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(55% 0.046 257.417)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(60% 0.118 184.704)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(0% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(60% 0.126 221.723)\", \"--color-info-content\": \"oklch(100% 0 0)\", \"--color-success\": \"oklch(62% 0.194 149.214)\", \"--color-success-content\": \"oklch(100% 0 0)\", \"--color-warning\": \"oklch(85% 0.199 91.936)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"0.25rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, pastel: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(98.462% 0.001 247.838)\", \"--color-base-300\": \"oklch(92.462% 0.001 247.838)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(90% 0.063 306.703)\", \"--color-primary-content\": \"oklch(49% 0.265 301.924)\", \"--color-secondary\": \"oklch(89% 0.058 10.001)\", \"--color-secondary-content\": \"oklch(51% 0.222 16.935)\", \"--color-accent\": \"oklch(90% 0.093 164.15)\", \"--color-accent-content\": \"oklch(50% 0.118 165.612)\", \"--color-neutral\": \"oklch(55% 0.046 257.417)\", \"--color-neutral-content\": \"oklch(92% 0.013 255.508)\", \"--color-info\": \"oklch(86% 0.127 207.078)\", \"--color-info-content\": \"oklch(52% 0.105 223.128)\", \"--color-success\": \"oklch(87% 0.15 154.449)\", \"--color-success-content\": \"oklch(52% 0.154 150.069)\", \"--color-warning\": \"oklch(83% 0.128 66.29)\", \"--color-warning-content\": \"oklch(55% 0.195 38.402)\", \"--color-error\": \"oklch(80% 0.114 19.571)\", \"--color-error-content\": \"oklch(50% 0.213 27.518)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"0\", \"--noise\": \"0\" }, bumblebee: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(92% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(85% 0.199 91.936)\", \"--color-primary-content\": \"oklch(42% 0.095 57.708)\", \"--color-secondary\": \"oklch(75% 0.183 55.934)\", \"--color-secondary-content\": \"oklch(40% 0.123 38.172)\", \"--color-accent\": \"oklch(0% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(37% 0.01 67.558)\", \"--color-neutral-content\": \"oklch(92% 0.003 48.717)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(39% 0.09 240.876)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(39% 0.141 25.723)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, coffee: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(24% 0.023 329.708)\", \"--color-base-200\": \"oklch(21% 0.021 329.708)\", \"--color-base-300\": \"oklch(16% 0.019 329.708)\", \"--color-base-content\": \"oklch(72.354% 0.092 79.129)\", \"--color-primary\": \"oklch(71.996% 0.123 62.756)\", \"--color-primary-content\": \"oklch(14.399% 0.024 62.756)\", \"--color-secondary\": \"oklch(34.465% 0.029 199.194)\", \"--color-secondary-content\": \"oklch(86.893% 0.005 199.194)\", \"--color-accent\": \"oklch(42.621% 0.074 224.389)\", \"--color-accent-content\": \"oklch(88.524% 0.014 224.389)\", \"--color-neutral\": \"oklch(16.51% 0.015 326.261)\", \"--color-neutral-content\": \"oklch(83.302% 0.003 326.261)\", \"--color-info\": \"oklch(79.49% 0.063 184.558)\", \"--color-info-content\": \"oklch(15.898% 0.012 184.558)\", \"--color-success\": \"oklch(74.722% 0.072 131.116)\", \"--color-success-content\": \"oklch(14.944% 0.014 131.116)\", \"--color-warning\": \"oklch(88.15% 0.14 87.722)\", \"--color-warning-content\": \"oklch(17.63% 0.028 87.722)\", \"--color-error\": \"oklch(77.318% 0.128 31.871)\", \"--color-error-content\": \"oklch(15.463% 0.025 31.871)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, silk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97% 0.0035 67.78)\", \"--color-base-200\": \"oklch(95% 0.0081 61.42)\", \"--color-base-300\": \"oklch(90% 0.0081 61.42)\", \"--color-base-content\": \"oklch(40% 0.0081 61.42)\", \"--color-primary\": \"oklch(23.27% 0.0249 284.3)\", \"--color-primary-content\": \"oklch(94.22% 0.2505 117.44)\", \"--color-secondary\": \"oklch(23.27% 0.0249 284.3)\", \"--color-secondary-content\": \"oklch(73.92% 0.2135 50.94)\", \"--color-accent\": \"oklch(23.27% 0.0249 284.3)\", \"--color-accent-content\": \"oklch(88.92% 0.2061 189.9)\", \"--color-neutral\": \"oklch(20% 0 0)\", \"--color-neutral-content\": \"oklch(80% 0.0081 61.42)\", \"--color-info\": \"oklch(80.39% 0.1148 241.68)\", \"--color-info-content\": \"oklch(30.39% 0.1148 241.68)\", \"--color-success\": \"oklch(83.92% 0.0901 136.87)\", \"--color-success-content\": \"oklch(23.92% 0.0901 136.87)\", \"--color-warning\": \"oklch(83.92% 0.1085 80)\", \"--color-warning-content\": \"oklch(43.92% 0.1085 80)\", \"--color-error\": \"oklch(75.1% 0.1814 22.37)\", \"--color-error-content\": \"oklch(35.1% 0.1814 22.37)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"0\" }, sunset: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(22% 0.019 237.69)\", \"--color-base-200\": \"oklch(20% 0.019 237.69)\", \"--color-base-300\": \"oklch(18% 0.019 237.69)\", \"--color-base-content\": \"oklch(77.383% 0.043 245.096)\", \"--color-primary\": \"oklch(74.703% 0.158 39.947)\", \"--color-primary-content\": \"oklch(14.94% 0.031 39.947)\", \"--color-secondary\": \"oklch(72.537% 0.177 2.72)\", \"--color-secondary-content\": \"oklch(14.507% 0.035 2.72)\", \"--color-accent\": \"oklch(71.294% 0.166 299.844)\", \"--color-accent-content\": \"oklch(14.258% 0.033 299.844)\", \"--color-neutral\": \"oklch(26% 0.019 237.69)\", \"--color-neutral-content\": \"oklch(70% 0.019 237.69)\", \"--color-info\": \"oklch(85.559% 0.085 206.015)\", \"--color-info-content\": \"oklch(17.111% 0.017 206.015)\", \"--color-success\": \"oklch(85.56% 0.085 144.778)\", \"--color-success-content\": \"oklch(17.112% 0.017 144.778)\", \"--color-warning\": \"oklch(85.569% 0.084 74.427)\", \"--color-warning-content\": \"oklch(17.113% 0.016 74.427)\", \"--color-error\": \"oklch(85.511% 0.078 16.886)\", \"--color-error-content\": \"oklch(17.102% 0.015 16.886)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, synthwave: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(15% 0.09 281.288)\", \"--color-base-200\": \"oklch(20% 0.09 281.288)\", \"--color-base-300\": \"oklch(25% 0.09 281.288)\", \"--color-base-content\": \"oklch(78% 0.115 274.713)\", \"--color-primary\": \"oklch(71% 0.202 349.761)\", \"--color-primary-content\": \"oklch(28% 0.109 3.907)\", \"--color-secondary\": \"oklch(82% 0.111 230.318)\", \"--color-secondary-content\": \"oklch(29% 0.066 243.157)\", \"--color-accent\": \"oklch(75% 0.183 55.934)\", \"--color-accent-content\": \"oklch(26% 0.079 36.259)\", \"--color-neutral\": \"oklch(45% 0.24 277.023)\", \"--color-neutral-content\": \"oklch(87% 0.065 274.039)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(77% 0.152 181.912)\", \"--color-success-content\": \"oklch(27% 0.046 192.524)\", \"--color-warning\": \"oklch(90% 0.182 98.111)\", \"--color-warning-content\": \"oklch(42% 0.095 57.708)\", \"--color-error\": \"oklch(73.7% 0.121 32.639)\", \"--color-error-content\": \"oklch(23.501% 0.096 290.329)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, dim: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(30.857% 0.023 264.149)\", \"--color-base-200\": \"oklch(28.036% 0.019 264.182)\", \"--color-base-300\": \"oklch(26.346% 0.018 262.177)\", \"--color-base-content\": \"oklch(82.901% 0.031 222.959)\", \"--color-primary\": \"oklch(86.133% 0.141 139.549)\", \"--color-primary-content\": \"oklch(17.226% 0.028 139.549)\", \"--color-secondary\": \"oklch(73.375% 0.165 35.353)\", \"--color-secondary-content\": \"oklch(14.675% 0.033 35.353)\", \"--color-accent\": \"oklch(74.229% 0.133 311.379)\", \"--color-accent-content\": \"oklch(14.845% 0.026 311.379)\", \"--color-neutral\": \"oklch(24.731% 0.02 264.094)\", \"--color-neutral-content\": \"oklch(82.901% 0.031 222.959)\", \"--color-info\": \"oklch(86.078% 0.142 206.182)\", \"--color-info-content\": \"oklch(17.215% 0.028 206.182)\", \"--color-success\": \"oklch(86.171% 0.142 166.534)\", \"--color-success-content\": \"oklch(17.234% 0.028 166.534)\", \"--color-warning\": \"oklch(86.163% 0.142 94.818)\", \"--color-warning-content\": \"oklch(17.232% 0.028 94.818)\", \"--color-error\": \"oklch(82.418% 0.099 33.756)\", \"--color-error-content\": \"oklch(16.483% 0.019 33.756)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, abyss: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20% 0.08 209)\", \"--color-base-200\": \"oklch(15% 0.08 209)\", \"--color-base-300\": \"oklch(10% 0.08 209)\", \"--color-base-content\": \"oklch(90% 0.076 70.697)\", \"--color-primary\": \"oklch(92% 0.2653 125)\", \"--color-primary-content\": \"oklch(50% 0.2653 125)\", \"--color-secondary\": \"oklch(83.27% 0.0764 298.3)\", \"--color-secondary-content\": \"oklch(43.27% 0.0764 298.3)\", \"--color-accent\": \"oklch(43% 0 0)\", \"--color-accent-content\": \"oklch(98% 0 0)\", \"--color-neutral\": \"oklch(30% 0.08 209)\", \"--color-neutral-content\": \"oklch(90% 0.076 70.697)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(79% 0.209 151.711)\", \"--color-success-content\": \"oklch(26% 0.065 152.934)\", \"--color-warning\": \"oklch(84.8% 0.1962 84.62)\", \"--color-warning-content\": \"oklch(44.8% 0.1962 84.62)\", \"--color-error\": \"oklch(65% 0.1985 24.22)\", \"--color-error-content\": \"oklch(27% 0.1985 24.22)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, forest: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20.84% 0.008 17.911)\", \"--color-base-200\": \"oklch(18.522% 0.007 17.911)\", \"--color-base-300\": \"oklch(16.203% 0.007 17.911)\", \"--color-base-content\": \"oklch(83.768% 0.001 17.911)\", \"--color-primary\": \"oklch(68.628% 0.185 148.958)\", \"--color-primary-content\": \"oklch(0% 0 0)\", \"--color-secondary\": \"oklch(69.776% 0.135 168.327)\", \"--color-secondary-content\": \"oklch(13.955% 0.027 168.327)\", \"--color-accent\": \"oklch(70.628% 0.119 185.713)\", \"--color-accent-content\": \"oklch(14.125% 0.023 185.713)\", \"--color-neutral\": \"oklch(30.698% 0.039 171.364)\", \"--color-neutral-content\": \"oklch(86.139% 0.007 171.364)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, night: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20.768% 0.039 265.754)\", \"--color-base-200\": \"oklch(19.314% 0.037 265.754)\", \"--color-base-300\": \"oklch(17.86% 0.034 265.754)\", \"--color-base-content\": \"oklch(84.153% 0.007 265.754)\", \"--color-primary\": \"oklch(75.351% 0.138 232.661)\", \"--color-primary-content\": \"oklch(15.07% 0.027 232.661)\", \"--color-secondary\": \"oklch(68.011% 0.158 276.934)\", \"--color-secondary-content\": \"oklch(13.602% 0.031 276.934)\", \"--color-accent\": \"oklch(72.36% 0.176 350.048)\", \"--color-accent-content\": \"oklch(14.472% 0.035 350.048)\", \"--color-neutral\": \"oklch(27.949% 0.036 260.03)\", \"--color-neutral-content\": \"oklch(85.589% 0.007 260.03)\", \"--color-info\": \"oklch(68.455% 0.148 237.251)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(78.452% 0.132 181.911)\", \"--color-success-content\": \"oklch(15.69% 0.026 181.911)\", \"--color-warning\": \"oklch(83.242% 0.139 82.95)\", \"--color-warning-content\": \"oklch(16.648% 0.027 82.95)\", \"--color-error\": \"oklch(71.785% 0.17 13.118)\", \"--color-error-content\": \"oklch(14.357% 0.034 13.118)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, caramellatte: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98% 0.016 73.684)\", \"--color-base-200\": \"oklch(95% 0.038 75.164)\", \"--color-base-300\": \"oklch(90% 0.076 70.697)\", \"--color-base-content\": \"oklch(40% 0.123 38.172)\", \"--color-primary\": \"oklch(0% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(22.45% 0.075 37.85)\", \"--color-secondary-content\": \"oklch(90% 0.076 70.697)\", \"--color-accent\": \"oklch(46.44% 0.111 37.85)\", \"--color-accent-content\": \"oklch(90% 0.076 70.697)\", \"--color-neutral\": \"oklch(55% 0.195 38.402)\", \"--color-neutral-content\": \"oklch(98% 0.016 73.684)\", \"--color-info\": \"oklch(42% 0.199 265.638)\", \"--color-info-content\": \"oklch(90% 0.076 70.697)\", \"--color-success\": \"oklch(43% 0.095 166.913)\", \"--color-success-content\": \"oklch(90% 0.076 70.697)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(39% 0.141 25.723)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"1\" }, autumn: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(95.814% 0 0)\", \"--color-base-200\": \"oklch(89.107% 0 0)\", \"--color-base-300\": \"oklch(82.4% 0 0)\", \"--color-base-content\": \"oklch(19.162% 0 0)\", \"--color-primary\": \"oklch(40.723% 0.161 17.53)\", \"--color-primary-content\": \"oklch(88.144% 0.032 17.53)\", \"--color-secondary\": \"oklch(61.676% 0.169 23.865)\", \"--color-secondary-content\": \"oklch(12.335% 0.033 23.865)\", \"--color-accent\": \"oklch(73.425% 0.094 60.729)\", \"--color-accent-content\": \"oklch(14.685% 0.018 60.729)\", \"--color-neutral\": \"oklch(54.367% 0.037 51.902)\", \"--color-neutral-content\": \"oklch(90.873% 0.007 51.902)\", \"--color-info\": \"oklch(69.224% 0.097 207.284)\", \"--color-info-content\": \"oklch(13.844% 0.019 207.284)\", \"--color-success\": \"oklch(60.995% 0.08 174.616)\", \"--color-success-content\": \"oklch(12.199% 0.016 174.616)\", \"--color-warning\": \"oklch(70.081% 0.164 56.844)\", \"--color-warning-content\": \"oklch(14.016% 0.032 56.844)\", \"--color-error\": \"oklch(53.07% 0.241 24.16)\", \"--color-error-content\": \"oklch(90.614% 0.048 24.16)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, emerald: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(35.519% 0.032 262.988)\", \"--color-primary\": \"oklch(76.662% 0.135 153.45)\", \"--color-primary-content\": \"oklch(33.387% 0.04 162.24)\", \"--color-secondary\": \"oklch(61.302% 0.202 261.294)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(72.772% 0.149 33.2)\", \"--color-accent-content\": \"oklch(0% 0 0)\", \"--color-neutral\": \"oklch(35.519% 0.032 262.988)\", \"--color-neutral-content\": \"oklch(98.462% 0.001 247.838)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, cupcake: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97.788% 0.004 56.375)\", \"--color-base-200\": \"oklch(93.982% 0.007 61.449)\", \"--color-base-300\": \"oklch(91.586% 0.006 53.44)\", \"--color-base-content\": \"oklch(23.574% 0.066 313.189)\", \"--color-primary\": \"oklch(85% 0.138 181.071)\", \"--color-primary-content\": \"oklch(43% 0.078 188.216)\", \"--color-secondary\": \"oklch(89% 0.061 343.231)\", \"--color-secondary-content\": \"oklch(45% 0.187 3.815)\", \"--color-accent\": \"oklch(90% 0.076 70.697)\", \"--color-accent-content\": \"oklch(47% 0.157 37.304)\", \"--color-neutral\": \"oklch(27% 0.006 286.033)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(68% 0.169 237.323)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(69% 0.17 162.48)\", \"--color-success-content\": \"oklch(26% 0.051 172.552)\", \"--color-warning\": \"oklch(79% 0.184 86.047)\", \"--color-warning-content\": \"oklch(28% 0.066 53.813)\", \"--color-error\": \"oklch(64% 0.246 16.439)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"0\" }, cmyk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(95% 0 0)\", \"--color-base-300\": \"oklch(90% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(71.772% 0.133 239.443)\", \"--color-primary-content\": \"oklch(14.354% 0.026 239.443)\", \"--color-secondary\": \"oklch(64.476% 0.202 359.339)\", \"--color-secondary-content\": \"oklch(12.895% 0.04 359.339)\", \"--color-accent\": \"oklch(94.228% 0.189 105.306)\", \"--color-accent-content\": \"oklch(18.845% 0.037 105.306)\", \"--color-neutral\": \"oklch(21.778% 0 0)\", \"--color-neutral-content\": \"oklch(84.355% 0 0)\", \"--color-info\": \"oklch(68.475% 0.094 217.284)\", \"--color-info-content\": \"oklch(13.695% 0.018 217.284)\", \"--color-success\": \"oklch(46.949% 0.162 321.406)\", \"--color-success-content\": \"oklch(89.389% 0.032 321.406)\", \"--color-warning\": \"oklch(71.236% 0.159 52.023)\", \"--color-warning-content\": \"oklch(14.247% 0.031 52.023)\", \"--color-error\": \"oklch(62.013% 0.208 28.717)\", \"--color-error-content\": \"oklch(12.402% 0.041 28.717)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, business: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(24.353% 0 0)\", \"--color-base-200\": \"oklch(22.648% 0 0)\", \"--color-base-300\": \"oklch(20.944% 0 0)\", \"--color-base-content\": \"oklch(84.87% 0 0)\", \"--color-primary\": \"oklch(41.703% 0.099 251.473)\", \"--color-primary-content\": \"oklch(88.34% 0.019 251.473)\", \"--color-secondary\": \"oklch(64.092% 0.027 229.389)\", \"--color-secondary-content\": \"oklch(12.818% 0.005 229.389)\", \"--color-accent\": \"oklch(67.271% 0.167 35.791)\", \"--color-accent-content\": \"oklch(13.454% 0.033 35.791)\", \"--color-neutral\": \"oklch(27.441% 0.013 253.041)\", \"--color-neutral-content\": \"oklch(85.488% 0.002 253.041)\", \"--color-info\": \"oklch(62.616% 0.143 240.033)\", \"--color-info-content\": \"oklch(12.523% 0.028 240.033)\", \"--color-success\": \"oklch(70.226% 0.094 156.596)\", \"--color-success-content\": \"oklch(14.045% 0.018 156.596)\", \"--color-warning\": \"oklch(77.482% 0.115 81.519)\", \"--color-warning-content\": \"oklch(15.496% 0.023 81.519)\", \"--color-error\": \"oklch(51.61% 0.146 29.674)\", \"--color-error-content\": \"oklch(90.322% 0.029 29.674)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, winter: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97.466% 0.011 259.822)\", \"--color-base-300\": \"oklch(93.268% 0.016 262.751)\", \"--color-base-content\": \"oklch(41.886% 0.053 255.824)\", \"--color-primary\": \"oklch(56.86% 0.255 257.57)\", \"--color-primary-content\": \"oklch(91.372% 0.051 257.57)\", \"--color-secondary\": \"oklch(42.551% 0.161 282.339)\", \"--color-secondary-content\": \"oklch(88.51% 0.032 282.339)\", \"--color-accent\": \"oklch(59.939% 0.191 335.171)\", \"--color-accent-content\": \"oklch(11.988% 0.038 335.171)\", \"--color-neutral\": \"oklch(19.616% 0.063 257.651)\", \"--color-neutral-content\": \"oklch(83.923% 0.012 257.651)\", \"--color-info\": \"oklch(88.127% 0.085 214.515)\", \"--color-info-content\": \"oklch(17.625% 0.017 214.515)\", \"--color-success\": \"oklch(80.494% 0.077 197.823)\", \"--color-success-content\": \"oklch(16.098% 0.015 197.823)\", \"--color-warning\": \"oklch(89.172% 0.045 71.47)\", \"--color-warning-content\": \"oklch(17.834% 0.009 71.47)\", \"--color-error\": \"oklch(73.092% 0.11 20.076)\", \"--color-error-content\": \"oklch(14.618% 0.022 20.076)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, halloween: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(21% 0.006 56.043)\", \"--color-base-200\": \"oklch(14% 0.004 49.25)\", \"--color-base-300\": \"oklch(0% 0 0)\", \"--color-base-content\": \"oklch(84.955% 0 0)\", \"--color-primary\": \"oklch(77.48% 0.204 60.62)\", \"--color-primary-content\": \"oklch(19.693% 0.004 196.779)\", \"--color-secondary\": \"oklch(45.98% 0.248 305.03)\", \"--color-secondary-content\": \"oklch(89.196% 0.049 305.03)\", \"--color-accent\": \"oklch(64.8% 0.223 136.073)\", \"--color-accent-content\": \"oklch(0% 0 0)\", \"--color-neutral\": \"oklch(24.371% 0.046 65.681)\", \"--color-neutral-content\": \"oklch(84.874% 0.009 65.681)\", \"--color-info\": \"oklch(54.615% 0.215 262.88)\", \"--color-info-content\": \"oklch(90.923% 0.043 262.88)\", \"--color-success\": \"oklch(62.705% 0.169 149.213)\", \"--color-success-content\": \"oklch(12.541% 0.033 149.213)\", \"--color-warning\": \"oklch(66.584% 0.157 58.318)\", \"--color-warning-content\": \"oklch(13.316% 0.031 58.318)\", \"--color-error\": \"oklch(65.72% 0.199 27.33)\", \"--color-error-content\": \"oklch(13.144% 0.039 27.33)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, fantasy: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(27.807% 0.029 256.847)\", \"--color-primary\": \"oklch(37.45% 0.189 325.02)\", \"--color-primary-content\": \"oklch(87.49% 0.037 325.02)\", \"--color-secondary\": \"oklch(53.92% 0.162 241.36)\", \"--color-secondary-content\": \"oklch(90.784% 0.032 241.36)\", \"--color-accent\": \"oklch(75.98% 0.204 56.72)\", \"--color-accent-content\": \"oklch(15.196% 0.04 56.72)\", \"--color-neutral\": \"oklch(27.807% 0.029 256.847)\", \"--color-neutral-content\": \"oklch(85.561% 0.005 256.847)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, wireframe: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(94% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(87% 0 0)\", \"--color-primary-content\": \"oklch(26% 0 0)\", \"--color-secondary\": \"oklch(87% 0 0)\", \"--color-secondary-content\": \"oklch(26% 0 0)\", \"--color-accent\": \"oklch(87% 0 0)\", \"--color-accent-content\": \"oklch(26% 0 0)\", \"--color-neutral\": \"oklch(87% 0 0)\", \"--color-neutral-content\": \"oklch(26% 0 0)\", \"--color-info\": \"oklch(44% 0.11 240.79)\", \"--color-info-content\": \"oklch(90% 0.058 230.902)\", \"--color-success\": \"oklch(43% 0.095 166.913)\", \"--color-success-content\": \"oklch(90% 0.093 164.15)\", \"--color-warning\": \"oklch(47% 0.137 46.201)\", \"--color-warning-content\": \"oklch(92% 0.12 95.746)\", \"--color-error\": \"oklch(44% 0.177 26.899)\", \"--color-error-content\": \"oklch(88% 0.062 18.334)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" } };\n\n// packages/daisyui/theme/index.js\nvar theme_default = plugin.withOptions((options = {}) => {\n  return ({ addBase }) => {\n    const {\n      name = \"custom-theme\",\n      default: isDefault = false,\n      prefersdark = false,\n      \"color-scheme\": colorScheme = \"normal\",\n      root = \":root\",\n      ...customThemeTokens\n    } = options;\n    let selector = `${root}:has(input.theme-controller[value=${name}]:checked),[data-theme=\"${name}\"]`;\n    if (isDefault) {\n      selector = `:where(${root}),${selector}`;\n    }\n    let themeTokens = { ...customThemeTokens };\n    if (object_default[name]) {\n      const builtinTheme = object_default[name];\n      themeTokens = {\n        ...builtinTheme,\n        ...customThemeTokens,\n        \"color-scheme\": colorScheme || builtinTheme.colorScheme\n      };\n    }\n    const baseStyles = {\n      [selector]: {\n        \"color-scheme\": themeTokens[\"color-scheme\"] || colorScheme,\n        ...themeTokens\n      }\n    };\n    if (prefersdark) {\n      addBase({\n        \"@media (prefers-color-scheme: dark)\": {\n          [root]: baseStyles[selector]\n        }\n      });\n    }\n    addBase(baseStyles);\n  };\n});\n\n\n/*\n\n  MIT License\n\n  Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n\n*/\n"
  },
  {
    "path": "installer/templates/phx_assets/daisyui.js.eex",
    "content": "/** 🌼\n *  @license MIT\n *  daisyUI bundle\n *  https://daisyui.com/\n */\n\nvar __defProp = Object.defineProperty;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __moduleCache = /* @__PURE__ */ new WeakMap;\nvar __toCommonJS = (from) => {\n  var entry = __moduleCache.get(from), desc;\n  if (entry)\n    return entry;\n  entry = __defProp({}, \"__esModule\", { value: true });\n  if (from && typeof from === \"object\" || typeof from === \"function\")\n    __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {\n      get: () => from[key],\n      enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable\n    }));\n  __moduleCache.set(from, entry);\n  return entry;\n};\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, {\n      get: all[name],\n      enumerable: true,\n      configurable: true,\n      set: (newValue) => all[name] = () => newValue\n    });\n};\n\n// packages/daisyui/index.js\nvar exports_daisyui = {};\n__export(exports_daisyui, {\n  default: () => daisyui_default\n});\nmodule.exports = __toCommonJS(exports_daisyui);\n\n// packages/daisyui/functions/themeOrder.js\nvar themeOrder_default = [\n  \"light\",\n  \"dark\",\n  \"cupcake\",\n  \"bumblebee\",\n  \"emerald\",\n  \"corporate\",\n  \"synthwave\",\n  \"retro\",\n  \"cyberpunk\",\n  \"valentine\",\n  \"halloween\",\n  \"garden\",\n  \"forest\",\n  \"aqua\",\n  \"lofi\",\n  \"pastel\",\n  \"fantasy\",\n  \"wireframe\",\n  \"black\",\n  \"luxury\",\n  \"dracula\",\n  \"cmyk\",\n  \"autumn\",\n  \"business\",\n  \"acid\",\n  \"lemonade\",\n  \"night\",\n  \"coffee\",\n  \"winter\",\n  \"dim\",\n  \"nord\",\n  \"sunset\",\n  \"caramellatte\",\n  \"abyss\",\n  \"silk\"\n];\n\n// packages/daisyui/functions/pluginOptionsHandler.js\nvar pluginOptionsHandler = (() => {\n  let firstRun = true;\n  return (options, addBase, themesObject, packageVersion) => {\n    const {\n      logs = true,\n      root = \":root\",\n      themes = [\"light --default\", \"dark --prefersdark\"],\n      include,\n      exclude,\n      prefix = \"\"\n    } = options || {};\n    if (logs !== false && firstRun) {\n      console.log(`${atob(\"Lyoh\")} ${decodeURIComponent(\"%F0%9F%8C%BC\")} ${atob(\"ZGFpc3lVSQ==\")} ${packageVersion} ${atob(\"Ki8=\")}`);\n      firstRun = false;\n    }\n    const applyTheme = (themeName, flags) => {\n      const theme = themesObject[themeName];\n      if (theme) {\n        let selector = `${root}:has(input.theme-controller[value=${themeName}]:checked),[data-theme=${themeName}]`;\n        if (flags.includes(\"--default\")) {\n          selector = `:where(${root}),${selector}`;\n        }\n        addBase({ [selector]: theme });\n        if (flags.includes(\"--prefersdark\")) {\n          addBase({ \"@media (prefers-color-scheme: dark)\": { [root]: theme } });\n        }\n      }\n    };\n    if (themes === \"all\") {\n      if (themesObject[\"light\"]) {\n        applyTheme(\"light\", [\"--default\"]);\n      }\n      if (themesObject[\"dark\"]) {\n        addBase({ \"@media (prefers-color-scheme: dark)\": { [root]: themesObject[\"dark\"] } });\n      }\n      themeOrder_default.forEach((themeName) => {\n        if (themesObject[themeName]) {\n          applyTheme(themeName, []);\n        }\n      });\n    } else if (themes) {\n      const themeArray = Array.isArray(themes) ? themes : [themes];\n      if (themeArray.length === 1 && themeArray[0].includes(\"--default\")) {\n        const [themeName, ...flags] = themeArray[0].split(\" \");\n        applyTheme(themeName, flags);\n        return { include, exclude, prefix };\n      }\n      themeArray.forEach((themeOption) => {\n        const [themeName, ...flags] = themeOption.split(\" \");\n        if (flags.includes(\"--default\")) {\n          applyTheme(themeName, [\"--default\"]);\n        }\n      });\n      themeArray.forEach((themeOption) => {\n        const [themeName, ...flags] = themeOption.split(\" \");\n        if (flags.includes(\"--prefersdark\")) {\n          addBase({ \"@media (prefers-color-scheme: dark)\": { [root]: themesObject[themeName] } });\n        }\n      });\n      themeArray.forEach((themeOption) => {\n        const [themeName] = themeOption.split(\" \");\n        applyTheme(themeName, []);\n      });\n    }\n    return { include, exclude, prefix };\n  };\n})();\n\n// packages/daisyui/functions/plugin.js\nvar plugin = {\n  withOptions: (pluginFunction, configFunction = () => ({})) => {\n    const optionsFunction = (options) => {\n      const handler = pluginFunction(options);\n      const config = configFunction(options);\n      return { handler, config };\n    };\n    optionsFunction.__isOptionsFunction = true;\n    return optionsFunction;\n  }\n};\n\n// packages/daisyui/functions/variables.js\nvar variables_default = {\n  colors: {\n    \"base-100\": \"var(--color-base-100)\",\n    \"base-200\": \"var(--color-base-200)\",\n    \"base-300\": \"var(--color-base-300)\",\n    \"base-content\": \"var(--color-base-content)\",\n    primary: \"var(--color-primary)\",\n    \"primary-content\": \"var(--color-primary-content)\",\n    secondary: \"var(--color-secondary)\",\n    \"secondary-content\": \"var(--color-secondary-content)\",\n    accent: \"var(--color-accent)\",\n    \"accent-content\": \"var(--color-accent-content)\",\n    neutral: \"var(--color-neutral)\",\n    \"neutral-content\": \"var(--color-neutral-content)\",\n    info: \"var(--color-info)\",\n    \"info-content\": \"var(--color-info-content)\",\n    success: \"var(--color-success)\",\n    \"success-content\": \"var(--color-success-content)\",\n    warning: \"var(--color-warning)\",\n    \"warning-content\": \"var(--color-warning-content)\",\n    error: \"var(--color-error)\",\n    \"error-content\": \"var(--color-error-content)\"\n  },\n  borderRadius: {\n    selector: \"var(--radius-selector)\",\n    field: \"var(--radius-field)\",\n    box: \"var(--radius-box)\"\n  }\n};\n\n// packages/daisyui/theme/object.js\nvar object_default = { cyberpunk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(94.51% 0.179 104.32)\", \"--color-base-200\": \"oklch(91.51% 0.179 104.32)\", \"--color-base-300\": \"oklch(85.51% 0.179 104.32)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(74.22% 0.209 6.35)\", \"--color-primary-content\": \"oklch(14.844% 0.041 6.35)\", \"--color-secondary\": \"oklch(83.33% 0.184 204.72)\", \"--color-secondary-content\": \"oklch(16.666% 0.036 204.72)\", \"--color-accent\": \"oklch(71.86% 0.217 310.43)\", \"--color-accent-content\": \"oklch(14.372% 0.043 310.43)\", \"--color-neutral\": \"oklch(23.04% 0.065 269.31)\", \"--color-neutral-content\": \"oklch(94.51% 0.179 104.32)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0rem\", \"--radius-box\": \"0rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, acid: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98% 0 0)\", \"--color-base-200\": \"oklch(95% 0 0)\", \"--color-base-300\": \"oklch(91% 0 0)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(71.9% 0.357 330.759)\", \"--color-primary-content\": \"oklch(14.38% 0.071 330.759)\", \"--color-secondary\": \"oklch(73.37% 0.224 48.25)\", \"--color-secondary-content\": \"oklch(14.674% 0.044 48.25)\", \"--color-accent\": \"oklch(92.78% 0.264 122.962)\", \"--color-accent-content\": \"oklch(18.556% 0.052 122.962)\", \"--color-neutral\": \"oklch(21.31% 0.128 278.68)\", \"--color-neutral-content\": \"oklch(84.262% 0.025 278.68)\", \"--color-info\": \"oklch(60.72% 0.227 252.05)\", \"--color-info-content\": \"oklch(12.144% 0.045 252.05)\", \"--color-success\": \"oklch(85.72% 0.266 158.53)\", \"--color-success-content\": \"oklch(17.144% 0.053 158.53)\", \"--color-warning\": \"oklch(91.01% 0.212 100.5)\", \"--color-warning-content\": \"oklch(18.202% 0.042 100.5)\", \"--color-error\": \"oklch(64.84% 0.293 29.349)\", \"--color-error-content\": \"oklch(12.968% 0.058 29.349)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"1rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, black: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(0% 0 0)\", \"--color-base-200\": \"oklch(19% 0 0)\", \"--color-base-300\": \"oklch(22% 0 0)\", \"--color-base-content\": \"oklch(87.609% 0 0)\", \"--color-primary\": \"oklch(35% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(35% 0 0)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(35% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(35% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(45.201% 0.313 264.052)\", \"--color-info-content\": \"oklch(89.04% 0.062 264.052)\", \"--color-success\": \"oklch(51.975% 0.176 142.495)\", \"--color-success-content\": \"oklch(90.395% 0.035 142.495)\", \"--color-warning\": \"oklch(96.798% 0.211 109.769)\", \"--color-warning-content\": \"oklch(19.359% 0.042 109.769)\", \"--color-error\": \"oklch(62.795% 0.257 29.233)\", \"--color-error-content\": \"oklch(12.559% 0.051 29.233)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0rem\", \"--radius-box\": \"0rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, dark: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(25.33% 0.016 252.42)\", \"--color-base-200\": \"oklch(23.26% 0.014 253.1)\", \"--color-base-300\": \"oklch(21.15% 0.012 254.09)\", \"--color-base-content\": \"oklch(97.807% 0.029 256.847)\", \"--color-primary\": \"oklch(58% 0.233 277.117)\", \"--color-primary-content\": \"oklch(96% 0.018 272.314)\", \"--color-secondary\": \"oklch(65% 0.241 354.308)\", \"--color-secondary-content\": \"oklch(94% 0.028 342.258)\", \"--color-accent\": \"oklch(77% 0.152 181.912)\", \"--color-accent-content\": \"oklch(38% 0.063 188.416)\", \"--color-neutral\": \"oklch(14% 0.005 285.823)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(71% 0.194 13.428)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"0.5rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, light: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(98% 0 0)\", \"--color-base-300\": \"oklch(95% 0 0)\", \"--color-base-content\": \"oklch(21% 0.006 285.885)\", \"--color-primary\": \"oklch(45% 0.24 277.023)\", \"--color-primary-content\": \"oklch(93% 0.034 272.788)\", \"--color-secondary\": \"oklch(65% 0.241 354.308)\", \"--color-secondary-content\": \"oklch(94% 0.028 342.258)\", \"--color-accent\": \"oklch(77% 0.152 181.912)\", \"--color-accent-content\": \"oklch(38% 0.063 188.416)\", \"--color-neutral\": \"oklch(14% 0.005 285.823)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(71% 0.194 13.428)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"0.5rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, luxury: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(14.076% 0.004 285.822)\", \"--color-base-200\": \"oklch(20.219% 0.004 308.229)\", \"--color-base-300\": \"oklch(23.219% 0.004 308.229)\", \"--color-base-content\": \"oklch(75.687% 0.123 76.89)\", \"--color-primary\": \"oklch(100% 0 0)\", \"--color-primary-content\": \"oklch(20% 0 0)\", \"--color-secondary\": \"oklch(27.581% 0.064 261.069)\", \"--color-secondary-content\": \"oklch(85.516% 0.012 261.069)\", \"--color-accent\": \"oklch(36.674% 0.051 338.825)\", \"--color-accent-content\": \"oklch(87.334% 0.01 338.825)\", \"--color-neutral\": \"oklch(24.27% 0.057 59.825)\", \"--color-neutral-content\": \"oklch(93.203% 0.089 90.861)\", \"--color-info\": \"oklch(79.061% 0.121 237.133)\", \"--color-info-content\": \"oklch(15.812% 0.024 237.133)\", \"--color-success\": \"oklch(78.119% 0.192 132.154)\", \"--color-success-content\": \"oklch(15.623% 0.038 132.154)\", \"--color-warning\": \"oklch(86.127% 0.136 102.891)\", \"--color-warning-content\": \"oklch(17.225% 0.027 102.891)\", \"--color-error\": \"oklch(71.753% 0.176 22.568)\", \"--color-error-content\": \"oklch(14.35% 0.035 22.568)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, dracula: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(28.822% 0.022 277.508)\", \"--color-base-200\": \"oklch(26.805% 0.02 277.508)\", \"--color-base-300\": \"oklch(24.787% 0.019 277.508)\", \"--color-base-content\": \"oklch(97.747% 0.007 106.545)\", \"--color-primary\": \"oklch(75.461% 0.183 346.812)\", \"--color-primary-content\": \"oklch(15.092% 0.036 346.812)\", \"--color-secondary\": \"oklch(74.202% 0.148 301.883)\", \"--color-secondary-content\": \"oklch(14.84% 0.029 301.883)\", \"--color-accent\": \"oklch(83.392% 0.124 66.558)\", \"--color-accent-content\": \"oklch(16.678% 0.024 66.558)\", \"--color-neutral\": \"oklch(39.445% 0.032 275.524)\", \"--color-neutral-content\": \"oklch(87.889% 0.006 275.524)\", \"--color-info\": \"oklch(88.263% 0.093 212.846)\", \"--color-info-content\": \"oklch(17.652% 0.018 212.846)\", \"--color-success\": \"oklch(87.099% 0.219 148.024)\", \"--color-success-content\": \"oklch(17.419% 0.043 148.024)\", \"--color-warning\": \"oklch(95.533% 0.134 112.757)\", \"--color-warning-content\": \"oklch(19.106% 0.026 112.757)\", \"--color-error\": \"oklch(68.22% 0.206 24.43)\", \"--color-error-content\": \"oklch(13.644% 0.041 24.43)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, retro: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(91.637% 0.034 90.515)\", \"--color-base-200\": \"oklch(88.272% 0.049 91.774)\", \"--color-base-300\": \"oklch(84.133% 0.065 90.856)\", \"--color-base-content\": \"oklch(41% 0.112 45.904)\", \"--color-primary\": \"oklch(80% 0.114 19.571)\", \"--color-primary-content\": \"oklch(39% 0.141 25.723)\", \"--color-secondary\": \"oklch(92% 0.084 155.995)\", \"--color-secondary-content\": \"oklch(44% 0.119 151.328)\", \"--color-accent\": \"oklch(68% 0.162 75.834)\", \"--color-accent-content\": \"oklch(41% 0.112 45.904)\", \"--color-neutral\": \"oklch(44% 0.011 73.639)\", \"--color-neutral-content\": \"oklch(86% 0.005 56.366)\", \"--color-info\": \"oklch(58% 0.158 241.966)\", \"--color-info-content\": \"oklch(96% 0.059 95.617)\", \"--color-success\": \"oklch(51% 0.096 186.391)\", \"--color-success-content\": \"oklch(96% 0.059 95.617)\", \"--color-warning\": \"oklch(64% 0.222 41.116)\", \"--color-warning-content\": \"oklch(96% 0.059 95.617)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(40% 0.123 38.172)\", \"--radius-selector\": \"0.25rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, lofi: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(94% 0 0)\", \"--color-base-content\": \"oklch(0% 0 0)\", \"--color-primary\": \"oklch(15.906% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(21.455% 0.001 17.278)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(26.861% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(0% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(79.54% 0.103 205.9)\", \"--color-info-content\": \"oklch(15.908% 0.02 205.9)\", \"--color-success\": \"oklch(90.13% 0.153 164.14)\", \"--color-success-content\": \"oklch(18.026% 0.03 164.14)\", \"--color-warning\": \"oklch(88.37% 0.135 79.94)\", \"--color-warning-content\": \"oklch(17.674% 0.027 79.94)\", \"--color-error\": \"oklch(78.66% 0.15 28.47)\", \"--color-error-content\": \"oklch(15.732% 0.03 28.47)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, valentine: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97% 0.014 343.198)\", \"--color-base-200\": \"oklch(94% 0.028 342.258)\", \"--color-base-300\": \"oklch(89% 0.061 343.231)\", \"--color-base-content\": \"oklch(52% 0.223 3.958)\", \"--color-primary\": \"oklch(65% 0.241 354.308)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(62% 0.265 303.9)\", \"--color-secondary-content\": \"oklch(97% 0.014 308.299)\", \"--color-accent\": \"oklch(82% 0.111 230.318)\", \"--color-accent-content\": \"oklch(39% 0.09 240.876)\", \"--color-neutral\": \"oklch(40% 0.153 2.432)\", \"--color-neutral-content\": \"oklch(89% 0.061 343.231)\", \"--color-info\": \"oklch(86% 0.127 207.078)\", \"--color-info-content\": \"oklch(44% 0.11 240.79)\", \"--color-success\": \"oklch(84% 0.143 164.978)\", \"--color-success-content\": \"oklch(43% 0.095 166.913)\", \"--color-warning\": \"oklch(75% 0.183 55.934)\", \"--color-warning-content\": \"oklch(26% 0.079 36.259)\", \"--color-error\": \"oklch(63% 0.237 25.331)\", \"--color-error-content\": \"oklch(97% 0.013 17.38)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, nord: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(95.127% 0.007 260.731)\", \"--color-base-200\": \"oklch(93.299% 0.01 261.788)\", \"--color-base-300\": \"oklch(89.925% 0.016 262.749)\", \"--color-base-content\": \"oklch(32.437% 0.022 264.182)\", \"--color-primary\": \"oklch(59.435% 0.077 254.027)\", \"--color-primary-content\": \"oklch(11.887% 0.015 254.027)\", \"--color-secondary\": \"oklch(69.651% 0.059 248.687)\", \"--color-secondary-content\": \"oklch(13.93% 0.011 248.687)\", \"--color-accent\": \"oklch(77.464% 0.062 217.469)\", \"--color-accent-content\": \"oklch(15.492% 0.012 217.469)\", \"--color-neutral\": \"oklch(45.229% 0.035 264.131)\", \"--color-neutral-content\": \"oklch(89.925% 0.016 262.749)\", \"--color-info\": \"oklch(69.207% 0.062 332.664)\", \"--color-info-content\": \"oklch(13.841% 0.012 332.664)\", \"--color-success\": \"oklch(76.827% 0.074 131.063)\", \"--color-success-content\": \"oklch(15.365% 0.014 131.063)\", \"--color-warning\": \"oklch(85.486% 0.089 84.093)\", \"--color-warning-content\": \"oklch(17.097% 0.017 84.093)\", \"--color-error\": \"oklch(60.61% 0.12 15.341)\", \"--color-error-content\": \"oklch(12.122% 0.024 15.341)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, lemonade: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98.71% 0.02 123.72)\", \"--color-base-200\": \"oklch(91.8% 0.018 123.72)\", \"--color-base-300\": \"oklch(84.89% 0.017 123.72)\", \"--color-base-content\": \"oklch(19.742% 0.004 123.72)\", \"--color-primary\": \"oklch(58.92% 0.199 134.6)\", \"--color-primary-content\": \"oklch(11.784% 0.039 134.6)\", \"--color-secondary\": \"oklch(77.75% 0.196 111.09)\", \"--color-secondary-content\": \"oklch(15.55% 0.039 111.09)\", \"--color-accent\": \"oklch(85.39% 0.201 100.73)\", \"--color-accent-content\": \"oklch(17.078% 0.04 100.73)\", \"--color-neutral\": \"oklch(30.98% 0.075 108.6)\", \"--color-neutral-content\": \"oklch(86.196% 0.015 108.6)\", \"--color-info\": \"oklch(86.19% 0.047 224.14)\", \"--color-info-content\": \"oklch(17.238% 0.009 224.14)\", \"--color-success\": \"oklch(86.19% 0.047 157.85)\", \"--color-success-content\": \"oklch(17.238% 0.009 157.85)\", \"--color-warning\": \"oklch(86.19% 0.047 102.15)\", \"--color-warning-content\": \"oklch(17.238% 0.009 102.15)\", \"--color-error\": \"oklch(86.19% 0.047 25.85)\", \"--color-error-content\": \"oklch(17.238% 0.009 25.85)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, garden: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(92.951% 0.002 17.197)\", \"--color-base-200\": \"oklch(86.445% 0.002 17.197)\", \"--color-base-300\": \"oklch(79.938% 0.001 17.197)\", \"--color-base-content\": \"oklch(16.961% 0.001 17.32)\", \"--color-primary\": \"oklch(62.45% 0.278 3.836)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(48.495% 0.11 355.095)\", \"--color-secondary-content\": \"oklch(89.699% 0.022 355.095)\", \"--color-accent\": \"oklch(56.273% 0.054 154.39)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(24.155% 0.049 89.07)\", \"--color-neutral-content\": \"oklch(92.951% 0.002 17.197)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, aqua: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(37% 0.146 265.522)\", \"--color-base-200\": \"oklch(28% 0.091 267.935)\", \"--color-base-300\": \"oklch(22% 0.091 267.935)\", \"--color-base-content\": \"oklch(90% 0.058 230.902)\", \"--color-primary\": \"oklch(85.661% 0.144 198.645)\", \"--color-primary-content\": \"oklch(40.124% 0.068 197.603)\", \"--color-secondary\": \"oklch(60.682% 0.108 309.782)\", \"--color-secondary-content\": \"oklch(96% 0.016 293.756)\", \"--color-accent\": \"oklch(93.426% 0.102 94.555)\", \"--color-accent-content\": \"oklch(18.685% 0.02 94.555)\", \"--color-neutral\": \"oklch(27% 0.146 265.522)\", \"--color-neutral-content\": \"oklch(80% 0.146 265.522)\", \"--color-info\": \"oklch(54.615% 0.215 262.88)\", \"--color-info-content\": \"oklch(90.923% 0.043 262.88)\", \"--color-success\": \"oklch(62.705% 0.169 149.213)\", \"--color-success-content\": \"oklch(12.541% 0.033 149.213)\", \"--color-warning\": \"oklch(66.584% 0.157 58.318)\", \"--color-warning-content\": \"oklch(27% 0.077 45.635)\", \"--color-error\": \"oklch(73.95% 0.19 27.33)\", \"--color-error-content\": \"oklch(14.79% 0.038 27.33)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, corporate: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(22.389% 0.031 278.072)\", \"--color-primary\": \"oklch(58% 0.158 241.966)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(55% 0.046 257.417)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(60% 0.118 184.704)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(0% 0 0)\", \"--color-neutral-content\": \"oklch(100% 0 0)\", \"--color-info\": \"oklch(60% 0.126 221.723)\", \"--color-info-content\": \"oklch(100% 0 0)\", \"--color-success\": \"oklch(62% 0.194 149.214)\", \"--color-success-content\": \"oklch(100% 0 0)\", \"--color-warning\": \"oklch(85% 0.199 91.936)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"0.25rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, pastel: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(98.462% 0.001 247.838)\", \"--color-base-300\": \"oklch(92.462% 0.001 247.838)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(90% 0.063 306.703)\", \"--color-primary-content\": \"oklch(49% 0.265 301.924)\", \"--color-secondary\": \"oklch(89% 0.058 10.001)\", \"--color-secondary-content\": \"oklch(51% 0.222 16.935)\", \"--color-accent\": \"oklch(90% 0.093 164.15)\", \"--color-accent-content\": \"oklch(50% 0.118 165.612)\", \"--color-neutral\": \"oklch(55% 0.046 257.417)\", \"--color-neutral-content\": \"oklch(92% 0.013 255.508)\", \"--color-info\": \"oklch(86% 0.127 207.078)\", \"--color-info-content\": \"oklch(52% 0.105 223.128)\", \"--color-success\": \"oklch(87% 0.15 154.449)\", \"--color-success-content\": \"oklch(52% 0.154 150.069)\", \"--color-warning\": \"oklch(83% 0.128 66.29)\", \"--color-warning-content\": \"oklch(55% 0.195 38.402)\", \"--color-error\": \"oklch(80% 0.114 19.571)\", \"--color-error-content\": \"oklch(50% 0.213 27.518)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"0\", \"--noise\": \"0\" }, bumblebee: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(92% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(85% 0.199 91.936)\", \"--color-primary-content\": \"oklch(42% 0.095 57.708)\", \"--color-secondary\": \"oklch(75% 0.183 55.934)\", \"--color-secondary-content\": \"oklch(40% 0.123 38.172)\", \"--color-accent\": \"oklch(0% 0 0)\", \"--color-accent-content\": \"oklch(100% 0 0)\", \"--color-neutral\": \"oklch(37% 0.01 67.558)\", \"--color-neutral-content\": \"oklch(92% 0.003 48.717)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(39% 0.09 240.876)\", \"--color-success\": \"oklch(76% 0.177 163.223)\", \"--color-success-content\": \"oklch(37% 0.077 168.94)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(39% 0.141 25.723)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, coffee: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(24% 0.023 329.708)\", \"--color-base-200\": \"oklch(21% 0.021 329.708)\", \"--color-base-300\": \"oklch(16% 0.019 329.708)\", \"--color-base-content\": \"oklch(72.354% 0.092 79.129)\", \"--color-primary\": \"oklch(71.996% 0.123 62.756)\", \"--color-primary-content\": \"oklch(14.399% 0.024 62.756)\", \"--color-secondary\": \"oklch(34.465% 0.029 199.194)\", \"--color-secondary-content\": \"oklch(86.893% 0.005 199.194)\", \"--color-accent\": \"oklch(42.621% 0.074 224.389)\", \"--color-accent-content\": \"oklch(88.524% 0.014 224.389)\", \"--color-neutral\": \"oklch(16.51% 0.015 326.261)\", \"--color-neutral-content\": \"oklch(83.302% 0.003 326.261)\", \"--color-info\": \"oklch(79.49% 0.063 184.558)\", \"--color-info-content\": \"oklch(15.898% 0.012 184.558)\", \"--color-success\": \"oklch(74.722% 0.072 131.116)\", \"--color-success-content\": \"oklch(14.944% 0.014 131.116)\", \"--color-warning\": \"oklch(88.15% 0.14 87.722)\", \"--color-warning-content\": \"oklch(17.63% 0.028 87.722)\", \"--color-error\": \"oklch(77.318% 0.128 31.871)\", \"--color-error-content\": \"oklch(15.463% 0.025 31.871)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, silk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97% 0.0035 67.78)\", \"--color-base-200\": \"oklch(95% 0.0081 61.42)\", \"--color-base-300\": \"oklch(90% 0.0081 61.42)\", \"--color-base-content\": \"oklch(40% 0.0081 61.42)\", \"--color-primary\": \"oklch(23.27% 0.0249 284.3)\", \"--color-primary-content\": \"oklch(94.22% 0.2505 117.44)\", \"--color-secondary\": \"oklch(23.27% 0.0249 284.3)\", \"--color-secondary-content\": \"oklch(73.92% 0.2135 50.94)\", \"--color-accent\": \"oklch(23.27% 0.0249 284.3)\", \"--color-accent-content\": \"oklch(88.92% 0.2061 189.9)\", \"--color-neutral\": \"oklch(20% 0 0)\", \"--color-neutral-content\": \"oklch(80% 0.0081 61.42)\", \"--color-info\": \"oklch(80.39% 0.1148 241.68)\", \"--color-info-content\": \"oklch(30.39% 0.1148 241.68)\", \"--color-success\": \"oklch(83.92% 0.0901 136.87)\", \"--color-success-content\": \"oklch(23.92% 0.0901 136.87)\", \"--color-warning\": \"oklch(83.92% 0.1085 80)\", \"--color-warning-content\": \"oklch(43.92% 0.1085 80)\", \"--color-error\": \"oklch(75.1% 0.1814 22.37)\", \"--color-error-content\": \"oklch(35.1% 0.1814 22.37)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"0\" }, sunset: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(22% 0.019 237.69)\", \"--color-base-200\": \"oklch(20% 0.019 237.69)\", \"--color-base-300\": \"oklch(18% 0.019 237.69)\", \"--color-base-content\": \"oklch(77.383% 0.043 245.096)\", \"--color-primary\": \"oklch(74.703% 0.158 39.947)\", \"--color-primary-content\": \"oklch(14.94% 0.031 39.947)\", \"--color-secondary\": \"oklch(72.537% 0.177 2.72)\", \"--color-secondary-content\": \"oklch(14.507% 0.035 2.72)\", \"--color-accent\": \"oklch(71.294% 0.166 299.844)\", \"--color-accent-content\": \"oklch(14.258% 0.033 299.844)\", \"--color-neutral\": \"oklch(26% 0.019 237.69)\", \"--color-neutral-content\": \"oklch(70% 0.019 237.69)\", \"--color-info\": \"oklch(85.559% 0.085 206.015)\", \"--color-info-content\": \"oklch(17.111% 0.017 206.015)\", \"--color-success\": \"oklch(85.56% 0.085 144.778)\", \"--color-success-content\": \"oklch(17.112% 0.017 144.778)\", \"--color-warning\": \"oklch(85.569% 0.084 74.427)\", \"--color-warning-content\": \"oklch(17.113% 0.016 74.427)\", \"--color-error\": \"oklch(85.511% 0.078 16.886)\", \"--color-error-content\": \"oklch(17.102% 0.015 16.886)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, synthwave: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(15% 0.09 281.288)\", \"--color-base-200\": \"oklch(20% 0.09 281.288)\", \"--color-base-300\": \"oklch(25% 0.09 281.288)\", \"--color-base-content\": \"oklch(78% 0.115 274.713)\", \"--color-primary\": \"oklch(71% 0.202 349.761)\", \"--color-primary-content\": \"oklch(28% 0.109 3.907)\", \"--color-secondary\": \"oklch(82% 0.111 230.318)\", \"--color-secondary-content\": \"oklch(29% 0.066 243.157)\", \"--color-accent\": \"oklch(75% 0.183 55.934)\", \"--color-accent-content\": \"oklch(26% 0.079 36.259)\", \"--color-neutral\": \"oklch(45% 0.24 277.023)\", \"--color-neutral-content\": \"oklch(87% 0.065 274.039)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(77% 0.152 181.912)\", \"--color-success-content\": \"oklch(27% 0.046 192.524)\", \"--color-warning\": \"oklch(90% 0.182 98.111)\", \"--color-warning-content\": \"oklch(42% 0.095 57.708)\", \"--color-error\": \"oklch(73.7% 0.121 32.639)\", \"--color-error-content\": \"oklch(23.501% 0.096 290.329)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, dim: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(30.857% 0.023 264.149)\", \"--color-base-200\": \"oklch(28.036% 0.019 264.182)\", \"--color-base-300\": \"oklch(26.346% 0.018 262.177)\", \"--color-base-content\": \"oklch(82.901% 0.031 222.959)\", \"--color-primary\": \"oklch(86.133% 0.141 139.549)\", \"--color-primary-content\": \"oklch(17.226% 0.028 139.549)\", \"--color-secondary\": \"oklch(73.375% 0.165 35.353)\", \"--color-secondary-content\": \"oklch(14.675% 0.033 35.353)\", \"--color-accent\": \"oklch(74.229% 0.133 311.379)\", \"--color-accent-content\": \"oklch(14.845% 0.026 311.379)\", \"--color-neutral\": \"oklch(24.731% 0.02 264.094)\", \"--color-neutral-content\": \"oklch(82.901% 0.031 222.959)\", \"--color-info\": \"oklch(86.078% 0.142 206.182)\", \"--color-info-content\": \"oklch(17.215% 0.028 206.182)\", \"--color-success\": \"oklch(86.171% 0.142 166.534)\", \"--color-success-content\": \"oklch(17.234% 0.028 166.534)\", \"--color-warning\": \"oklch(86.163% 0.142 94.818)\", \"--color-warning-content\": \"oklch(17.232% 0.028 94.818)\", \"--color-error\": \"oklch(82.418% 0.099 33.756)\", \"--color-error-content\": \"oklch(16.483% 0.019 33.756)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, abyss: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20% 0.08 209)\", \"--color-base-200\": \"oklch(15% 0.08 209)\", \"--color-base-300\": \"oklch(10% 0.08 209)\", \"--color-base-content\": \"oklch(90% 0.076 70.697)\", \"--color-primary\": \"oklch(92% 0.2653 125)\", \"--color-primary-content\": \"oklch(50% 0.2653 125)\", \"--color-secondary\": \"oklch(83.27% 0.0764 298.3)\", \"--color-secondary-content\": \"oklch(43.27% 0.0764 298.3)\", \"--color-accent\": \"oklch(43% 0 0)\", \"--color-accent-content\": \"oklch(98% 0 0)\", \"--color-neutral\": \"oklch(30% 0.08 209)\", \"--color-neutral-content\": \"oklch(90% 0.076 70.697)\", \"--color-info\": \"oklch(74% 0.16 232.661)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(79% 0.209 151.711)\", \"--color-success-content\": \"oklch(26% 0.065 152.934)\", \"--color-warning\": \"oklch(84.8% 0.1962 84.62)\", \"--color-warning-content\": \"oklch(44.8% 0.1962 84.62)\", \"--color-error\": \"oklch(65% 0.1985 24.22)\", \"--color-error-content\": \"oklch(27% 0.1985 24.22)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.5rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, forest: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20.84% 0.008 17.911)\", \"--color-base-200\": \"oklch(18.522% 0.007 17.911)\", \"--color-base-300\": \"oklch(16.203% 0.007 17.911)\", \"--color-base-content\": \"oklch(83.768% 0.001 17.911)\", \"--color-primary\": \"oklch(68.628% 0.185 148.958)\", \"--color-primary-content\": \"oklch(0% 0 0)\", \"--color-secondary\": \"oklch(69.776% 0.135 168.327)\", \"--color-secondary-content\": \"oklch(13.955% 0.027 168.327)\", \"--color-accent\": \"oklch(70.628% 0.119 185.713)\", \"--color-accent-content\": \"oklch(14.125% 0.023 185.713)\", \"--color-neutral\": \"oklch(30.698% 0.039 171.364)\", \"--color-neutral-content\": \"oklch(86.139% 0.007 171.364)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, night: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(20.768% 0.039 265.754)\", \"--color-base-200\": \"oklch(19.314% 0.037 265.754)\", \"--color-base-300\": \"oklch(17.86% 0.034 265.754)\", \"--color-base-content\": \"oklch(84.153% 0.007 265.754)\", \"--color-primary\": \"oklch(75.351% 0.138 232.661)\", \"--color-primary-content\": \"oklch(15.07% 0.027 232.661)\", \"--color-secondary\": \"oklch(68.011% 0.158 276.934)\", \"--color-secondary-content\": \"oklch(13.602% 0.031 276.934)\", \"--color-accent\": \"oklch(72.36% 0.176 350.048)\", \"--color-accent-content\": \"oklch(14.472% 0.035 350.048)\", \"--color-neutral\": \"oklch(27.949% 0.036 260.03)\", \"--color-neutral-content\": \"oklch(85.589% 0.007 260.03)\", \"--color-info\": \"oklch(68.455% 0.148 237.251)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(78.452% 0.132 181.911)\", \"--color-success-content\": \"oklch(15.69% 0.026 181.911)\", \"--color-warning\": \"oklch(83.242% 0.139 82.95)\", \"--color-warning-content\": \"oklch(16.648% 0.027 82.95)\", \"--color-error\": \"oklch(71.785% 0.17 13.118)\", \"--color-error-content\": \"oklch(14.357% 0.034 13.118)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, caramellatte: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(98% 0.016 73.684)\", \"--color-base-200\": \"oklch(95% 0.038 75.164)\", \"--color-base-300\": \"oklch(90% 0.076 70.697)\", \"--color-base-content\": \"oklch(40% 0.123 38.172)\", \"--color-primary\": \"oklch(0% 0 0)\", \"--color-primary-content\": \"oklch(100% 0 0)\", \"--color-secondary\": \"oklch(22.45% 0.075 37.85)\", \"--color-secondary-content\": \"oklch(90% 0.076 70.697)\", \"--color-accent\": \"oklch(46.44% 0.111 37.85)\", \"--color-accent-content\": \"oklch(90% 0.076 70.697)\", \"--color-neutral\": \"oklch(55% 0.195 38.402)\", \"--color-neutral-content\": \"oklch(98% 0.016 73.684)\", \"--color-info\": \"oklch(42% 0.199 265.638)\", \"--color-info-content\": \"oklch(90% 0.076 70.697)\", \"--color-success\": \"oklch(43% 0.095 166.913)\", \"--color-success-content\": \"oklch(90% 0.076 70.697)\", \"--color-warning\": \"oklch(82% 0.189 84.429)\", \"--color-warning-content\": \"oklch(41% 0.112 45.904)\", \"--color-error\": \"oklch(70% 0.191 22.216)\", \"--color-error-content\": \"oklch(39% 0.141 25.723)\", \"--radius-selector\": \"2rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"1\" }, autumn: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(95.814% 0 0)\", \"--color-base-200\": \"oklch(89.107% 0 0)\", \"--color-base-300\": \"oklch(82.4% 0 0)\", \"--color-base-content\": \"oklch(19.162% 0 0)\", \"--color-primary\": \"oklch(40.723% 0.161 17.53)\", \"--color-primary-content\": \"oklch(88.144% 0.032 17.53)\", \"--color-secondary\": \"oklch(61.676% 0.169 23.865)\", \"--color-secondary-content\": \"oklch(12.335% 0.033 23.865)\", \"--color-accent\": \"oklch(73.425% 0.094 60.729)\", \"--color-accent-content\": \"oklch(14.685% 0.018 60.729)\", \"--color-neutral\": \"oklch(54.367% 0.037 51.902)\", \"--color-neutral-content\": \"oklch(90.873% 0.007 51.902)\", \"--color-info\": \"oklch(69.224% 0.097 207.284)\", \"--color-info-content\": \"oklch(13.844% 0.019 207.284)\", \"--color-success\": \"oklch(60.995% 0.08 174.616)\", \"--color-success-content\": \"oklch(12.199% 0.016 174.616)\", \"--color-warning\": \"oklch(70.081% 0.164 56.844)\", \"--color-warning-content\": \"oklch(14.016% 0.032 56.844)\", \"--color-error\": \"oklch(53.07% 0.241 24.16)\", \"--color-error-content\": \"oklch(90.614% 0.048 24.16)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, emerald: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(35.519% 0.032 262.988)\", \"--color-primary\": \"oklch(76.662% 0.135 153.45)\", \"--color-primary-content\": \"oklch(33.387% 0.04 162.24)\", \"--color-secondary\": \"oklch(61.302% 0.202 261.294)\", \"--color-secondary-content\": \"oklch(100% 0 0)\", \"--color-accent\": \"oklch(72.772% 0.149 33.2)\", \"--color-accent-content\": \"oklch(0% 0 0)\", \"--color-neutral\": \"oklch(35.519% 0.032 262.988)\", \"--color-neutral-content\": \"oklch(98.462% 0.001 247.838)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, cupcake: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(97.788% 0.004 56.375)\", \"--color-base-200\": \"oklch(93.982% 0.007 61.449)\", \"--color-base-300\": \"oklch(91.586% 0.006 53.44)\", \"--color-base-content\": \"oklch(23.574% 0.066 313.189)\", \"--color-primary\": \"oklch(85% 0.138 181.071)\", \"--color-primary-content\": \"oklch(43% 0.078 188.216)\", \"--color-secondary\": \"oklch(89% 0.061 343.231)\", \"--color-secondary-content\": \"oklch(45% 0.187 3.815)\", \"--color-accent\": \"oklch(90% 0.076 70.697)\", \"--color-accent-content\": \"oklch(47% 0.157 37.304)\", \"--color-neutral\": \"oklch(27% 0.006 286.033)\", \"--color-neutral-content\": \"oklch(92% 0.004 286.32)\", \"--color-info\": \"oklch(68% 0.169 237.323)\", \"--color-info-content\": \"oklch(29% 0.066 243.157)\", \"--color-success\": \"oklch(69% 0.17 162.48)\", \"--color-success-content\": \"oklch(26% 0.051 172.552)\", \"--color-warning\": \"oklch(79% 0.184 86.047)\", \"--color-warning-content\": \"oklch(28% 0.066 53.813)\", \"--color-error\": \"oklch(64% 0.246 16.439)\", \"--color-error-content\": \"oklch(27% 0.105 12.094)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"2rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"2px\", \"--depth\": \"1\", \"--noise\": \"0\" }, cmyk: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(95% 0 0)\", \"--color-base-300\": \"oklch(90% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(71.772% 0.133 239.443)\", \"--color-primary-content\": \"oklch(14.354% 0.026 239.443)\", \"--color-secondary\": \"oklch(64.476% 0.202 359.339)\", \"--color-secondary-content\": \"oklch(12.895% 0.04 359.339)\", \"--color-accent\": \"oklch(94.228% 0.189 105.306)\", \"--color-accent-content\": \"oklch(18.845% 0.037 105.306)\", \"--color-neutral\": \"oklch(21.778% 0 0)\", \"--color-neutral-content\": \"oklch(84.355% 0 0)\", \"--color-info\": \"oklch(68.475% 0.094 217.284)\", \"--color-info-content\": \"oklch(13.695% 0.018 217.284)\", \"--color-success\": \"oklch(46.949% 0.162 321.406)\", \"--color-success-content\": \"oklch(89.389% 0.032 321.406)\", \"--color-warning\": \"oklch(71.236% 0.159 52.023)\", \"--color-warning-content\": \"oklch(14.247% 0.031 52.023)\", \"--color-error\": \"oklch(62.013% 0.208 28.717)\", \"--color-error-content\": \"oklch(12.402% 0.041 28.717)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, business: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(24.353% 0 0)\", \"--color-base-200\": \"oklch(22.648% 0 0)\", \"--color-base-300\": \"oklch(20.944% 0 0)\", \"--color-base-content\": \"oklch(84.87% 0 0)\", \"--color-primary\": \"oklch(41.703% 0.099 251.473)\", \"--color-primary-content\": \"oklch(88.34% 0.019 251.473)\", \"--color-secondary\": \"oklch(64.092% 0.027 229.389)\", \"--color-secondary-content\": \"oklch(12.818% 0.005 229.389)\", \"--color-accent\": \"oklch(67.271% 0.167 35.791)\", \"--color-accent-content\": \"oklch(13.454% 0.033 35.791)\", \"--color-neutral\": \"oklch(27.441% 0.013 253.041)\", \"--color-neutral-content\": \"oklch(85.488% 0.002 253.041)\", \"--color-info\": \"oklch(62.616% 0.143 240.033)\", \"--color-info-content\": \"oklch(12.523% 0.028 240.033)\", \"--color-success\": \"oklch(70.226% 0.094 156.596)\", \"--color-success-content\": \"oklch(14.045% 0.018 156.596)\", \"--color-warning\": \"oklch(77.482% 0.115 81.519)\", \"--color-warning-content\": \"oklch(15.496% 0.023 81.519)\", \"--color-error\": \"oklch(51.61% 0.146 29.674)\", \"--color-error-content\": \"oklch(90.322% 0.029 29.674)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, winter: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97.466% 0.011 259.822)\", \"--color-base-300\": \"oklch(93.268% 0.016 262.751)\", \"--color-base-content\": \"oklch(41.886% 0.053 255.824)\", \"--color-primary\": \"oklch(56.86% 0.255 257.57)\", \"--color-primary-content\": \"oklch(91.372% 0.051 257.57)\", \"--color-secondary\": \"oklch(42.551% 0.161 282.339)\", \"--color-secondary-content\": \"oklch(88.51% 0.032 282.339)\", \"--color-accent\": \"oklch(59.939% 0.191 335.171)\", \"--color-accent-content\": \"oklch(11.988% 0.038 335.171)\", \"--color-neutral\": \"oklch(19.616% 0.063 257.651)\", \"--color-neutral-content\": \"oklch(83.923% 0.012 257.651)\", \"--color-info\": \"oklch(88.127% 0.085 214.515)\", \"--color-info-content\": \"oklch(17.625% 0.017 214.515)\", \"--color-success\": \"oklch(80.494% 0.077 197.823)\", \"--color-success-content\": \"oklch(16.098% 0.015 197.823)\", \"--color-warning\": \"oklch(89.172% 0.045 71.47)\", \"--color-warning-content\": \"oklch(17.834% 0.009 71.47)\", \"--color-error\": \"oklch(73.092% 0.11 20.076)\", \"--color-error-content\": \"oklch(14.618% 0.022 20.076)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" }, halloween: { \"color-scheme\": \"dark\", \"--color-base-100\": \"oklch(21% 0.006 56.043)\", \"--color-base-200\": \"oklch(14% 0.004 49.25)\", \"--color-base-300\": \"oklch(0% 0 0)\", \"--color-base-content\": \"oklch(84.955% 0 0)\", \"--color-primary\": \"oklch(77.48% 0.204 60.62)\", \"--color-primary-content\": \"oklch(19.693% 0.004 196.779)\", \"--color-secondary\": \"oklch(45.98% 0.248 305.03)\", \"--color-secondary-content\": \"oklch(89.196% 0.049 305.03)\", \"--color-accent\": \"oklch(64.8% 0.223 136.073)\", \"--color-accent-content\": \"oklch(0% 0 0)\", \"--color-neutral\": \"oklch(24.371% 0.046 65.681)\", \"--color-neutral-content\": \"oklch(84.874% 0.009 65.681)\", \"--color-info\": \"oklch(54.615% 0.215 262.88)\", \"--color-info-content\": \"oklch(90.923% 0.043 262.88)\", \"--color-success\": \"oklch(62.705% 0.169 149.213)\", \"--color-success-content\": \"oklch(12.541% 0.033 149.213)\", \"--color-warning\": \"oklch(66.584% 0.157 58.318)\", \"--color-warning-content\": \"oklch(13.316% 0.031 58.318)\", \"--color-error\": \"oklch(65.72% 0.199 27.33)\", \"--color-error-content\": \"oklch(13.144% 0.039 27.33)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, fantasy: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(93% 0 0)\", \"--color-base-300\": \"oklch(86% 0 0)\", \"--color-base-content\": \"oklch(27.807% 0.029 256.847)\", \"--color-primary\": \"oklch(37.45% 0.189 325.02)\", \"--color-primary-content\": \"oklch(87.49% 0.037 325.02)\", \"--color-secondary\": \"oklch(53.92% 0.162 241.36)\", \"--color-secondary-content\": \"oklch(90.784% 0.032 241.36)\", \"--color-accent\": \"oklch(75.98% 0.204 56.72)\", \"--color-accent-content\": \"oklch(15.196% 0.04 56.72)\", \"--color-neutral\": \"oklch(27.807% 0.029 256.847)\", \"--color-neutral-content\": \"oklch(85.561% 0.005 256.847)\", \"--color-info\": \"oklch(72.06% 0.191 231.6)\", \"--color-info-content\": \"oklch(0% 0 0)\", \"--color-success\": \"oklch(64.8% 0.15 160)\", \"--color-success-content\": \"oklch(0% 0 0)\", \"--color-warning\": \"oklch(84.71% 0.199 83.87)\", \"--color-warning-content\": \"oklch(0% 0 0)\", \"--color-error\": \"oklch(71.76% 0.221 22.18)\", \"--color-error-content\": \"oklch(0% 0 0)\", \"--radius-selector\": \"1rem\", \"--radius-field\": \"0.5rem\", \"--radius-box\": \"1rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"1\", \"--noise\": \"0\" }, wireframe: { \"color-scheme\": \"light\", \"--color-base-100\": \"oklch(100% 0 0)\", \"--color-base-200\": \"oklch(97% 0 0)\", \"--color-base-300\": \"oklch(94% 0 0)\", \"--color-base-content\": \"oklch(20% 0 0)\", \"--color-primary\": \"oklch(87% 0 0)\", \"--color-primary-content\": \"oklch(26% 0 0)\", \"--color-secondary\": \"oklch(87% 0 0)\", \"--color-secondary-content\": \"oklch(26% 0 0)\", \"--color-accent\": \"oklch(87% 0 0)\", \"--color-accent-content\": \"oklch(26% 0 0)\", \"--color-neutral\": \"oklch(87% 0 0)\", \"--color-neutral-content\": \"oklch(26% 0 0)\", \"--color-info\": \"oklch(44% 0.11 240.79)\", \"--color-info-content\": \"oklch(90% 0.058 230.902)\", \"--color-success\": \"oklch(43% 0.095 166.913)\", \"--color-success-content\": \"oklch(90% 0.093 164.15)\", \"--color-warning\": \"oklch(47% 0.137 46.201)\", \"--color-warning-content\": \"oklch(92% 0.12 95.746)\", \"--color-error\": \"oklch(44% 0.177 26.899)\", \"--color-error-content\": \"oklch(88% 0.062 18.334)\", \"--radius-selector\": \"0rem\", \"--radius-field\": \"0.25rem\", \"--radius-box\": \"0.25rem\", \"--size-selector\": \"0.25rem\", \"--size-field\": \"0.25rem\", \"--border\": \"1px\", \"--depth\": \"0\", \"--noise\": \"0\" } };\n\n// packages/daisyui/base/rootscrolllock/object.js\nvar object_default2 = { ':root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not([class*=\"drawer-open\"]) > .drawer-toggle:checked )': { overflow: \"hidden\" } };\n\n// packages/daisyui/functions/addPrefix.js\nvar defaultExcludedPrefixes = [\"color-\", \"size-\", \"radius-\", \"border\", \"depth\", \"noise\"];\nvar shouldExcludeVariable = (variableName, excludedPrefixes) => {\n  if (variableName.startsWith(\"tw\")) {\n    return true;\n  }\n  return excludedPrefixes.some((excludedPrefix) => variableName.startsWith(excludedPrefix));\n};\nvar prefixVariable = (variableName, prefix, excludedPrefixes) => {\n  if (shouldExcludeVariable(variableName, excludedPrefixes)) {\n    return variableName;\n  }\n  return `${prefix}${variableName}`;\n};\nvar getPrefixedSelector = (selector, prefix) => {\n  if (!selector.startsWith(\".\"))\n    return selector;\n  return `.${prefix}${selector.slice(1)}`;\n};\nvar getPrefixedKey = (key, prefix, excludedPrefixes) => {\n  const prefixAmpDot = prefix ? `&.${prefix}` : \"\";\n  if (!prefix)\n    return key;\n  if (key.startsWith(\"--\")) {\n    const variableName = key.slice(2);\n    return `--${prefixVariable(variableName, prefix, excludedPrefixes)}`;\n  }\n  if (key.startsWith(\"@\") || key.startsWith(\"[\")) {\n    return key;\n  }\n  if (key.startsWith(\"&\")) {\n    if (key.match(/:[a-z-]+\\(/)) {\n      return key.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n    }\n    if (key.startsWith(\"&.\")) {\n      return `${prefixAmpDot}${key.slice(2)}`;\n    }\n    return key.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n  }\n  if (key.startsWith(\":\")) {\n    return key.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n  }\n  if (key.includes(\".\") && !key.includes(\" \") && !key.includes(\">\") && !key.includes(\"+\") && !key.includes(\"~\")) {\n    return key.split(\".\").filter(Boolean).map((part) => prefix + part).join(\".\").replace(/^/, \".\");\n  }\n  if (key.includes(\">\") || key.includes(\"+\") || key.includes(\"~\")) {\n    if (key.includes(\",\")) {\n      return key.split(/\\s*,\\s*/).map((part) => {\n        return part.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n      }).join(\", \");\n    }\n    let processedKey = key.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n    if (processedKey.startsWith(\">\") || processedKey.startsWith(\"+\") || processedKey.startsWith(\"~\")) {\n      processedKey = ` ${processedKey}`;\n    }\n    return processedKey;\n  }\n  if (key.includes(\" \")) {\n    return key.split(/\\s+/).map((part) => {\n      if (part.startsWith(\".\")) {\n        return getPrefixedSelector(part, prefix);\n      }\n      return part;\n    }).join(\" \");\n  }\n  if (key.includes(\":\")) {\n    const [selector, ...pseudo] = key.split(\":\");\n    if (selector.startsWith(\".\")) {\n      return `${getPrefixedSelector(selector, prefix)}:${pseudo.join(\":\")}`;\n    }\n    return key.replace(/\\.([\\w-]+)/g, `.${prefix}$1`);\n  }\n  if (key.startsWith(\".\")) {\n    return getPrefixedSelector(key, prefix);\n  }\n  return key;\n};\nvar processArrayValue = (value, prefix, excludedPrefixes) => {\n  return value.map((item) => {\n    if (typeof item === \"string\") {\n      if (item.startsWith(\".\")) {\n        return prefix ? `.${prefix}${item.slice(1)}` : item;\n      }\n      return processStringValue(item, prefix, excludedPrefixes);\n    }\n    return item;\n  });\n};\nvar processStringValue = (value, prefix, excludedPrefixes) => {\n  if (prefix === 0)\n    return value;\n  return value.replace(/var\\(--([^)]+)\\)/g, (match, variableName) => {\n    if (shouldExcludeVariable(variableName, excludedPrefixes)) {\n      return match;\n    }\n    return `var(--${prefix}${variableName})`;\n  });\n};\nvar processValue = (value, prefix, excludedPrefixes) => {\n  if (Array.isArray(value)) {\n    return processArrayValue(value, prefix, excludedPrefixes);\n  } else if (typeof value === \"object\" && value !== null) {\n    return addPrefix(value, prefix, excludedPrefixes);\n  } else if (typeof value === \"string\") {\n    return processStringValue(value, prefix, excludedPrefixes);\n  } else {\n    return value;\n  }\n};\nvar addPrefix = (obj, prefix, excludedPrefixes = defaultExcludedPrefixes) => {\n  return Object.entries(obj).reduce((result, [key, value]) => {\n    const newKey = getPrefixedKey(key, prefix, excludedPrefixes);\n    result[newKey] = processValue(value, prefix, excludedPrefixes);\n    return result;\n  }, {});\n};\n\n// packages/daisyui/base/rootscrolllock/index.js\nvar rootscrolllock_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedrootscrolllock = addPrefix(object_default2, prefix);\n  addBase({ ...prefixedrootscrolllock });\n};\n\n// packages/daisyui/base/rootcolor/object.js\nvar object_default3 = { \":root, [data-theme]\": { \"background-color\": \"var(--root-bg, var(--color-base-100))\", color: \"var(--color-base-content)\" } };\n\n// packages/daisyui/base/rootcolor/index.js\nvar rootcolor_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedrootcolor = addPrefix(object_default3, prefix);\n  addBase({ ...prefixedrootcolor });\n};\n\n// packages/daisyui/base/scrollbar/object.js\nvar object_default4 = { \":root\": { \"scrollbar-color\": \"color-mix(in oklch, currentColor 35%, #0000) #0000\" } };\n\n// packages/daisyui/base/scrollbar/index.js\nvar scrollbar_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedscrollbar = addPrefix(object_default4, prefix);\n  addBase({ ...prefixedscrollbar });\n};\n\n// packages/daisyui/base/properties/object.js\nvar object_default5 = { \"@property --radialprogress\": { syntax: '\"<percentage>\"', inherits: \"true\", \"initial-value\": \"0%\" } };\n\n// packages/daisyui/base/properties/index.js\nvar properties_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedproperties = addPrefix(object_default5, prefix);\n  addBase({ ...prefixedproperties });\n};\n\n// packages/daisyui/base/rootscrollgutter/object.js\nvar object_default6 = { \":where( :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not(.drawer-open) > .drawer-toggle:checked ) )\": { \"scrollbar-gutter\": \"stable\", \"background-image\": \"linear-gradient(var(--color-base-100), var(--color-base-100))\", \"--root-bg\": \"color-mix(in srgb, var(--color-base-100), oklch(0% 0 0) 40%)\" }, \":where(.modal[open], .modal-open, .modal-toggle:checked + .modal):not(.modal-start, .modal-end)\": { \"scrollbar-gutter\": \"stable\" } };\n\n// packages/daisyui/base/rootscrollgutter/index.js\nvar rootscrollgutter_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedrootscrollgutter = addPrefix(object_default6, prefix);\n  addBase({ ...prefixedrootscrollgutter });\n};\n\n// packages/daisyui/base/svg/object.js\nvar object_default7 = { \":root\": { \"--fx-noise\": `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E\")` }, \".chat\": { \"--mask-chat\": `url(\"data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e\")` } };\n\n// packages/daisyui/base/svg/index.js\nvar svg_default = ({ addBase, prefix = \"\" }) => {\n  const prefixedsvg = addPrefix(object_default7, prefix);\n  addBase({ ...prefixedsvg });\n};\n\n// packages/daisyui/components/drawer/object.js\nvar object_default8 = { \".drawer\": { position: \"relative\", display: \"grid\", width: \"100%\", \"grid-auto-columns\": \"max-content auto\" }, \".drawer-content\": { \"grid-column-start\": \"2\", \"grid-row-start\": \"1\", \"min-width\": \"calc(0.25rem * 0)\" }, \".drawer-side\": { \"pointer-events\": \"none\", visibility: \"hidden\", position: \"fixed\", \"inset-inline-start\": \"calc(0.25rem * 0)\", top: \"calc(0.25rem * 0)\", \"z-index\": 1, \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", display: \"grid\", width: \"100%\", \"grid-template-columns\": \"repeat(1, minmax(0, 1fr))\", \"grid-template-rows\": \"repeat(1, minmax(0, 1fr))\", \"align-items\": \"flex-start\", \"justify-items\": \"start\", \"overflow-x\": \"hidden\", \"overflow-y\": \"hidden\", \"overscroll-behavior\": \"contain\", opacity: \"0%\", transition: \"opacity 0.2s ease-out 0.1s allow-discrete, visibility 0.3s ease-out 0.1s allow-discrete\", height: [\"100vh\", \"100dvh\"], \"> .drawer-overlay\": { position: \"sticky\", top: \"calc(0.25rem * 0)\", cursor: \"pointer\", \"place-self\": \"stretch\", \"background-color\": \"oklch(0% 0 0 / 40%)\" }, \"> *\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\" }, \"> *:not(.drawer-overlay)\": { \"will-change\": \"transform\", transition: \"translate 0.3s ease-out\", translate: \"-100%\", '[dir=\"rtl\"] &': { translate: \"100%\" } } }, \".drawer-toggle\": { position: \"fixed\", height: \"calc(0.25rem * 0)\", width: \"calc(0.25rem * 0)\", appearance: \"none\", opacity: \"0%\", \"&:checked\": { \"& ~ .drawer-side\": { \"pointer-events\": \"auto\", visibility: \"visible\", \"overflow-y\": \"auto\", opacity: \"100%\", \"& > *:not(.drawer-overlay)\": { translate: \"0%\" } } }, \"&:focus-visible ~ .drawer-content label.drawer-button\": { outline: \"2px solid\", \"outline-offset\": \"2px\" } }, \".drawer-end\": { \"grid-auto-columns\": \"auto max-content\", \"> .drawer-toggle\": { \"& ~ .drawer-content\": { \"grid-column-start\": \"1\" }, \"& ~ .drawer-side\": { \"grid-column-start\": \"2\", \"justify-items\": \"end\" }, \"& ~ .drawer-side > *:not(.drawer-overlay)\": { translate: \"100%\", '[dir=\"rtl\"] &': { translate: \"-100%\" } }, \"&:checked ~ .drawer-side > *:not(.drawer-overlay)\": { translate: \"0%\" } } }, \".drawer-open\": { \"> .drawer-side\": { \"overflow-y\": \"auto\" }, \"> .drawer-toggle\": { display: \"none\", \"& ~ .drawer-side\": { \"pointer-events\": \"auto\", visibility: \"visible\", position: \"sticky\", display: \"block\", width: \"auto\", \"overscroll-behavior\": \"auto\", opacity: \"100%\", \"& > .drawer-overlay\": { cursor: \"default\", \"background-color\": \"transparent\" }, \"& > *:not(.drawer-overlay)\": { translate: \"0%\", '[dir=\"rtl\"] &': { translate: \"0%\" } } }, \"&:checked ~ .drawer-side\": { \"pointer-events\": \"auto\", visibility: \"visible\" } } } };\n\n// packages/daisyui/components/drawer/index.js\nvar drawer_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixeddrawer = addPrefix(object_default8, prefix);\n  addComponents({ ...prefixeddrawer });\n};\n\n// packages/daisyui/components/link/object.js\nvar object_default9 = { \".link\": { cursor: \"pointer\", \"text-decoration-line\": \"underline\", \"&:focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:focus-visible\": { outline: \"2px solid currentColor\", \"outline-offset\": \"2px\" } }, \".link-hover\": { \"text-decoration-line\": \"none\", \"&:hover\": { \"@media (hover: hover)\": { \"text-decoration-line\": \"underline\" } } }, \".link-primary\": { color: \"var(--color-primary)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-primary) 80%, #000)\" } } }, \".link-secondary\": { color: \"var(--color-secondary)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-secondary) 80%, #000)\" } } }, \".link-accent\": { color: \"var(--color-accent)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-accent) 80%, #000)\" } } }, \".link-neutral\": { color: \"var(--color-neutral)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-neutral) 80%, #000)\" } } }, \".link-success\": { color: \"var(--color-success)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-success) 80%, #000)\" } } }, \".link-info\": { color: \"var(--color-info)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-info) 80%, #000)\" } } }, \".link-warning\": { color: \"var(--color-warning)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-warning) 80%, #000)\" } } }, \".link-error\": { color: \"var(--color-error)\", \"@media (hover: hover)\": { \"&:hover\": { color: \"color-mix(in oklab, var(--color-error) 80%, #000)\" } } } };\n\n// packages/daisyui/components/link/index.js\nvar link_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedlink = addPrefix(object_default9, prefix);\n  addComponents({ ...prefixedlink });\n};\n\n// packages/daisyui/components/stat/object.js\nvar object_default10 = { \".stats\": { position: \"relative\", display: \"inline-grid\", \"grid-auto-flow\": \"column\", \"overflow-x\": \"auto\", \"border-radius\": \"var(--radius-box)\" }, \".stat\": { display: \"inline-grid\", width: \"100%\", \"column-gap\": \"calc(0.25rem * 4)\", \"padding-inline\": \"calc(0.25rem * 6)\", \"padding-block\": \"calc(0.25rem * 4)\", \"grid-template-columns\": \"repeat(1, 1fr)\", \"&:not(:last-child)\": { \"border-inline-end\": \"var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)\", \"border-block-end\": \"none\" } }, \".stat-figure\": { \"grid-column-start\": \"2\", \"grid-row\": \"span 3 / span 3\", \"grid-row-start\": \"1\", \"place-self\": \"center\", \"justify-self\": \"flex-end\" }, \".stat-title\": { \"grid-column-start\": \"1\", \"white-space\": \"nowrap\", color: \"color-mix(in oklab, var(--color-base-content) 60%, transparent)\", \"font-size\": \"0.75rem\" }, \".stat-value\": { \"grid-column-start\": \"1\", \"white-space\": \"nowrap\", \"font-size\": \"2rem\", \"font-weight\": 800 }, \".stat-desc\": { \"grid-column-start\": \"1\", \"white-space\": \"nowrap\", color: \"color-mix(in oklab, var(--color-base-content) 60%, transparent)\", \"font-size\": \"0.75rem\" }, \".stat-actions\": { \"grid-column-start\": \"1\", \"white-space\": \"nowrap\" }, \".stats-horizontal\": { \"grid-auto-flow\": \"column\", \"overflow-x\": \"auto\", \".stat:not(:last-child)\": { \"border-inline-end\": \"var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)\", \"border-block-end\": \"none\" } }, \".stats-vertical\": { \"grid-auto-flow\": \"row\", \"overflow-y\": \"auto\", \".stat:not(:last-child)\": { \"border-inline-end\": \"none\", \"border-block-end\": \"var(--border) dashed color-mix(in oklab, currentColor 10%, #0000)\" } } };\n\n// packages/daisyui/components/stat/index.js\nvar stat_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedstat = addPrefix(object_default10, prefix);\n  addComponents({ ...prefixedstat });\n};\n\n// packages/daisyui/components/carousel/object.js\nvar object_default11 = { \".carousel\": { display: \"inline-flex\", \"overflow-x\": \"scroll\", \"scroll-snap-type\": \"x mandatory\", \"scroll-behavior\": \"smooth\", \"scrollbar-width\": \"none\", \"&::-webkit-scrollbar\": { display: \"none\" } }, \".carousel-vertical\": { \"flex-direction\": \"column\", \"overflow-y\": \"scroll\", \"scroll-snap-type\": \"y mandatory\" }, \".carousel-horizontal\": { \"flex-direction\": \"row\", \"overflow-x\": \"scroll\", \"scroll-snap-type\": \"x mandatory\" }, \".carousel-item\": { \"box-sizing\": \"content-box\", display: \"flex\", flex: \"none\", \"scroll-snap-align\": \"start\" }, \".carousel-start\": { \".carousel-item\": { \"scroll-snap-align\": \"start\" } }, \".carousel-center\": { \".carousel-item\": { \"scroll-snap-align\": \"center\" } }, \".carousel-end\": { \".carousel-item\": { \"scroll-snap-align\": \"end\" } } };\n\n// packages/daisyui/components/carousel/index.js\nvar carousel_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcarousel = addPrefix(object_default11, prefix);\n  addComponents({ ...prefixedcarousel });\n};\n\n// packages/daisyui/components/divider/object.js\nvar object_default12 = { \".divider\": { display: \"flex\", height: \"calc(0.25rem * 4)\", \"flex-direction\": \"row\", \"align-items\": \"center\", \"align-self\": \"stretch\", \"white-space\": \"nowrap\", margin: \"var(--divider-m, 1rem 0)\", \"--divider-color\": \"color-mix(in oklab, var(--color-base-content) 10%, transparent)\", \"&:before, &:after\": { content: '\"\"', height: \"calc(0.25rem * 0.5)\", width: \"100%\", \"flex-grow\": 1, \"background-color\": \"var(--divider-color)\" }, \"@media print\": { \"&:before, &:after\": { border: \"0.5px solid\" } }, \"&:not(:empty)\": { gap: \"calc(0.25rem * 4)\" } }, \".divider-horizontal\": { \"--divider-m\": \"0 1rem\", \"&.divider\": { height: \"auto\", width: \"calc(0.25rem * 4)\", \"flex-direction\": \"column\", \"&:before\": { height: \"100%\", width: \"calc(0.25rem * 0.5)\" }, \"&:after\": { height: \"100%\", width: \"calc(0.25rem * 0.5)\" } } }, \".divider-vertical\": { \"--divider-m\": \"1rem 0\", \"&.divider\": { height: \"calc(0.25rem * 4)\", width: \"auto\", \"flex-direction\": \"row\", \"&:before\": { height: \"calc(0.25rem * 0.5)\", width: \"100%\" }, \"&:after\": { height: \"calc(0.25rem * 0.5)\", width: \"100%\" } } }, \".divider-neutral\": { \"&:before, &:after\": { \"background-color\": \"var(--color-neutral)\" } }, \".divider-primary\": { \"&:before, &:after\": { \"background-color\": \"var(--color-primary)\" } }, \".divider-secondary\": { \"&:before, &:after\": { \"background-color\": \"var(--color-secondary)\" } }, \".divider-accent\": { \"&:before, &:after\": { \"background-color\": \"var(--color-accent)\" } }, \".divider-success\": { \"&:before, &:after\": { \"background-color\": \"var(--color-success)\" } }, \".divider-warning\": { \"&:before, &:after\": { \"background-color\": \"var(--color-warning)\" } }, \".divider-info\": { \"&:before, &:after\": { \"background-color\": \"var(--color-info)\" } }, \".divider-error\": { \"&:before, &:after\": { \"background-color\": \"var(--color-error)\" } }, \".divider-start:before\": { display: \"none\" }, \".divider-end:after\": { display: \"none\" } };\n\n// packages/daisyui/components/divider/index.js\nvar divider_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixeddivider = addPrefix(object_default12, prefix);\n  addComponents({ ...prefixeddivider });\n};\n\n// packages/daisyui/components/mask/object.js\nvar object_default13 = { \".mask\": { display: \"inline-block\", \"vertical-align\": \"middle\", \"mask-size\": \"contain\", \"mask-repeat\": \"no-repeat\", \"mask-position\": \"center\" }, \".mask-half-1\": { \"mask-size\": \"200%\", \"mask-position\": [\"left\", \"left\"], '&:where(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *)': { \"mask-position\": \"right\" } }, \".mask-half-2\": { \"mask-size\": \"200%\", \"mask-position\": [\"right\", \"right\"], '&:where(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *)': { \"mask-position\": \"left\" } }, \".mask-squircle\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 0C20 0 0 20 0 100s20 100 100 100 100-20 100-100S180 0 100 0Z'/%3e%3c/svg%3e\")` }, \".mask-decagon\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='192' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 58.779 19.098 36.327 50v61.804l-36.327 50L96 200l-58.779-19.098-36.327-50V69.098l36.327-50z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-diamond\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m100 0 100 100-100 100L0 100z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-heart\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='200' height='185' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M100 184.606a15.384 15.384 0 0 1-8.653-2.678C53.565 156.28 37.205 138.695 28.182 127.7 8.952 104.264-.254 80.202.005 54.146.308 24.287 24.264 0 53.406 0c21.192 0 35.869 11.937 44.416 21.879a2.884 2.884 0 0 0 4.356 0C110.725 11.927 125.402 0 146.594 0c29.142 0 53.098 24.287 53.4 54.151.26 26.061-8.956 50.122-28.176 73.554-9.023 10.994-25.383 28.58-63.165 54.228a15.384 15.384 0 0 1-8.653 2.673Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e\")` }, \".mask-hexagon\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='182' height='201' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M.3 65.486c0-9.196 6.687-20.063 14.211-25.078l61.86-35.946c8.36-5.016 20.899-5.016 29.258 0l61.86 35.946c8.36 5.015 14.211 15.882 14.211 25.078v71.055c0 9.196-6.687 20.063-14.211 25.079l-61.86 35.945c-8.36 4.18-20.899 4.18-29.258 0L14.51 161.62C6.151 157.44.3 145.737.3 136.54V65.486Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e\")` }, \".mask-hexagon-2\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='200' height='182' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M64.786 181.4c-9.196 0-20.063-6.687-25.079-14.21L3.762 105.33c-5.016-8.36-5.016-20.9 0-29.259l35.945-61.86C44.723 5.851 55.59 0 64.786 0h71.055c9.196 0 20.063 6.688 25.079 14.211l35.945 61.86c4.18 8.36 4.18 20.899 0 29.258l-35.945 61.86c-4.18 8.36-15.883 14.211-25.079 14.211H64.786Z' fill='black' fill-rule='nonzero'/%3e%3c/svg%3e\")` }, \".mask-circle\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='200' height='200' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle fill='black' cx='100' cy='100' r='100' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-pentagon\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='192' height='181' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 0 95.106 69.098-36.327 111.804H37.22L.894 69.098z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-star\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 137.263-58.779 42.024 22.163-68.389L.894 68.481l72.476-.243L96 0l22.63 68.238 72.476.243-58.49 42.417 22.163 68.389z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-star-2\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='192' height='180' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m96 153.044-58.779 26.243 7.02-63.513L.894 68.481l63.117-13.01L96 0l31.989 55.472 63.117 13.01-43.347 47.292 7.02 63.513z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-triangle\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='174' height='149' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 148.476-86.603.185L43.86 74.423 87 0l43.14 74.423 43.463 74.238z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-triangle-2\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='174' height='150' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m87 .738 86.603-.184-43.463 74.238L87 149.214 43.86 74.792.397.554z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-triangle-3\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='m149.369 87.107.185 86.603-74.239-43.463L.893 87.107l74.422-43.14L149.554.505z' fill-rule='evenodd'/%3e%3c/svg%3e\")` }, \".mask-triangle-4\": { \"mask-image\": `url(\"data:image/svg+xml,%3csvg width='150' height='174' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M.631 87.107.446.505l74.239 43.462 74.422 43.14-74.422 43.14L.446 173.71z' fill-rule='evenodd'/%3e%3c/svg%3e\")` } };\n\n// packages/daisyui/components/mask/index.js\nvar mask_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedmask = addPrefix(object_default13, prefix);\n  addComponents({ ...prefixedmask });\n};\n\n// packages/daisyui/components/fieldset/object.js\nvar object_default14 = { \".fieldset\": { display: \"grid\", gap: \"calc(0.25rem * 1.5)\", \"padding-block\": \"calc(0.25rem * 1)\", \"font-size\": \"0.75rem\", \"grid-template-columns\": \"1fr\", \"grid-auto-rows\": \"max-content\" }, \".fieldset-legend\": { \"margin-bottom\": \"calc(0.25rem * -1)\", display: \"flex\", \"align-items\": \"center\", \"justify-content\": \"space-between\", gap: \"calc(0.25rem * 2)\", \"padding-block\": \"calc(0.25rem * 2)\", color: \"var(--color-base-content)\", \"font-weight\": 600 }, \".fieldset-label\": { display: \"flex\", \"align-items\": \"center\", gap: \"calc(0.25rem * 1.5)\", color: \"color-mix(in oklab, var(--color-base-content) 60%, transparent)\", \"&:has(input)\": { cursor: \"pointer\" } } };\n\n// packages/daisyui/components/fieldset/index.js\nvar fieldset_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedfieldset = addPrefix(object_default14, prefix);\n  addComponents({ ...prefixedfieldset });\n};\n\n// packages/daisyui/components/dropdown/object.js\nvar object_default15 = { \".dropdown\": { position: \"relative\", display: \"inline-block\", \"position-area\": \"var(--anchor-v, bottom) var(--anchor-h, span-right)\", \"& > *:not(summary):focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \".dropdown-content\": { position: \"absolute\" }, \"&:not(details, .dropdown-open, .dropdown-hover:hover, :focus-within)\": { \".dropdown-content\": { display: \"none\", \"transform-origin\": \"top\", opacity: \"0%\", scale: \"95%\" } }, \"&[popover], .dropdown-content\": { \"z-index\": 999, animation: \"dropdown 0.2s\", \"transition-property\": \"opacity, scale, display\", \"transition-behavior\": \"allow-discrete\", \"transition-duration\": \"0.2s\", \"transition-timing-function\": \"cubic-bezier(0.4, 0, 0.2, 1)\" }, \"@starting-style\": { \"&[popover], .dropdown-content\": { scale: \"95%\", opacity: 0 } }, \"&.dropdown-open, &:not(.dropdown-hover):focus, &:focus-within\": { \"> [tabindex]:first-child\": { \"pointer-events\": \"none\" }, \".dropdown-content\": { opacity: \"100%\" } }, \"&.dropdown-hover:hover\": { \".dropdown-content\": { opacity: \"100%\", scale: \"100%\" } }, \"&:is(details)\": { summary: { \"&::-webkit-details-marker\": { display: \"none\" } } }, \"&.dropdown-open, &:focus, &:focus-within\": { \".dropdown-content\": { scale: \"100%\" } }, \"&:where([popover])\": { background: \"#0000\" }, \"&[popover]\": { position: \"fixed\", color: \"inherit\", \"@supports not (position-area: bottom)\": { margin: \"auto\", \"&.dropdown-open:not(:popover-open)\": { display: \"none\", \"transform-origin\": \"top\", opacity: \"0%\", scale: \"95%\" }, \"&::backdrop\": { \"background-color\": \"color-mix(in oklab, #000 30%, #0000)\" } }, \"&:not(.dropdown-open, :popover-open)\": { display: \"none\", \"transform-origin\": \"top\", opacity: \"0%\", scale: \"95%\" } } }, \".dropdown-start\": { \"--anchor-h\": \"span-right\", \":where(.dropdown-content)\": { \"inset-inline-end\": \"auto\" }, \"&.dropdown-left\": { \"--anchor-h\": \"left\", \"--anchor-v\": \"span-bottom\", \".dropdown-content\": { top: \"calc(0.25rem * 0)\", bottom: \"auto\" } }, \"&.dropdown-right\": { \"--anchor-h\": \"right\", \"--anchor-v\": \"span-bottom\", \".dropdown-content\": { top: \"calc(0.25rem * 0)\", bottom: \"auto\" } } }, \".dropdown-center\": { \"--anchor-h\": \"center\", \":where(.dropdown-content)\": { \"inset-inline-end\": \"calc(1/2 * 100%)\", translate: \"50% 0\", '[dir=\"rtl\"] &': { translate: \"-50% 0\" } }, \"&.dropdown-left\": { \"--anchor-h\": \"left\", \"--anchor-v\": \"center\", \".dropdown-content\": { top: \"auto\", bottom: \"calc(1/2 * 100%)\", translate: \"0 50%\" } }, \"&.dropdown-right\": { \"--anchor-h\": \"right\", \"--anchor-v\": \"center\", \".dropdown-content\": { top: \"auto\", bottom: \"calc(1/2 * 100%)\", translate: \"0 50%\" } } }, \".dropdown-end\": { \"--anchor-h\": \"span-left\", \":where(.dropdown-content)\": { \"inset-inline-end\": \"calc(0.25rem * 0)\", translate: \"0 0\" }, \"&.dropdown-left\": { \"--anchor-h\": \"left\", \"--anchor-v\": \"span-top\", \".dropdown-content\": { top: \"auto\", bottom: \"calc(0.25rem * 0)\" } }, \"&.dropdown-right\": { \"--anchor-h\": \"right\", \"--anchor-v\": \"span-top\", \".dropdown-content\": { top: \"auto\", bottom: \"calc(0.25rem * 0)\" } } }, \".dropdown-left\": { \"--anchor-h\": \"left\", \"--anchor-v\": \"span-bottom\", \".dropdown-content\": { \"inset-inline-end\": \"100%\", top: \"calc(0.25rem * 0)\", bottom: \"auto\", \"transform-origin\": \"right\" } }, \".dropdown-right\": { \"--anchor-h\": \"right\", \"--anchor-v\": \"span-bottom\", \".dropdown-content\": { \"inset-inline-start\": \"100%\", top: \"calc(0.25rem * 0)\", bottom: \"auto\", \"transform-origin\": \"left\" } }, \".dropdown-bottom\": { \"--anchor-v\": \"bottom\", \".dropdown-content\": { top: \"100%\", bottom: \"auto\", \"transform-origin\": \"top\" } }, \".dropdown-top\": { \"--anchor-v\": \"top\", \".dropdown-content\": { top: \"auto\", bottom: \"100%\", \"transform-origin\": \"bottom\" } }, \"@keyframes dropdown\": { \"0%\": { opacity: 0 } } };\n\n// packages/daisyui/components/dropdown/index.js\nvar dropdown_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixeddropdown = addPrefix(object_default15, prefix);\n  addComponents({ ...prefixeddropdown });\n};\n\n// packages/daisyui/components/card/object.js\nvar object_default16 = { \".card\": { position: \"relative\", display: \"flex\", \"flex-direction\": \"column\", \"border-radius\": \"var(--radius-box)\", \"outline-width\": \"2px\", transition: \"outline 0.2s ease-in-out\", outline: \"0 solid #0000\", \"outline-offset\": \"2px\", \"&:focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:focus-visible\": { \"outline-color\": \"currentColor\" }, \":where(figure:first-child)\": { overflow: \"hidden\", \"border-start-start-radius\": \"inherit\", \"border-start-end-radius\": \"inherit\", \"border-end-start-radius\": \"unset\", \"border-end-end-radius\": \"unset\" }, \":where(figure:last-child)\": { overflow: \"hidden\", \"border-start-start-radius\": \"unset\", \"border-start-end-radius\": \"unset\", \"border-end-start-radius\": \"inherit\", \"border-end-end-radius\": \"inherit\" }, \"&:where(.card-border)\": { border: \"var(--border) solid var(--color-base-200)\" }, \"&:where(.card-dash)\": { border: \"var(--border) dashed var(--color-base-200)\" }, \"&.image-full\": { display: \"grid\", \"> *\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\" }, \"> .card-body\": { position: \"relative\", color: \"var(--color-neutral-content)\" }, \":where(figure)\": { overflow: \"hidden\", \"border-radius\": \"inherit\" }, \"> figure img\": { height: \"100%\", \"object-fit\": \"cover\", filter: \"brightness(28%)\" } }, figure: { display: \"flex\", \"align-items\": \"center\", \"justify-content\": \"center\" }, '&:has(> input:is(input[type=\"checkbox\"], input[type=\"radio\"]))': { cursor: \"pointer\", \"user-select\": \"none\" }, \"&:has(> :checked)\": { outline: \"2px solid currentColor\" } }, \".card-title\": { display: \"flex\", \"align-items\": \"center\", gap: \"calc(0.25rem * 2)\", \"font-size\": \"var(--cardtitle-fs, 1.125rem)\", \"font-weight\": 600 }, \".card-body\": { display: \"flex\", flex: \"auto\", \"flex-direction\": \"column\", gap: \"calc(0.25rem * 2)\", padding: \"var(--card-p, 1.5rem)\", \"font-size\": \"var(--card-fs, 0.875rem)\", \":where(p)\": { \"flex-grow\": 1 } }, \".card-actions\": { display: \"flex\", \"flex-wrap\": \"wrap\", \"align-items\": \"flex-start\", gap: \"calc(0.25rem * 2)\" }, \".card-xs\": { \".card-body\": { \"--card-p\": \"0.5rem\", \"--card-fs\": \"0.6875rem\" }, \".card-title\": { \"--cardtitle-fs\": \"0.875rem\" } }, \".card-sm\": { \".card-body\": { \"--card-p\": \"1rem\", \"--card-fs\": \"0.75rem\" }, \".card-title\": { \"--cardtitle-fs\": \"1rem\" } }, \".card-md\": { \".card-body\": { \"--card-p\": \"1.5rem\", \"--card-fs\": \"0.875rem\" }, \".card-title\": { \"--cardtitle-fs\": \"1.125rem\" } }, \".card-lg\": { \".card-body\": { \"--card-p\": \"2rem\", \"--card-fs\": \"1rem\" }, \".card-title\": { \"--cardtitle-fs\": \"1.25rem\" } }, \".card-xl\": { \".card-body\": { \"--card-p\": \"2.5rem\", \"--card-fs\": \"1.125rem\" }, \".card-title\": { \"--cardtitle-fs\": \"1.375rem\" } }, \".card-side\": { \"align-items\": \"stretch\", \"flex-direction\": \"row\", \":where(figure:first-child)\": { overflow: \"hidden\", \"border-start-start-radius\": \"inherit\", \"border-start-end-radius\": \"unset\", \"border-end-start-radius\": \"inherit\", \"border-end-end-radius\": \"unset\" }, \":where(figure:last-child)\": { overflow: \"hidden\", \"border-start-start-radius\": \"unset\", \"border-start-end-radius\": \"inherit\", \"border-end-start-radius\": \"unset\", \"border-end-end-radius\": \"inherit\" }, \"figure > *\": { \"max-width\": \"unset\" }, \":where(figure > *)\": { width: \"100%\", height: \"100%\", \"object-fit\": \"cover\" } } };\n\n// packages/daisyui/components/card/index.js\nvar card_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcard = addPrefix(object_default16, prefix);\n  addComponents({ ...prefixedcard });\n};\n\n// packages/daisyui/components/steps/object.js\nvar object_default17 = { \".steps\": { display: \"inline-grid\", \"grid-auto-flow\": \"column\", overflow: \"hidden\", \"overflow-x\": \"auto\", \"counter-reset\": \"step\", \"grid-auto-columns\": \"1fr\", \".step\": { display: \"grid\", \"grid-template-columns\": [\"repeat(1, minmax(0, 1fr))\", \"auto\"], \"grid-template-rows\": [\"repeat(2, minmax(0, 1fr))\", \"40px 1fr\"], \"place-items\": \"center\", \"text-align\": \"center\", \"min-width\": \"4rem\", \"--step-bg\": \"var(--color-base-300)\", \"--step-fg\": \"var(--color-base-content)\", \"&:before\": { top: \"calc(0.25rem * 0)\", \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", height: \"calc(0.25rem * 2)\", width: \"100%\", border: \"1px solid\", color: \"var(--step-bg)\", \"background-color\": \"var(--step-bg)\", \"--tw-content\": '\"\"', content: \"var(--tw-content)\", \"margin-inline-start\": \"-100%\" }, \"> .step-icon, &:not(:has(.step-icon)):after\": { content: \"counter(step)\", \"counter-increment\": \"step\", \"z-index\": 1, color: \"var(--step-fg)\", \"background-color\": \"var(--step-bg)\", border: \"1px solid var(--step-bg)\", position: \"relative\", \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", display: \"grid\", height: \"calc(0.25rem * 8)\", width: \"calc(0.25rem * 8)\", \"place-items\": \"center\", \"place-self\": \"center\", \"border-radius\": \"calc(infinity * 1px)\" }, \"&:first-child:before\": { content: \"none\" }, \"&[data-content]:after\": { content: \"attr(data-content)\" } }, \".step-neutral\": { \"+ .step-neutral:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-neutral)\", \"--step-fg\": \"var(--color-neutral-content)\" } }, \".step-primary\": { \"+ .step-primary:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-primary)\", \"--step-fg\": \"var(--color-primary-content)\" } }, \".step-secondary\": { \"+ .step-secondary:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-secondary)\", \"--step-fg\": \"var(--color-secondary-content)\" } }, \".step-accent\": { \"+ .step-accent:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-accent)\", \"--step-fg\": \"var(--color-accent-content)\" } }, \".step-info\": { \"+ .step-info:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-info)\", \"--step-fg\": \"var(--color-info-content)\" } }, \".step-success\": { \"+ .step-success:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-success)\", \"--step-fg\": \"var(--color-success-content)\" } }, \".step-warning\": { \"+ .step-warning:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-warning)\", \"--step-fg\": \"var(--color-warning-content)\" } }, \".step-error\": { \"+ .step-error:before, &:after, > .step-icon\": { \"--step-bg\": \"var(--color-error)\", \"--step-fg\": \"var(--color-error-content)\" } } }, \".steps-horizontal\": { \"grid-auto-columns\": \"1fr\", display: \"inline-grid\", \"grid-auto-flow\": \"column\", overflow: \"hidden\", \"overflow-x\": \"auto\", \".step\": { display: \"grid\", \"grid-template-columns\": [\"repeat(1, minmax(0, 1fr))\", \"auto\"], \"grid-template-rows\": [\"repeat(2, minmax(0, 1fr))\", \"40px 1fr\"], \"place-items\": \"center\", \"text-align\": \"center\", \"min-width\": \"4rem\", \"&:before\": { height: \"calc(0.25rem * 2)\", width: \"100%\", translate: \"0\", content: '\"\"', \"margin-inline-start\": \"-100%\" }, '[dir=\"rtl\"] &:before': { translate: \"0\" } } }, \".steps-vertical\": { \"grid-auto-rows\": \"1fr\", \"grid-auto-flow\": \"row\", \".step\": { display: \"grid\", \"grid-template-columns\": [\"repeat(2, minmax(0, 1fr))\", \"40px 1fr\"], \"grid-template-rows\": [\"repeat(1, minmax(0, 1fr))\", \"auto\"], gap: \"0.5rem\", \"min-height\": \"4rem\", \"justify-items\": \"start\", \"&:before\": { height: \"100%\", width: \"calc(0.25rem * 2)\", translate: \"-50% -50%\", \"margin-inline-start\": \"50%\" }, '[dir=\"rtl\"] &:before': { translate: \"50% -50%\" } } } };\n\n// packages/daisyui/components/steps/index.js\nvar steps_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedsteps = addPrefix(object_default17, prefix);\n  addComponents({ ...prefixedsteps });\n};\n\n// packages/daisyui/components/alert/object.js\nvar object_default18 = { \".alert\": { display: \"grid\", \"align-items\": \"center\", gap: \"calc(0.25rem * 4)\", \"border-radius\": \"var(--radius-box)\", \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 3)\", color: \"var(--color-base-content)\", \"background-color\": \"var(--alert-color, var(--color-base-200))\", \"justify-content\": \"start\", \"justify-items\": \"start\", \"grid-auto-flow\": \"column\", \"grid-template-columns\": \"auto\", \"text-align\": \"start\", border: \"var(--border) solid var(--color-base-200)\", \"font-size\": \"0.875rem\", \"line-height\": \"1.25rem\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\", \"box-shadow\": \"0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08))\", \"&:has(:nth-child(2))\": { \"grid-template-columns\": \"auto minmax(auto, 1fr)\" }, \"&.alert-outline\": { \"background-color\": \"transparent\", color: \"var(--alert-color)\", \"box-shadow\": \"none\", \"background-image\": \"none\" }, \"&.alert-dash\": { \"background-color\": \"transparent\", color: \"var(--alert-color)\", \"border-style\": \"dashed\", \"box-shadow\": \"none\", \"background-image\": \"none\" }, \"&.alert-soft\": { color: \"var(--alert-color, var(--color-base-content))\", background: \"color-mix( in oklab, var(--alert-color, var(--color-base-content)) 8%, var(--color-base-100) )\", \"border-color\": \"color-mix( in oklab, var(--alert-color, var(--color-base-content)) 10%, var(--color-base-100) )\", \"box-shadow\": \"none\", \"background-image\": \"none\" } }, \".alert-info\": { \"border-color\": \"var(--color-info)\", color: \"var(--color-info-content)\", \"--alert-color\": \"var(--color-info)\" }, \".alert-success\": { \"border-color\": \"var(--color-success)\", color: \"var(--color-success-content)\", \"--alert-color\": \"var(--color-success)\" }, \".alert-warning\": { \"border-color\": \"var(--color-warning)\", color: \"var(--color-warning-content)\", \"--alert-color\": \"var(--color-warning)\" }, \".alert-error\": { \"border-color\": \"var(--color-error)\", color: \"var(--color-error-content)\", \"--alert-color\": \"var(--color-error)\" }, \".alert-vertical\": { \"justify-content\": \"center\", \"justify-items\": \"center\", \"grid-auto-flow\": \"row\", \"grid-template-columns\": \"auto\", \"text-align\": \"center\", \"&:has(:nth-child(2))\": { \"grid-template-columns\": \"auto\" } }, \".alert-horizontal\": { \"justify-content\": \"start\", \"justify-items\": \"start\", \"grid-auto-flow\": \"column\", \"grid-template-columns\": \"auto\", \"text-align\": \"start\", \"&:has(:nth-child(2))\": { \"grid-template-columns\": \"auto minmax(auto, 1fr)\" } } };\n\n// packages/daisyui/components/alert/index.js\nvar alert_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedalert = addPrefix(object_default18, prefix);\n  addComponents({ ...prefixedalert });\n};\n\n// packages/daisyui/components/kbd/object.js\nvar object_default19 = { \".kbd\": { display: \"inline-flex\", \"align-items\": \"center\", \"justify-content\": \"center\", \"border-radius\": \"var(--radius-field)\", \"background-color\": \"var(--color-base-200)\", \"vertical-align\": \"middle\", \"padding-left\": \"0.5em\", \"padding-right\": \"0.5em\", border: \"var(--border) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)\", \"border-bottom\": \"calc(var(--border) + 1px) solid color-mix(in srgb, var(--color-base-content) 20%, #0000)\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", \"font-size\": \"0.875rem\", height: \"var(--size)\", \"min-width\": \"var(--size)\" }, \".kbd-xs\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 4)\", \"font-size\": \"0.625rem\" }, \".kbd-sm\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 5)\", \"font-size\": \"0.75rem\" }, \".kbd-md\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", \"font-size\": \"0.875rem\" }, \".kbd-lg\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 7)\", \"font-size\": \"1rem\" }, \".kbd-xl\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 8)\", \"font-size\": \"1.125rem\" } };\n\n// packages/daisyui/components/kbd/index.js\nvar kbd_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedkbd = addPrefix(object_default19, prefix);\n  addComponents({ ...prefixedkbd });\n};\n\n// packages/daisyui/components/select/object.js\nvar object_default20 = { \".select\": { border: \"var(--border) solid #0000\", position: \"relative\", display: \"inline-flex\", \"flex-shrink\": 1, appearance: \"none\", \"align-items\": \"center\", gap: \"calc(0.25rem * 1.5)\", \"background-color\": \"var(--color-base-100)\", \"padding-inline-start\": \"calc(0.25rem * 4)\", \"padding-inline-end\": \"calc(0.25rem * 7)\", \"vertical-align\": \"middle\", width: \"clamp(3rem, 20rem, 100%)\", height: \"var(--size)\", \"font-size\": \"0.875rem\", \"border-start-start-radius\": \"var(--join-ss, var(--radius-field))\", \"border-start-end-radius\": \"var(--join-se, var(--radius-field))\", \"border-end-start-radius\": \"var(--join-es, var(--radius-field))\", \"border-end-end-radius\": \"var(--join-ee, var(--radius-field))\", \"background-image\": \"linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%)\", \"background-position\": \"calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%)\", \"background-size\": \"4px 4px, 4px 4px\", \"background-repeat\": \"no-repeat\", \"text-overflow\": \"ellipsis\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset\", \"border-color\": \"var(--input-color)\", \"--input-color\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", '[dir=\"rtl\"] &': { \"background-position\": \"calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%)\" }, select: { \"margin-inline-start\": \"calc(0.25rem * -4)\", \"margin-inline-end\": \"calc(0.25rem * -7)\", width: \"calc(100% + 2.75rem)\", appearance: \"none\", \"padding-inline-start\": \"calc(0.25rem * 4)\", \"padding-inline-end\": \"calc(0.25rem * 7)\", height: \"calc(100% - 2px)\", background: \"inherit\", \"border-radius\": \"inherit\", \"border-style\": \"none\", \"&:focus, &:focus-within\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:not(:last-child)\": { \"margin-inline-end\": \"calc(0.25rem * -5.5)\", \"background-image\": \"none\" } }, \"&:focus, &:focus-within\": { \"--input-color\": \"var(--color-base-content)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)\", outline: \"2px solid var(--input-color)\", \"outline-offset\": \"2px\", isolation: \"isolate\", \"z-index\": 1 }, \"&:has(> select[disabled]), &:is(:disabled, [disabled])\": { cursor: \"not-allowed\", \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", color: \"color-mix(in oklab, var(--color-base-content) 40%, transparent)\", \"&::placeholder\": { color: \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\" } }, \"&:has(> select[disabled]) > select[disabled]\": { cursor: \"not-allowed\" } }, \".select-ghost\": { \"background-color\": \"transparent\", transition: \"background-color 0.2s\", \"box-shadow\": \"none\", \"border-color\": \"#0000\", \"&:focus, &:focus-within\": { \"background-color\": \"var(--color-base-100)\", color: \"var(--color-base-content)\", \"border-color\": \"#0000\", \"box-shadow\": \"none\" } }, \".select-neutral\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-neutral)\" } }, \".select-primary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-primary)\" } }, \".select-secondary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-secondary)\" } }, \".select-accent\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-accent)\" } }, \".select-info\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-info)\" } }, \".select-success\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-success)\" } }, \".select-warning\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-warning)\" } }, \".select-error\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-error)\" } }, \".select-xs\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 6)\", \"font-size\": \"0.6875rem\" }, \".select-sm\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 8)\", \"font-size\": \"0.75rem\" }, \".select-md\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"font-size\": \"0.875rem\" }, \".select-lg\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 12)\", \"font-size\": \"1.125rem\" }, \".select-xl\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 14)\", \"font-size\": \"1.375rem\" } };\n\n// packages/daisyui/components/select/index.js\nvar select_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedselect = addPrefix(object_default20, prefix);\n  addComponents({ ...prefixedselect });\n};\n\n// packages/daisyui/components/progress/object.js\nvar object_default21 = { \".progress\": { position: \"relative\", height: \"calc(0.25rem * 2)\", width: \"100%\", appearance: \"none\", overflow: \"hidden\", \"border-radius\": \"var(--radius-box)\", \"background-color\": \"color-mix(in oklab, currentColor 20%, transparent)\", color: \"var(--color-base-content)\", \"&:indeterminate\": { \"background-image\": \"repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )\", \"background-size\": \"200%\", \"background-position-x\": \"15%\", animation: \"progress 5s ease-in-out infinite\", \"@supports (-moz-appearance: none)\": { \"&::-moz-progress-bar\": { \"background-color\": \"transparent\", \"background-image\": \"repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% )\", \"background-size\": \"200%\", \"background-position-x\": \"15%\", animation: \"progress 5s ease-in-out infinite\" } } }, \"@supports (-moz-appearance: none)\": { \"&::-moz-progress-bar\": { \"border-radius\": \"var(--radius-box)\", \"background-color\": \"currentColor\" } }, \"@supports (-webkit-appearance: none)\": { \"&::-webkit-progress-bar\": { \"border-radius\": \"var(--radius-box)\", \"background-color\": \"transparent\" }, \"&::-webkit-progress-value\": { \"border-radius\": \"var(--radius-box)\", \"background-color\": \"currentColor\" } } }, \".progress-primary\": { color: \"var(--color-primary)\" }, \".progress-secondary\": { color: \"var(--color-secondary)\" }, \".progress-accent\": { color: \"var(--color-accent)\" }, \".progress-neutral\": { color: \"var(--color-neutral)\" }, \".progress-info\": { color: \"var(--color-info)\" }, \".progress-success\": { color: \"var(--color-success)\" }, \".progress-warning\": { color: \"var(--color-warning)\" }, \".progress-error\": { color: \"var(--color-error)\" }, \"@keyframes progress\": { \"50%\": { \"background-position-x\": \"-115%\" } } };\n\n// packages/daisyui/components/progress/index.js\nvar progress_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedprogress = addPrefix(object_default21, prefix);\n  addComponents({ ...prefixedprogress });\n};\n\n// packages/daisyui/components/fileinput/object.js\nvar object_default22 = { \".file-input\": { cursor: [\"pointer\", \"pointer\"], border: \"var(--border) solid #0000\", display: \"inline-flex\", appearance: \"none\", \"align-items\": \"center\", \"background-color\": \"var(--color-base-100)\", \"vertical-align\": \"middle\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", width: \"clamp(3rem, 20rem, 100%)\", height: \"var(--size)\", \"padding-inline-end\": \"0.75rem\", \"font-size\": \"0.875rem\", \"line-height\": 2, \"border-start-start-radius\": \"var(--join-ss, var(--radius-field))\", \"border-start-end-radius\": \"var(--join-se, var(--radius-field))\", \"border-end-start-radius\": \"var(--join-es, var(--radius-field))\", \"border-end-end-radius\": \"var(--join-ee, var(--radius-field))\", \"border-color\": \"var(--input-color)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"--input-color\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"&::file-selector-button\": { \"margin-inline-end\": \"calc(0.25rem * 4)\", cursor: \"pointer\", \"padding-inline\": \"calc(0.25rem * 4)\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", height: \"calc(100% + var(--border) * 2)\", \"margin-block\": \"calc(var(--border) * -1)\", \"margin-inline-start\": \"calc(var(--border) * -1)\", \"font-size\": \"0.875rem\", color: \"var(--btn-fg)\", \"border-width\": \"var(--border)\", \"border-style\": \"solid\", \"border-color\": \"var(--btn-border)\", \"border-start-start-radius\": \"calc(var(--join-ss, var(--radius-field) - var(--border)))\", \"border-end-start-radius\": \"calc(var(--join-es, var(--radius-field) - var(--border)))\", \"font-weight\": 600, \"background-color\": \"var(--btn-bg)\", \"background-size\": \"calc(var(--noise) * 100%)\", \"background-image\": \"var(--btn-noise)\", \"text-shadow\": \"0 0.5px oklch(1 0 0 / calc(var(--depth) * 0.15))\", \"box-shadow\": \"0 0.5px 0 0.5px color-mix( in oklab, color-mix(in oklab, white 30%, var(--btn-bg)) calc(var(--depth) * 20%), #0000 ) inset, var(--btn-shadow)\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"--btn-bg\": \"var(--btn-color, var(--color-base-200))\", \"--btn-fg\": \"var(--color-base-content)\", \"--btn-border\": \"color-mix(in oklab, var(--btn-bg), #000 5%)\", \"--btn-shadow\": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000),\n      0 4px 3px -2px color-mix(in oklab, var(--btn-bg) 30%, #0000)`, \"--btn-noise\": \"var(--fx-noise)\" }, \"&:focus\": { \"--input-color\": \"var(--color-base-content)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) 10%, #0000)\", outline: \"2px solid var(--input-color)\", \"outline-offset\": \"2px\", isolation: \"isolate\" }, \"&:has(> input[disabled]), &:is(:disabled, [disabled])\": { cursor: \"not-allowed\", \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", \"&::placeholder\": { color: \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\" }, \"box-shadow\": \"none\", color: \"color-mix(in oklch, var(--color-base-content) 20%, #0000)\", \"&::file-selector-button\": { cursor: \"not-allowed\", \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", \"--btn-border\": \"#0000\", \"--btn-noise\": \"none\", \"--btn-fg\": \"color-mix(in oklch, var(--color-base-content) 20%, #0000)\" } } }, \".file-input-ghost\": { \"background-color\": \"transparent\", transition: \"background-color 0.2s\", \"box-shadow\": \"none\", \"border-color\": \"#0000\", \"&::file-selector-button\": { \"margin-inline-start\": \"calc(0.25rem * 0)\", \"margin-inline-end\": \"calc(0.25rem * 4)\", height: \"100%\", cursor: \"pointer\", \"padding-inline\": \"calc(0.25rem * 4)\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", \"margin-block\": \"0\", \"border-start-end-radius\": \"calc(var(--join-ss, var(--radius-field) - var(--border)))\", \"border-end-end-radius\": \"calc(var(--join-es, var(--radius-field) - var(--border)))\" }, \"&:focus, &:focus-within\": { \"background-color\": \"var(--color-base-100)\", color: \"var(--color-base-content)\", \"border-color\": \"#0000\", \"box-shadow\": \"none\" } }, \".file-input-neutral\": { \"--btn-color\": \"var(--color-neutral)\", \"&::file-selector-button\": { color: \"var(--color-neutral-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-neutral)\" } }, \".file-input-primary\": { \"--btn-color\": \"var(--color-primary)\", \"&::file-selector-button\": { color: \"var(--color-primary-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-primary)\" } }, \".file-input-secondary\": { \"--btn-color\": \"var(--color-secondary)\", \"&::file-selector-button\": { color: \"var(--color-secondary-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-secondary)\" } }, \".file-input-accent\": { \"--btn-color\": \"var(--color-accent)\", \"&::file-selector-button\": { color: \"var(--color-accent-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-accent)\" } }, \".file-input-info\": { \"--btn-color\": \"var(--color-info)\", \"&::file-selector-button\": { color: \"var(--color-info-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-info)\" } }, \".file-input-success\": { \"--btn-color\": \"var(--color-success)\", \"&::file-selector-button\": { color: \"var(--color-success-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-success)\" } }, \".file-input-warning\": { \"--btn-color\": \"var(--color-warning)\", \"&::file-selector-button\": { color: \"var(--color-warning-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-warning)\" } }, \".file-input-error\": { \"--btn-color\": \"var(--color-error)\", \"&::file-selector-button\": { color: \"var(--color-error-content)\" }, \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-error)\" } }, \".file-input-xs\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 6)\", \"font-size\": \"0.6875rem\", \"line-height\": \"1rem\", \"&::file-selector-button\": { \"font-size\": \"0.6875rem\" } }, \".file-input-sm\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 8)\", \"font-size\": \"0.75rem\", \"line-height\": \"1.5rem\", \"&::file-selector-button\": { \"font-size\": \"0.75rem\" } }, \".file-input-md\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"font-size\": \"0.875rem\", \"line-height\": 2, \"&::file-selector-button\": { \"font-size\": \"0.875rem\" } }, \".file-input-lg\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 12)\", \"font-size\": \"1.125rem\", \"line-height\": \"2.5rem\", \"&::file-selector-button\": { \"font-size\": \"1.125rem\" } }, \".file-input-xl\": { \"padding-inline-end\": \"calc(0.25rem * 6)\", \"--size\": \"calc(var(--size-field, 0.25rem) * 14)\", \"font-size\": \"1.125rem\", \"line-height\": \"3rem\", \"&::file-selector-button\": { \"font-size\": \"1.375rem\" } } };\n\n// packages/daisyui/components/fileinput/index.js\nvar fileinput_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedfileinput = addPrefix(object_default22, prefix);\n  addComponents({ ...prefixedfileinput });\n};\n\n// packages/daisyui/components/modal/object.js\nvar object_default23 = { \".modal\": { \"pointer-events\": \"none\", visibility: \"hidden\", position: \"fixed\", inset: \"calc(0.25rem * 0)\", margin: \"calc(0.25rem * 0)\", display: \"grid\", height: \"100%\", \"max-height\": \"none\", width: \"100%\", \"max-width\": \"none\", \"align-items\": \"center\", \"justify-items\": \"center\", \"background-color\": \"transparent\", padding: \"calc(0.25rem * 0)\", color: \"inherit\", \"overflow-x\": \"hidden\", transition: \"translate 0.3s ease-out, visibility 0.3s allow-discrete, background-color 0.3s ease-out, opacity 0.1s ease-out\", \"overflow-y\": \"hidden\", \"overscroll-behavior\": \"contain\", \"z-index\": 999, \"&::backdrop\": { display: \"none\" }, \"&.modal-open, &[open], &:target\": { \"pointer-events\": \"auto\", visibility: \"visible\", opacity: \"100%\", \"background-color\": \"oklch(0% 0 0/ 0.4)\", \".modal-box\": { translate: \"0 0\", scale: \"1\", opacity: 1 } }, \"@starting-style\": { \"&.modal-open, &[open], &:target\": { visibility: \"hidden\", opacity: \"0%\" } } }, \".modal-action\": { \"margin-top\": \"calc(0.25rem * 6)\", display: \"flex\", \"justify-content\": \"flex-end\", gap: \"calc(0.25rem * 2)\" }, \".modal-toggle\": { position: \"fixed\", height: \"calc(0.25rem * 0)\", width: \"calc(0.25rem * 0)\", appearance: \"none\", opacity: \"0%\", \"&:checked + .modal\": { \"pointer-events\": \"auto\", visibility: \"visible\", opacity: \"100%\", \"background-color\": \"oklch(0% 0 0/ 0.4)\", \".modal-box\": { translate: \"0 0\", scale: \"1\", opacity: 1 } }, \"@starting-style\": { \"&:checked + .modal\": { visibility: \"hidden\", opacity: \"0%\" } } }, \".modal-backdrop\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", display: \"grid\", \"align-self\": \"stretch\", \"justify-self\": \"stretch\", color: \"transparent\", \"z-index\": -1, button: { cursor: \"pointer\" } }, \".modal-box\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", \"max-height\": \"100vh\", width: \"calc(11/12 * 100%)\", \"max-width\": \"32rem\", \"background-color\": \"var(--color-base-100)\", padding: \"calc(0.25rem * 6)\", transition: \"translate 0.3s ease-out, scale 0.3s ease-out, opacity 0.2s ease-out 0.05s, box-shadow 0.3s ease-out\", \"border-top-left-radius\": \"var(--modal-tl, var(--radius-box))\", \"border-top-right-radius\": \"var(--modal-tr, var(--radius-box))\", \"border-bottom-left-radius\": \"var(--modal-bl, var(--radius-box))\", \"border-bottom-right-radius\": \"var(--modal-br, var(--radius-box))\", scale: \"95%\", opacity: 0, \"box-shadow\": \"oklch(0% 0 0/ 0.25) 0px 25px 50px -12px\", \"overflow-y\": \"auto\", \"overscroll-behavior\": \"contain\" }, \".modal-top\": { \"place-items\": \"start\", \":where(.modal-box)\": { height: \"auto\", width: \"100%\", \"max-width\": \"none\", \"max-height\": \"calc(100vh - 5em)\", translate: \"0 -100%\", scale: \"1\", \"--modal-tl\": \"0\", \"--modal-tr\": \"0\", \"--modal-bl\": \"var(--radius-box)\", \"--modal-br\": \"var(--radius-box)\" } }, \".modal-middle\": { \"place-items\": \"center\", \":where(.modal-box)\": { height: \"auto\", width: \"calc(11/12 * 100%)\", \"max-width\": \"32rem\", \"max-height\": \"calc(100vh - 5em)\", translate: \"0 2%\", scale: \"98%\", \"--modal-tl\": \"var(--radius-box)\", \"--modal-tr\": \"var(--radius-box)\", \"--modal-bl\": \"var(--radius-box)\", \"--modal-br\": \"var(--radius-box)\" } }, \".modal-bottom\": { \"place-items\": \"end\", \":where(.modal-box)\": { height: \"auto\", width: \"100%\", \"max-width\": \"none\", \"max-height\": \"calc(100vh - 5em)\", translate: \"0 100%\", scale: \"1\", \"--modal-tl\": \"var(--radius-box)\", \"--modal-tr\": \"var(--radius-box)\", \"--modal-bl\": \"0\", \"--modal-br\": \"0\" } }, \".modal-start\": { \"place-items\": \"start\", \":where(.modal-box)\": { height: \"100vh\", \"max-height\": \"none\", width: \"auto\", \"max-width\": \"none\", translate: \"-100% 0\", scale: \"1\", \"--modal-tl\": \"0\", \"--modal-tr\": \"var(--radius-box)\", \"--modal-bl\": \"0\", \"--modal-br\": \"var(--radius-box)\" } }, \".modal-end\": { \"place-items\": \"end\", \":where(.modal-box)\": { height: \"100vh\", \"max-height\": \"none\", width: \"auto\", \"max-width\": \"none\", translate: \"100% 0\", scale: \"1\", \"--modal-tl\": \"var(--radius-box)\", \"--modal-tr\": \"0\", \"--modal-bl\": \"var(--radius-box)\", \"--modal-br\": \"0\" } } };\n\n// packages/daisyui/components/modal/index.js\nvar modal_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedmodal = addPrefix(object_default23, prefix);\n  addComponents({ ...prefixedmodal });\n};\n\n// packages/daisyui/components/footer/object.js\nvar object_default24 = { \".footer\": { display: \"grid\", width: \"100%\", \"grid-auto-flow\": \"row\", \"place-items\": \"start\", \"column-gap\": \"calc(0.25rem * 4)\", \"row-gap\": \"calc(0.25rem * 10)\", \"font-size\": \"0.875rem\", \"line-height\": \"1.25rem\", \"& > *\": { display: \"grid\", \"place-items\": \"start\", gap: \"calc(0.25rem * 2)\" }, \"&.footer-center\": { \"grid-auto-flow\": \"column dense\", \"place-items\": \"center\", \"text-align\": \"center\", \"& > *\": { \"place-items\": \"center\" } } }, \".footer-title\": { \"margin-bottom\": \"calc(0.25rem * 2)\", \"text-transform\": \"uppercase\", opacity: \"60%\", \"font-weight\": 600 }, \".footer-horizontal\": { \"grid-auto-flow\": \"column\", \"&.footer-center\": { \"grid-auto-flow\": \"row dense\" } }, \".footer-vertical\": { \"grid-auto-flow\": \"row\", \"&.footer-center\": { \"grid-auto-flow\": \"column dense\" } } };\n\n// packages/daisyui/components/footer/index.js\nvar footer_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedfooter = addPrefix(object_default24, prefix);\n  addComponents({ ...prefixedfooter });\n};\n\n// packages/daisyui/components/table/object.js\nvar object_default25 = { \".table\": { \"font-size\": \"0.875rem\", position: \"relative\", width: \"100%\", \"border-radius\": \"var(--radius-box)\", \"text-align\": \"left\", '&:where(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *)': { \"text-align\": \"right\" }, \"tr.row-hover\": { \"&, &:nth-child(even)\": { \"&:hover\": { \"@media (hover: hover)\": { \"background-color\": \"var(--color-base-200)\" } } } }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 3)\", \"vertical-align\": \"middle\" }, \":where(thead, tfoot)\": { \"white-space\": \"nowrap\", color: \"color-mix(in oklab, var(--color-base-content) 60%, transparent)\", \"font-size\": \"0.875rem\", \"font-weight\": 600 }, \":where(tfoot)\": { \"border-top\": \"var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)\" }, \":where(.table-pin-rows thead tr)\": { position: \"sticky\", top: \"calc(0.25rem * 0)\", \"z-index\": 1, \"background-color\": \"var(--color-base-100)\" }, \":where(.table-pin-rows tfoot tr)\": { position: \"sticky\", bottom: \"calc(0.25rem * 0)\", \"z-index\": 1, \"background-color\": \"var(--color-base-100)\" }, \":where(.table-pin-cols tr th)\": { position: \"sticky\", right: \"calc(0.25rem * 0)\", left: \"calc(0.25rem * 0)\", \"background-color\": \"var(--color-base-100)\" }, \":where(thead tr, tbody tr:not(:last-child))\": { \"border-bottom\": \"var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000)\" } }, \".table-zebra\": { tbody: { tr: { \"&:where(:nth-child(even))\": { \"background-color\": \"var(--color-base-200)\", \":where(.table-pin-cols tr th)\": { \"background-color\": \"var(--color-base-200)\" } }, \"&.row-hover\": { \"&, &:where(:nth-child(even))\": { \"&:hover\": { \"@media (hover: hover)\": { \"background-color\": \"var(--color-base-300)\" } } } } } } }, \".table-xs\": { \":not(thead, tfoot) tr\": { \"font-size\": \"0.6875rem\" }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 2)\", \"padding-block\": \"calc(0.25rem * 1)\" } }, \".table-sm\": { \":not(thead, tfoot) tr\": { \"font-size\": \"0.75rem\" }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 2)\" } }, \".table-md\": { \":not(thead, tfoot) tr\": { \"font-size\": \"0.875rem\" }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 3)\" } }, \".table-lg\": { \":not(thead, tfoot) tr\": { \"font-size\": \"1.125rem\" }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 5)\", \"padding-block\": \"calc(0.25rem * 4)\" } }, \".table-xl\": { \":not(thead, tfoot) tr\": { \"font-size\": \"1.375rem\" }, \":where(th, td)\": { \"padding-inline\": \"calc(0.25rem * 6)\", \"padding-block\": \"calc(0.25rem * 5)\" } } };\n\n// packages/daisyui/components/table/index.js\nvar table_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtable = addPrefix(object_default25, prefix);\n  addComponents({ ...prefixedtable });\n};\n\n// packages/daisyui/components/avatar/object.js\nvar object_default26 = { \".avatar-group\": { display: \"flex\", overflow: \"hidden\", \":where(.avatar)\": { overflow: \"hidden\", \"border-radius\": \"calc(infinity * 1px)\", border: \"4px solid var(--color-base-100)\" } }, \".avatar\": { position: \"relative\", display: \"inline-flex\", \"vertical-align\": \"middle\", \"& > div\": { display: \"block\", \"aspect-ratio\": \"1 / 1\", overflow: \"hidden\" }, img: { height: \"100%\", width: \"100%\", \"object-fit\": \"cover\" } }, \".avatar-placeholder\": { \"& > div\": { display: \"flex\", \"align-items\": \"center\", \"justify-content\": \"center\" } }, \".avatar-online\": { \"&:before\": { content: '\"\"', position: \"absolute\", \"z-index\": 1, display: \"block\", \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"var(--color-success)\", outline: \"2px solid var(--color-base-100)\", width: \"15%\", height: \"15%\", top: \"7%\", right: \"7%\" } }, \".avatar-offline\": { \"&:before\": { content: '\"\"', position: \"absolute\", \"z-index\": 1, display: \"block\", \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"var(--color-base-300)\", outline: \"2px solid var(--color-base-100)\", width: \"15%\", height: \"15%\", top: \"7%\", right: \"7%\" } } };\n\n// packages/daisyui/components/avatar/index.js\nvar avatar_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedavatar = addPrefix(object_default26, prefix);\n  addComponents({ ...prefixedavatar });\n};\n\n// packages/daisyui/components/input/object.js\nvar object_default27 = { \".input\": { cursor: \"text\", border: \"var(--border) solid #0000\", position: \"relative\", display: \"inline-flex\", \"flex-shrink\": 1, appearance: \"none\", \"align-items\": \"center\", gap: \"calc(0.25rem * 2)\", \"background-color\": \"var(--color-base-100)\", \"padding-inline\": \"calc(0.25rem * 3)\", \"vertical-align\": \"middle\", \"white-space\": \"nowrap\", width: \"clamp(3rem, 20rem, 100%)\", height: \"var(--size)\", \"font-size\": \"0.875rem\", \"border-start-start-radius\": \"var(--join-ss, var(--radius-field))\", \"border-start-end-radius\": \"var(--join-se, var(--radius-field))\", \"border-end-start-radius\": \"var(--join-es, var(--radius-field))\", \"border-end-end-radius\": \"var(--join-ee, var(--radius-field))\", \"border-color\": \"var(--input-color)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"--input-color\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"&:where(input)\": { display: \"inline-flex\" }, \":where(input)\": { display: \"inline-flex\", height: \"100%\", width: \"100%\", appearance: \"none\", \"background-color\": \"transparent\", border: \"none\", \"&:focus, &:focus-within\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } } }, ':where(input[type=\"date\"])': { display: \"inline-block\" }, \"&:focus, &:focus-within\": { \"--input-color\": \"var(--color-base-content)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)\", outline: \"2px solid var(--input-color)\", \"outline-offset\": \"2px\", isolation: \"isolate\", \"z-index\": 1 }, \"&:has(> input[disabled]), &:is(:disabled, [disabled])\": { cursor: \"not-allowed\", \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", color: \"color-mix(in oklab, var(--color-base-content) 40%, transparent)\", \"&::placeholder\": { color: \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\" }, \"box-shadow\": \"none\" }, \"&:has(> input[disabled]) > input[disabled]\": { cursor: \"not-allowed\" }, \"&::-webkit-date-and-time-value\": { \"text-align\": \"inherit\" }, '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -3)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } }, \"&::-webkit-calendar-picker-indicator\": { position: \"absolute\", \"inset-inline-end\": \"0.75em\" } }, \".input-ghost\": { \"background-color\": \"transparent\", \"box-shadow\": \"none\", \"border-color\": \"#0000\", \"&:focus, &:focus-within\": { \"background-color\": \"var(--color-base-100)\", color: \"var(--color-base-content)\", \"border-color\": \"#0000\", \"box-shadow\": \"none\" } }, \".input-neutral\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-neutral)\" } }, \".input-primary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-primary)\" } }, \".input-secondary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-secondary)\" } }, \".input-accent\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-accent)\" } }, \".input-info\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-info)\" } }, \".input-success\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-success)\" } }, \".input-warning\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-warning)\" } }, \".input-error\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-error)\" } }, \".input-xs\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 6)\", \"font-size\": \"0.6875rem\", '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -1)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } } }, \".input-sm\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 8)\", \"font-size\": \"0.75rem\", '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -2)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } } }, \".input-md\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"font-size\": \"0.875rem\", '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -3)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } } }, \".input-lg\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 12)\", \"font-size\": \"1.125rem\", '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -3)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } } }, \".input-xl\": { \"--size\": \"calc(var(--size-field, 0.25rem) * 14)\", \"font-size\": \"1.375rem\", '&[type=\"number\"]': { \"&::-webkit-inner-spin-button\": { \"margin-block\": \"calc(0.25rem * -4)\", \"margin-inline-end\": \"calc(0.25rem * -3)\" } } } };\n\n// packages/daisyui/components/input/index.js\nvar input_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedinput = addPrefix(object_default27, prefix);\n  addComponents({ ...prefixedinput });\n};\n\n// packages/daisyui/components/checkbox/object.js\nvar object_default28 = { \".checkbox\": { border: \"var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000))\", position: \"relative\", \"flex-shrink\": 0, cursor: \"pointer\", appearance: \"none\", \"border-radius\": \"var(--radius-selector)\", padding: \"calc(0.25rem * 1)\", \"vertical-align\": \"middle\", color: \"var(--color-base-content)\", \"box-shadow\": \"0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000\", transition: \"background-color 0.2s, box-shadow 0.2s\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", width: \"var(--size)\", height: \"var(--size)\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\", \"&:before\": { \"--tw-content\": '\"\"', content: \"var(--tw-content)\", display: \"block\", width: \"100%\", height: \"100%\", rotate: \"45deg\", \"background-color\": \"currentColor\", opacity: \"0%\", transition: \"clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s\", \"transition-delay\": \"0.1s\", \"clip-path\": \"polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%)\", \"box-shadow\": \"0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset\", \"font-size\": \"1rem\", \"line-height\": 0.75 }, \"&:focus-visible\": { outline: \"2px solid var(--input-color, currentColor)\", \"outline-offset\": \"2px\" }, '&:checked, &[aria-checked=\"true\"]': { \"background-color\": \"var(--input-color, #0000)\", \"box-shadow\": \"0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))\", \"&:before\": { \"clip-path\": \"polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%)\", opacity: \"100%\" }, \"@media (forced-colors: active)\": { \"&:before\": { rotate: \"0deg\", \"background-color\": \"transparent\", \"--tw-content\": '\"✔︎\"', \"clip-path\": \"none\" } }, \"@media print\": { \"&:before\": { rotate: \"0deg\", \"background-color\": \"transparent\", \"--tw-content\": '\"✔︎\"', \"clip-path\": \"none\" } } }, \"&:indeterminate\": { \"&:before\": { rotate: \"0deg\", opacity: \"100%\", translate: \"0 -35%\", \"clip-path\": \"polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%)\" } } }, \".checkbox-primary\": { color: \"var(--color-primary-content)\", \"--input-color\": \"var(--color-primary)\" }, \".checkbox-secondary\": { color: \"var(--color-secondary-content)\", \"--input-color\": \"var(--color-secondary)\" }, \".checkbox-accent\": { color: \"var(--color-accent-content)\", \"--input-color\": \"var(--color-accent)\" }, \".checkbox-neutral\": { color: \"var(--color-neutral-content)\", \"--input-color\": \"var(--color-neutral)\" }, \".checkbox-info\": { color: \"var(--color-info-content)\", \"--input-color\": \"var(--color-info)\" }, \".checkbox-success\": { color: \"var(--color-success-content)\", \"--input-color\": \"var(--color-success)\" }, \".checkbox-warning\": { color: \"var(--color-warning-content)\", \"--input-color\": \"var(--color-warning)\" }, \".checkbox-error\": { color: \"var(--color-error-content)\", \"--input-color\": \"var(--color-error)\" }, \".checkbox:disabled\": { cursor: \"not-allowed\", opacity: \"20%\" }, \".checkbox-xs\": { padding: \"0.125rem\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 4)\" }, \".checkbox-sm\": { padding: \"0.1875rem\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 5)\" }, \".checkbox-md\": { padding: \"0.25rem\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\" }, \".checkbox-lg\": { padding: \"0.3125rem\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 7)\" }, \".checkbox-xl\": { padding: \"0.375rem\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 8)\" } };\n\n// packages/daisyui/components/checkbox/index.js\nvar checkbox_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcheckbox = addPrefix(object_default28, prefix);\n  addComponents({ ...prefixedcheckbox });\n};\n\n// packages/daisyui/components/badge/object.js\nvar object_default29 = { \".badge\": { display: \"inline-flex\", \"align-items\": \"center\", \"justify-content\": \"center\", gap: \"calc(0.25rem * 2)\", \"border-radius\": \"var(--radius-selector)\", \"vertical-align\": \"middle\", color: \"var(--badge-fg)\", border: \"var(--border) solid var(--badge-color, var(--color-base-200))\", \"font-size\": \"0.875rem\", width: \"fit-content\", \"padding-inline\": \"calc(0.25rem * 3 - var(--border))\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\", \"background-color\": \"var(--badge-bg)\", \"--badge-bg\": \"var(--badge-color, var(--color-base-100))\", \"--badge-fg\": \"var(--color-base-content)\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", height: \"var(--size)\", \"&.badge-outline\": { \"--badge-fg\": \"var(--badge-color)\", \"--badge-bg\": \"#0000\", \"background-image\": \"none\", \"border-color\": \"currentColor\" }, \"&.badge-dash\": { \"--badge-fg\": \"var(--badge-color)\", \"--badge-bg\": \"#0000\", \"background-image\": \"none\", \"border-color\": \"currentColor\", \"border-style\": \"dashed\" }, \"&.badge-soft\": { color: \"var(--badge-color, var(--color-base-content))\", \"background-color\": \"color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) )\", \"border-color\": \"color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) )\", \"background-image\": \"none\" } }, \".badge-primary\": { \"--badge-color\": \"var(--color-primary)\", \"--badge-fg\": \"var(--color-primary-content)\" }, \".badge-secondary\": { \"--badge-color\": \"var(--color-secondary)\", \"--badge-fg\": \"var(--color-secondary-content)\" }, \".badge-accent\": { \"--badge-color\": \"var(--color-accent)\", \"--badge-fg\": \"var(--color-accent-content)\" }, \".badge-neutral\": { \"--badge-color\": \"var(--color-neutral)\", \"--badge-fg\": \"var(--color-neutral-content)\" }, \".badge-info\": { \"--badge-color\": \"var(--color-info)\", \"--badge-fg\": \"var(--color-info-content)\" }, \".badge-success\": { \"--badge-color\": \"var(--color-success)\", \"--badge-fg\": \"var(--color-success-content)\" }, \".badge-warning\": { \"--badge-color\": \"var(--color-warning)\", \"--badge-fg\": \"var(--color-warning-content)\" }, \".badge-error\": { \"--badge-color\": \"var(--color-error)\", \"--badge-fg\": \"var(--color-error-content)\" }, \".badge-ghost\": { \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", color: \"var(--color-base-content)\", \"background-image\": \"none\" }, \".badge-xs\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 4)\", \"font-size\": \"0.625rem\", \"padding-inline\": \"calc(0.25rem * 2 - var(--border))\" }, \".badge-sm\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 5)\", \"font-size\": \"0.75rem\", \"padding-inline\": \"calc(0.25rem * 2.5 - var(--border))\" }, \".badge-md\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", \"font-size\": \"0.875rem\", \"padding-inline\": \"calc(0.25rem * 3 - var(--border))\" }, \".badge-lg\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 7)\", \"font-size\": \"1rem\", \"padding-inline\": \"calc(0.25rem * 3.5 - var(--border))\" }, \".badge-xl\": { \"--size\": \"calc(var(--size-selector, 0.25rem) * 8)\", \"font-size\": \"1.125rem\", \"padding-inline\": \"calc(0.25rem * 4 - var(--border))\" } };\n\n// packages/daisyui/components/badge/index.js\nvar badge_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedbadge = addPrefix(object_default29, prefix);\n  addComponents({ ...prefixedbadge });\n};\n\n// packages/daisyui/components/status/object.js\nvar object_default30 = { \".status\": { display: \"inline-block\", \"aspect-ratio\": \"1 / 1\", width: \"calc(0.25rem * 2)\", height: \"calc(0.25rem * 2)\", \"border-radius\": \"var(--radius-selector)\", \"background-color\": \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\", \"background-position\": \"center\", \"background-repeat\": \"no-repeat\", \"vertical-align\": \"middle\", color: \"color-mix(in srgb, #000 30%, transparent)\", \"@supports (color: color-mix(in lab, red, red))\": { color: \"color-mix(in oklab, var(--color-black) 30%, transparent)\" }, \"background-image\": \"radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 )\", \"box-shadow\": \"0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000)\" }, \".status-primary\": { \"background-color\": \"var(--color-primary)\", color: \"var(--color-primary)\" }, \".status-secondary\": { \"background-color\": \"var(--color-secondary)\", color: \"var(--color-secondary)\" }, \".status-accent\": { \"background-color\": \"var(--color-accent)\", color: \"var(--color-accent)\" }, \".status-neutral\": { \"background-color\": \"var(--color-neutral)\", color: \"var(--color-neutral)\" }, \".status-info\": { \"background-color\": \"var(--color-info)\", color: \"var(--color-info)\" }, \".status-success\": { \"background-color\": \"var(--color-success)\", color: \"var(--color-success)\" }, \".status-warning\": { \"background-color\": \"var(--color-warning)\", color: \"var(--color-warning)\" }, \".status-error\": { \"background-color\": \"var(--color-error)\", color: \"var(--color-error)\" }, \".status-xs\": { width: \"calc(0.25rem * 0.5)\", height: \"calc(0.25rem * 0.5)\" }, \".status-sm\": { width: \"calc(0.25rem * 1)\", height: \"calc(0.25rem * 1)\" }, \".status-md\": { width: \"calc(0.25rem * 2)\", height: \"calc(0.25rem * 2)\" }, \".status-lg\": { width: \"calc(0.25rem * 3)\", height: \"calc(0.25rem * 3)\" }, \".status-xl\": { width: \"calc(0.25rem * 4)\", height: \"calc(0.25rem * 4)\" } };\n\n// packages/daisyui/components/status/index.js\nvar status_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedstatus = addPrefix(object_default30, prefix);\n  addComponents({ ...prefixedstatus });\n};\n\n// packages/daisyui/components/diff/object.js\nvar object_default31 = { \".diff\": { position: \"relative\", display: \"grid\", width: \"100%\", overflow: \"hidden\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", direction: \"ltr\", \"container-type\": \"inline-size\", \"grid-template-columns\": \"auto 1fr\", \"&:focus-visible, &:has(.diff-item-1:focus-visible)\": { \"outline-style\": \"var(--tw-outline-style)\", \"outline-width\": \"2px\", \"outline-offset\": \"1px\", \"outline-color\": \"var(--color-base-content)\" }, \"&:focus-visible\": { \"outline-style\": \"var(--tw-outline-style)\", \"outline-width\": \"2px\", \"outline-offset\": \"1px\", \"outline-color\": \"var(--color-base-content)\", \".diff-resizer\": { \"min-width\": \"90cqi\", \"max-width\": \"90cqi\" } }, \"&:has(.diff-item-2:focus-visible)\": { \"outline-style\": \"var(--tw-outline-style)\", \"outline-width\": \"2px\", \"outline-offset\": \"1px\", \".diff-resizer\": { \"min-width\": \"10cqi\", \"max-width\": \"10cqi\" } }, \"@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)\": { \"&:focus\": { \".diff-resizer\": { \"min-width\": \"10cqi\", \"max-width\": \"10cqi\" } }, \"&:has(.diff-item-1:focus)\": { \".diff-resizer\": { \"min-width\": \"90cqi\", \"max-width\": \"90cqi\" } } } }, \".diff-resizer\": { position: \"relative\", top: \"calc(1/2 * 100%)\", \"z-index\": 1, \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", height: \"calc(0.25rem * 2)\", width: \"50cqi\", \"max-width\": \"calc(100cqi - 1rem)\", \"min-width\": \"1rem\", resize: \"horizontal\", overflow: \"hidden\", opacity: \"0%\", transform: \"scaleY(3) translate(0.35rem, 0.08rem)\", cursor: \"ew-resize\", \"transform-origin\": \"100% 100%\", \"clip-path\": \"inset(calc(100% - 0.75rem) 0 0 calc(100% - 0.75rem))\", transition: \"min-width 0.3s ease-out, max-width 0.3s ease-out\" }, \".diff-item-2\": { position: \"relative\", \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", \"&:after\": { \"pointer-events\": \"none\", position: \"absolute\", top: \"calc(1/2 * 100%)\", right: \"1px\", bottom: \"calc(0.25rem * 0)\", \"z-index\": 2, \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"color-mix(in oklab, var(--color-base-100) 50%, transparent)\", width: \"1.2rem\", height: \"1.8rem\", border: \"2px solid var(--color-base-100)\", content: '\"\"', outline: \"1px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)\", \"outline-offset\": \"-3px\", \"backdrop-filter\": \"blur(8px)\", \"box-shadow\": \"0 1px 2px 0 oklch(0% 0 0 / 0.1)\", translate: \"50% -50%\" }, \"> *\": { \"pointer-events\": \"none\", position: \"absolute\", top: \"calc(0.25rem * 0)\", bottom: \"calc(0.25rem * 0)\", left: \"calc(0.25rem * 0)\", height: \"100%\", width: \"100cqi\", \"max-width\": \"none\", \"object-fit\": \"cover\", \"object-position\": \"center\" }, \"@supports (-webkit-overflow-scrolling: touch) and (overflow: -webkit-paged-x)\": { \"&:after\": { content: \"none\" } } }, \".diff-item-1\": { position: \"relative\", \"z-index\": 1, \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", overflow: \"hidden\", \"border-right\": \"2px solid var(--color-base-100)\", \"> *\": { \"pointer-events\": \"none\", position: \"absolute\", top: \"calc(0.25rem * 0)\", bottom: \"calc(0.25rem * 0)\", left: \"calc(0.25rem * 0)\", height: \"100%\", width: \"100cqi\", \"max-width\": \"none\", \"object-fit\": \"cover\", \"object-position\": \"center\" } } };\n\n// packages/daisyui/components/diff/index.js\nvar diff_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixeddiff = addPrefix(object_default31, prefix);\n  addComponents({ ...prefixeddiff });\n};\n\n// packages/daisyui/components/hero/object.js\nvar object_default32 = { \".hero\": { display: \"grid\", width: \"100%\", \"place-items\": \"center\", \"background-size\": \"cover\", \"background-position\": \"center\", \"& > *\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\" } }, \".hero-overlay\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", height: \"100%\", width: \"100%\", \"background-color\": \"color-mix(in oklab, var(--color-neutral) 50%, transparent)\" }, \".hero-content\": { isolation: \"isolate\", display: \"flex\", \"max-width\": \"80rem\", \"align-items\": \"center\", \"justify-content\": \"center\", gap: \"calc(0.25rem * 4)\", padding: \"calc(0.25rem * 4)\" } };\n\n// packages/daisyui/components/hero/index.js\nvar hero_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedhero = addPrefix(object_default32, prefix);\n  addComponents({ ...prefixedhero });\n};\n\n// packages/daisyui/components/toggle/object.js\nvar object_default33 = { \".toggle\": { border: \"var(--border) solid currentColor\", color: \"var(--input-color)\", position: \"relative\", display: \"inline-grid\", \"flex-shrink\": 0, cursor: \"pointer\", appearance: \"none\", \"place-content\": \"center\", \"vertical-align\": \"middle\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", \"grid-template-columns\": \"0fr 1fr 1fr\", \"--radius-selector-max\": `calc(\n    var(--radius-selector) + var(--radius-selector) + var(--radius-selector)\n  )`, \"border-radius\": \"calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) )\", padding: \"var(--toggle-p)\", \"box-shadow\": \"0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset\", transition: \"color 0.3s, grid-template-columns 0.2s\", \"--input-color\": \"color-mix(in oklab, var(--color-base-content) 50%, #0000)\", \"--toggle-p\": \"calc(var(--size) * 0.125)\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", width: \"calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2)\", height: \"var(--size)\", \"> *\": { \"z-index\": 1, \"grid-column\": \"span 1 / span 1\", \"grid-column-start\": \"2\", \"grid-row-start\": \"1\", height: \"100%\", cursor: \"pointer\", appearance: \"none\", \"background-color\": \"transparent\", padding: \"calc(0.25rem * 0.5)\", transition: \"opacity 0.2s, rotate 0.4s\", border: \"none\", \"&:focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:nth-child(2)\": { color: \"var(--color-base-100)\", rotate: \"0deg\" }, \"&:nth-child(3)\": { color: \"var(--color-base-100)\", opacity: \"0%\", rotate: \"-15deg\" } }, \"&:has(:checked)\": { \"> :nth-child(2)\": { opacity: \"0%\", rotate: \"15deg\" }, \"> :nth-child(3)\": { opacity: \"100%\", rotate: \"0deg\" } }, \"&:before\": { position: \"relative\", \"inset-inline-start\": \"calc(0.25rem * 0)\", \"grid-column-start\": \"2\", \"grid-row-start\": \"1\", \"aspect-ratio\": \"1 / 1\", height: \"100%\", \"border-radius\": \"var(--radius-selector)\", \"background-color\": \"currentColor\", translate: \"0\", \"--tw-content\": '\"\"', content: \"var(--tw-content)\", transition: \"background-color 0.1s, translate 0.2s, inset-inline-start 0.2s\", \"box-shadow\": \"0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000)\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\" }, \"@media (forced-colors: active)\": { \"&:before\": { \"outline-style\": \"var(--tw-outline-style)\", \"outline-width\": \"1px\", \"outline-offset\": \"calc(1px * -1)\" } }, \"@media print\": { \"&:before\": { outline: \"0.25rem solid\", \"outline-offset\": \"-1rem\" } }, \"&:focus-visible, &:has(:focus-visible)\": { outline: \"2px solid currentColor\", \"outline-offset\": \"2px\" }, '&:checked, &[aria-checked=\"true\"], &:has(> input:checked)': { \"grid-template-columns\": \"1fr 1fr 0fr\", \"background-color\": \"var(--color-base-100)\", \"--input-color\": \"var(--color-base-content)\", \"&:before\": { \"background-color\": \"currentColor\" }, \"@starting-style\": { \"&:before\": { opacity: 0 } } }, \"&:indeterminate\": { \"grid-template-columns\": \"0.5fr 1fr 0.5fr\" }, \"&:disabled\": { cursor: \"not-allowed\", opacity: \"30%\", \"&:before\": { \"background-color\": \"transparent\", border: \"var(--border) solid currentColor\" } } }, \".toggle-primary\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-primary)\" } }, \".toggle-secondary\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-secondary)\" } }, \".toggle-accent\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-accent)\" } }, \".toggle-neutral\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-neutral)\" } }, \".toggle-success\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-success)\" } }, \".toggle-warning\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-warning)\" } }, \".toggle-info\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-info)\" } }, \".toggle-error\": { '&:checked, &[aria-checked=\"true\"]': { \"--input-color\": \"var(--color-error)\" } }, \".toggle-xs\": { '&:is([type=\"checkbox\"]), &:has([type=\"checkbox\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 4)\" } }, \".toggle-sm\": { '&:is([type=\"checkbox\"]), &:has([type=\"checkbox\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 5)\" } }, \".toggle-md\": { '&:is([type=\"checkbox\"]), &:has([type=\"checkbox\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\" } }, \".toggle-lg\": { '&:is([type=\"checkbox\"]), &:has([type=\"checkbox\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 7)\" } }, \".toggle-xl\": { '&:is([type=\"checkbox\"]), &:has([type=\"checkbox\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 8)\" } } };\n\n// packages/daisyui/components/toggle/index.js\nvar toggle_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtoggle = addPrefix(object_default33, prefix);\n  addComponents({ ...prefixedtoggle });\n};\n\n// packages/daisyui/components/stack/object.js\nvar object_default34 = { \".stack\": { display: \"inline-grid\", \"grid-template-columns\": \"3px 4px 1fr 4px 3px\", \"grid-template-rows\": \"3px 4px 1fr 4px 3px\", \"& > *\": { height: \"100%\", width: \"100%\", \"&:nth-child(n + 2)\": { width: \"100%\", opacity: \"70%\" }, \"&:nth-child(2)\": { \"z-index\": 2, opacity: \"90%\" }, \"&:nth-child(1)\": { \"z-index\": 3, width: \"100%\" } }, \"&, &.stack-bottom\": { \"> *\": { \"grid-column\": \"3 / 4\", \"grid-row\": \"3 / 6\", \"&:nth-child(2)\": { \"grid-column\": \"2 / 5\", \"grid-row\": \"2 / 5\" }, \"&:nth-child(1)\": { \"grid-column\": \"1 / 6\", \"grid-row\": \"1 / 4\" } } }, \"&.stack-top\": { \"> *\": { \"grid-column\": \"3 / 4\", \"grid-row\": \"1 / 4\", \"&:nth-child(2)\": { \"grid-column\": \"2 / 5\", \"grid-row\": \"2 / 5\" }, \"&:nth-child(1)\": { \"grid-column\": \"1 / 6\", \"grid-row\": \"3 / 6\" } } }, \"&.stack-start\": { \"> *\": { \"grid-column\": \"1 / 4\", \"grid-row\": \"3 / 4\", \"&:nth-child(2)\": { \"grid-column\": \"2 / 5\", \"grid-row\": \"2 / 5\" }, \"&:nth-child(1)\": { \"grid-column\": \"3 / 6\", \"grid-row\": \"1 / 6\" } } }, \"&.stack-end\": { \"> *\": { \"grid-column\": \"3 / 6\", \"grid-row\": \"3 / 4\", \"&:nth-child(2)\": { \"grid-column\": \"2 / 5\", \"grid-row\": \"2 / 5\" }, \"&:nth-child(1)\": { \"grid-column\": \"1 / 4\", \"grid-row\": \"1 / 6\" } } } } };\n\n// packages/daisyui/components/stack/index.js\nvar stack_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedstack = addPrefix(object_default34, prefix);\n  addComponents({ ...prefixedstack });\n};\n\n// packages/daisyui/components/navbar/object.js\nvar object_default35 = { \".navbar\": { display: \"flex\", width: \"100%\", \"align-items\": \"center\", padding: \"0.5rem\", \"min-height\": \"4rem\" }, \".navbar-start\": { display: \"inline-flex\", \"align-items\": \"center\", width: \"50%\", \"justify-content\": \"flex-start\" }, \".navbar-center\": { display: \"inline-flex\", \"align-items\": \"center\", \"flex-shrink\": 0 }, \".navbar-end\": { display: \"inline-flex\", \"align-items\": \"center\", width: \"50%\", \"justify-content\": \"flex-end\" } };\n\n// packages/daisyui/components/navbar/index.js\nvar navbar_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixednavbar = addPrefix(object_default35, prefix);\n  addComponents({ ...prefixednavbar });\n};\n\n// packages/daisyui/components/label/object.js\nvar object_default36 = { \".label\": { display: \"inline-flex\", \"align-items\": \"center\", gap: \"calc(0.25rem * 1.5)\", \"white-space\": \"nowrap\", color: \"color-mix(in oklab, currentColor 60%, transparent)\", \"&:has(input)\": { cursor: \"pointer\" }, \"&:is(.input > *, .select > *)\": { display: \"flex\", height: \"calc(100% - 0.5rem)\", \"align-items\": \"center\", \"padding-inline\": \"calc(0.25rem * 3)\", \"white-space\": \"nowrap\", \"font-size\": \"inherit\", \"&:first-child\": { \"margin-inline-start\": \"calc(0.25rem * -3)\", \"margin-inline-end\": \"calc(0.25rem * 3)\", \"border-inline-end\": \"var(--border) solid color-mix(in oklab, currentColor 10%, #0000)\" }, \"&:last-child\": { \"margin-inline-start\": \"calc(0.25rem * 3)\", \"margin-inline-end\": \"calc(0.25rem * -3)\", \"border-inline-start\": \"var(--border) solid color-mix(in oklab, currentColor 10%, #0000)\" } } }, \".floating-label\": { position: \"relative\", display: \"block\", input: { display: \"block\", \"&::placeholder\": { transition: \"top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out\" } }, textarea: { \"&::placeholder\": { transition: \"top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out\" } }, \"> span\": { position: \"absolute\", \"inset-inline-start\": \"calc(0.25rem * 3)\", \"z-index\": 1, \"background-color\": \"var(--color-base-100)\", \"padding-inline\": \"calc(0.25rem * 1)\", opacity: \"0%\", \"font-size\": \"0.875rem\", top: \"calc(var(--size-field, 0.25rem) * 10 / 2)\", \"line-height\": 1, \"border-radius\": \"2px\", \"pointer-events\": \"none\", translate: \"0 -50%\", transition: \"top 0.1s ease-out, translate 0.1s ease-out, scale 0.1s ease-out, opacity 0.1s ease-out\" }, \"&:focus-within, &:not(:has(input:placeholder-shown, textarea:placeholder-shown))\": { \"::placeholder\": { opacity: \"0%\", top: \"0\", translate: \"-12.5% calc(-50% - 0.125em)\", scale: \"0.75\", \"pointer-events\": \"auto\" }, \"> span\": { opacity: \"100%\", top: \"0\", translate: \"-12.5% calc(-50% - 0.125em)\", scale: \"0.75\", \"pointer-events\": \"auto\", \"z-index\": 2 } }, \"&:has(:disabled, [disabled])\": { \"> span\": { opacity: \"0%\" } }, \"&:has(.input-xs, .select-xs, .textarea-xs) span\": { \"font-size\": \"0.6875rem\", top: \"calc(var(--size-field, 0.25rem) * 6 / 2)\" }, \"&:has(.input-sm, .select-sm, .textarea-sm) span\": { \"font-size\": \"0.75rem\", top: \"calc(var(--size-field, 0.25rem) * 8 / 2)\" }, \"&:has(.input-md, .select-md, .textarea-md) span\": { \"font-size\": \"0.875rem\", top: \"calc(var(--size-field, 0.25rem) * 10 / 2)\" }, \"&:has(.input-lg, .select-lg, .textarea-lg) span\": { \"font-size\": \"1.125rem\", top: \"calc(var(--size-field, 0.25rem) * 12 / 2)\" }, \"&:has(.input-xl, .select-xl, .textarea-xl) span\": { \"font-size\": \"1.375rem\", top: \"calc(var(--size-field, 0.25rem) * 14 / 2)\" } } };\n\n// packages/daisyui/components/label/index.js\nvar label_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedlabel = addPrefix(object_default36, prefix);\n  addComponents({ ...prefixedlabel });\n};\n\n// packages/daisyui/components/menu/object.js\nvar object_default37 = { \".menu\": { display: \"flex\", width: \"fit-content\", \"flex-direction\": \"column\", \"flex-wrap\": \"wrap\", padding: \"calc(0.25rem * 2)\", \"--menu-active-fg\": \"var(--color-neutral-content)\", \"--menu-active-bg\": \"var(--color-neutral)\", \"font-size\": \"0.875rem\", \":where(li ul)\": { position: \"relative\", \"margin-inline-start\": \"calc(0.25rem * 4)\", \"padding-inline-start\": \"calc(0.25rem * 2)\", \"white-space\": \"nowrap\", \"&:before\": { position: \"absolute\", \"inset-inline-start\": \"calc(0.25rem * 0)\", top: \"calc(0.25rem * 3)\", bottom: \"calc(0.25rem * 3)\", \"background-color\": \"var(--color-base-content)\", opacity: \"10%\", width: \"var(--border)\", content: '\"\"' } }, \":where(li > .menu-dropdown:not(.menu-dropdown-show))\": { display: \"none\" }, \":where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { display: \"grid\", \"grid-auto-flow\": \"column\", \"align-content\": \"flex-start\", \"align-items\": \"center\", gap: \"calc(0.25rem * 2)\", \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 1.5)\", \"text-align\": \"start\", \"transition-property\": \"color, background-color, box-shadow\", \"transition-duration\": \"0.2s\", \"transition-timing-function\": \"cubic-bezier(0, 0, 0.2, 1)\", \"grid-auto-columns\": \"minmax(auto, max-content) auto max-content\", \"text-wrap\": \"balance\", \"user-select\": \"none\" }, \":where(li > details > summary)\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" }, \"&::-webkit-details-marker\": { display: \"none\" } }, \":where(li > details > summary), :where(li > .menu-dropdown-toggle)\": { \"&:after\": { \"justify-self\": \"flex-end\", display: \"block\", height: \"0.375rem\", width: \"0.375rem\", rotate: \"-135deg\", translate: \"0 -1px\", \"transition-property\": \"rotate, translate\", \"transition-duration\": \"0.2s\", content: '\"\"', \"transform-origin\": \"50% 50%\", \"box-shadow\": \"2px 2px inset\", \"pointer-events\": \"none\" } }, \":where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after\": { rotate: \"45deg\", translate: \"0 1px\" }, \":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn)\": { \"&.menu-focus, &:focus-visible\": { cursor: \"pointer\", \"background-color\": \"color-mix(in oklab, var(--color-base-content) 10%, transparent)\", color: \"var(--color-base-content)\", \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } } }, \":where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover )\": { cursor: \"pointer\", \"background-color\": \"color-mix(in oklab, var(--color-base-content) 10%, transparent)\", \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" }, \"box-shadow\": \"0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset\" }, \":where(li:empty)\": { \"background-color\": \"var(--color-base-content)\", opacity: \"10%\", margin: \"0.5rem 1rem\", height: \"1px\" }, \":where(li)\": { position: \"relative\", display: \"flex\", \"flex-shrink\": 0, \"flex-direction\": \"column\", \"flex-wrap\": \"wrap\", \"align-items\": \"stretch\", \".badge\": { \"justify-self\": \"flex-end\" }, \"& > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" }, color: \"var(--menu-active-fg)\", \"background-color\": \"var(--menu-active-bg)\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\", \"&:not(&:active)\": { \"box-shadow\": \"0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg)\" } }, \"&.menu-disabled\": { \"pointer-events\": \"none\", color: \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\" } }, \".dropdown:focus-within\": { \".menu-dropdown-toggle:after\": { rotate: \"45deg\", translate: \"0 1px\" } }, \".dropdown-content\": { \"margin-top\": \"calc(0.25rem * 2)\", padding: \"calc(0.25rem * 2)\", \"&:before\": { display: \"none\" } } }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 2)\", color: \"color-mix(in oklab, var(--color-base-content) 40%, transparent)\", \"font-size\": \"0.875rem\", \"font-weight\": 600 }, \".menu-horizontal\": { display: \"inline-flex\", \"flex-direction\": \"row\", \"& > li:not(.menu-title) > details > ul\": { position: \"absolute\", \"margin-inline-start\": \"calc(0.25rem * 0)\", \"margin-top\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 2)\", \"padding-inline-end\": \"calc(0.25rem * 2)\" }, \"& > li > details > ul\": { \"&:before\": { content: \"none\" } }, \":where(& > li:not(.menu-title) > details > ul)\": { \"border-radius\": \"var(--radius-box)\", \"background-color\": \"var(--color-base-100)\", \"box-shadow\": \"0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1)\" } }, \".menu-vertical\": { display: \"inline-flex\", \"flex-direction\": \"column\", \"& > li:not(.menu-title) > details > ul\": { position: \"relative\", \"margin-inline-start\": \"calc(0.25rem * 4)\", \"margin-top\": \"calc(0.25rem * 0)\", \"padding-block\": \"calc(0.25rem * 0)\", \"padding-inline-end\": \"calc(0.25rem * 0)\" } }, \".menu-xs\": { \":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 2)\", \"padding-block\": \"calc(0.25rem * 1)\", \"font-size\": \"0.6875rem\" }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 2)\", \"padding-block\": \"calc(0.25rem * 1)\" } }, \".menu-sm\": { \":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 2.5)\", \"padding-block\": \"calc(0.25rem * 1)\", \"font-size\": \"0.75rem\" }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 2)\" } }, \".menu-md\": { \":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 1.5)\", \"font-size\": \"0.875rem\" }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 2)\" } }, \".menu-lg\": { \":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 1.5)\", \"font-size\": \"1.125rem\" }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 6)\", \"padding-block\": \"calc(0.25rem * 3)\" } }, \".menu-xl\": { \":where(li:not(.menu-title) > *:not(ul, details, .menu-title)), :where(li:not(.menu-title) > details > summary:not(.menu-title))\": { \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 5)\", \"padding-block\": \"calc(0.25rem * 1.5)\", \"font-size\": \"1.375rem\" }, \".menu-title\": { \"padding-inline\": \"calc(0.25rem * 6)\", \"padding-block\": \"calc(0.25rem * 3)\" } } };\n\n// packages/daisyui/components/menu/index.js\nvar menu_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedmenu = addPrefix(object_default37, prefix);\n  addComponents({ ...prefixedmenu });\n};\n\n// packages/daisyui/components/toast/object.js\nvar object_default38 = { \".toast\": { position: \"fixed\", \"inset-inline-start\": \"auto\", \"inset-inline-end\": \"calc(0.25rem * 4)\", top: \"auto\", bottom: \"calc(0.25rem * 4)\", display: \"flex\", \"flex-direction\": \"column\", gap: \"calc(0.25rem * 2)\", \"background-color\": \"transparent\", translate: \"var(--toast-x, 0) var(--toast-y, 0)\", width: \"max-content\", \"max-width\": \"calc(100vw - 2rem)\", \"& > *\": { animation: \"toast 0.25s ease-out\" }, \"&:where(.toast-start)\": { \"inset-inline-start\": \"calc(0.25rem * 4)\", \"inset-inline-end\": \"auto\", \"--toast-x\": \"0\" }, \"&:where(.toast-center)\": { \"inset-inline-start\": \"calc(1/2 * 100%)\", \"inset-inline-end\": \"calc(1/2 * 100%)\", \"--toast-x\": \"-50%\" }, \"&:where(.toast-end)\": { \"inset-inline-start\": \"auto\", \"inset-inline-end\": \"calc(0.25rem * 4)\", \"--toast-x\": \"0\" }, \"&:where(.toast-bottom)\": { top: \"auto\", bottom: \"calc(0.25rem * 4)\", \"--toast-y\": \"0\" }, \"&:where(.toast-middle)\": { top: \"calc(1/2 * 100%)\", bottom: \"auto\", \"--toast-y\": \"-50%\" }, \"&:where(.toast-top)\": { top: \"calc(0.25rem * 4)\", bottom: \"auto\", \"--toast-y\": \"0\" } }, \"@keyframes toast\": { \"0%\": { scale: \"0.9\", opacity: 0 }, \"100%\": { scale: \"1\", opacity: 1 } } };\n\n// packages/daisyui/components/toast/index.js\nvar toast_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtoast = addPrefix(object_default38, prefix);\n  addComponents({ ...prefixedtoast });\n};\n\n// packages/daisyui/components/button/object.js\nvar object_default39 = { \":where(.btn)\": { width: \"unset\" }, \".btn\": { display: \"inline-flex\", \"flex-shrink\": 0, cursor: \"pointer\", \"flex-wrap\": \"nowrap\", \"align-items\": \"center\", \"justify-content\": \"center\", gap: \"calc(0.25rem * 1.5)\", \"text-align\": \"center\", \"vertical-align\": \"middle\", \"outline-offset\": \"2px\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", \"padding-inline\": \"var(--btn-p)\", color: \"var(--btn-fg)\", \"--tw-prose-links\": \"var(--btn-fg)\", height: \"var(--size)\", \"font-size\": \"var(--fontsize, 0.875rem)\", \"font-weight\": 600, \"outline-color\": \"var(--btn-color, var(--color-base-content))\", \"transition-property\": \"color, background-color, border-color, box-shadow\", \"transition-timing-function\": \"cubic-bezier(0, 0, 0.2, 1)\", \"transition-duration\": \"0.2s\", \"border-start-start-radius\": \"var(--join-ss, var(--radius-field))\", \"border-start-end-radius\": \"var(--join-se, var(--radius-field))\", \"border-end-start-radius\": \"var(--join-es, var(--radius-field))\", \"border-end-end-radius\": \"var(--join-ee, var(--radius-field))\", \"background-color\": \"var(--btn-bg)\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--btn-noise)\", \"border-width\": \"var(--border)\", \"border-style\": \"solid\", \"border-color\": \"var(--btn-border)\", \"text-shadow\": \"0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15))\", \"touch-action\": \"manipulation\", \"box-shadow\": \"0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow)\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\", \"--btn-bg\": \"var(--btn-color, var(--color-base-200))\", \"--btn-fg\": \"var(--color-base-content)\", \"--btn-p\": \"1rem\", \"--btn-border\": \"color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%))\", \"--btn-shadow\": `0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000),\n    0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000)`, \"--btn-noise\": \"var(--fx-noise)\", \".prose &\": { \"text-decoration-line\": \"none\" }, \"@media (hover: hover)\": { \"&:hover\": { \"--btn-bg\": \"color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)\" } }, \"&:focus-visible\": { \"outline-width\": \"2px\", \"outline-style\": \"solid\", isolation: \"isolate\" }, \"&:active:not(.btn-active)\": { translate: \"0 0.5px\", \"--btn-bg\": \"color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%)\", \"--btn-border\": \"color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)\", \"--btn-shadow\": \"0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)\" }, \"&:is(:disabled, [disabled], .btn-disabled)\": { \"&:not(.btn-link, .btn-ghost)\": { \"background-color\": \"color-mix(in oklab, var(--color-base-content) 10%, transparent)\", \"box-shadow\": \"none\" }, \"pointer-events\": \"none\", \"--btn-border\": \"#0000\", \"--btn-noise\": \"none\", \"--btn-fg\": \"color-mix(in oklch, var(--color-base-content) 20%, #0000)\", \"@media (hover: hover)\": { \"&:hover\": { \"pointer-events\": \"none\", \"background-color\": \"color-mix(in oklab, var(--color-neutral) 20%, transparent)\", \"--btn-border\": \"#0000\", \"--btn-fg\": \"color-mix(in oklch, var(--color-base-content) 20%, #0000)\" } } }, '&:is(input[type=\"checkbox\"], input[type=\"radio\"])': { appearance: \"none\", \"&::after\": { content: \"attr(aria-label)\" } }, \"&:where(input:checked:not(.filter .btn))\": { \"--btn-color\": \"var(--color-primary)\", \"--btn-fg\": \"var(--color-primary-content)\", isolation: \"isolate\" } }, \".btn-active\": { \"--btn-bg\": \"color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%)\", \"--btn-shadow\": \"0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0)\", isolation: \"isolate\" }, \".btn-primary\": { \"--btn-color\": \"var(--color-primary)\", \"--btn-fg\": \"var(--color-primary-content)\" }, \".btn-secondary\": { \"--btn-color\": \"var(--color-secondary)\", \"--btn-fg\": \"var(--color-secondary-content)\" }, \".btn-accent\": { \"--btn-color\": \"var(--color-accent)\", \"--btn-fg\": \"var(--color-accent-content)\" }, \".btn-neutral\": { \"--btn-color\": \"var(--color-neutral)\", \"--btn-fg\": \"var(--color-neutral-content)\" }, \".btn-info\": { \"--btn-color\": \"var(--color-info)\", \"--btn-fg\": \"var(--color-info-content)\" }, \".btn-success\": { \"--btn-color\": \"var(--color-success)\", \"--btn-fg\": \"var(--color-success-content)\" }, \".btn-warning\": { \"--btn-color\": \"var(--color-warning)\", \"--btn-fg\": \"var(--color-warning-content)\" }, \".btn-error\": { \"--btn-color\": \"var(--color-error)\", \"--btn-fg\": \"var(--color-error-content)\" }, \".btn-ghost\": { \"&:not(.btn-active, :hover, :active:focus, :focus-visible)\": { \"--btn-shadow\": '\"\"', \"--btn-bg\": \"#0000\", \"--btn-border\": \"#0000\", \"--btn-noise\": \"none\", \"&:not(:disabled, [disabled], .btn-disabled)\": { \"outline-color\": \"currentColor\", \"--btn-fg\": \"currentColor\" } } }, \".btn-link\": { \"text-decoration-line\": \"underline\", \"outline-color\": \"currentColor\", \"--btn-border\": \"#0000\", \"--btn-bg\": \"#0000\", \"--btn-fg\": \"var(--color-primary)\", \"--btn-noise\": \"none\", \"--btn-shadow\": '\"\"', \"&:is(.btn-active, :hover, :active:focus, :focus-visible)\": { \"text-decoration-line\": \"underline\", \"--btn-border\": \"#0000\", \"--btn-bg\": \"#0000\" } }, \".btn-outline\": { \"&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )\": { \"--btn-shadow\": '\"\"', \"--btn-bg\": \"#0000\", \"--btn-fg\": \"var(--btn-color)\", \"--btn-border\": \"var(--btn-color)\", \"--btn-noise\": \"none\" }, \"@media (hover: none)\": { \"&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )\": { \"--btn-shadow\": '\"\"', \"--btn-bg\": \"#0000\", \"--btn-fg\": \"var(--btn-color)\", \"--btn-border\": \"var(--btn-color)\", \"--btn-noise\": \"none\" } } }, \".btn-dash\": { \"&:not( .btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )\": { \"--btn-shadow\": '\"\"', \"border-style\": \"dashed\", \"--btn-bg\": \"#0000\", \"--btn-fg\": \"var(--btn-color)\", \"--btn-border\": \"var(--btn-color)\", \"--btn-noise\": \"none\" }, \"@media (hover: none)\": { \"&:hover:not( .btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled, :checked )\": { \"--btn-shadow\": '\"\"', \"border-style\": \"dashed\", \"--btn-bg\": \"#0000\", \"--btn-fg\": \"var(--btn-color)\", \"--btn-border\": \"var(--btn-color)\", \"--btn-noise\": \"none\" } } }, \".btn-soft\": { \"&:not(.btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled)\": { \"--btn-shadow\": '\"\"', \"--btn-fg\": \"var(--btn-color, var(--color-base-content))\", \"--btn-bg\": `color-mix(\n      in oklab,\n      var(--btn-color, var(--color-base-content)) 8%,\n      var(--color-base-100)\n    )`, \"--btn-border\": `color-mix(\n      in oklab,\n      var(--btn-color, var(--color-base-content)) 10%,\n      var(--color-base-100)\n    )`, \"--btn-noise\": \"none\" }, \"@media (hover: none)\": { \"&:hover:not(.btn-active, :active, :focus-visible, :disabled, [disabled], .btn-disabled)\": { \"--btn-shadow\": '\"\"', \"--btn-fg\": \"var(--btn-color, var(--color-base-content))\", \"--btn-bg\": `color-mix(\n        in oklab,\n        var(--btn-color, var(--color-base-content)) 8%,\n        var(--color-base-100)\n      )`, \"--btn-border\": `color-mix(\n        in oklab,\n        var(--btn-color, var(--color-base-content)) 10%,\n        var(--color-base-100)\n      )`, \"--btn-noise\": \"none\" } } }, \".btn-xs\": { \"--fontsize\": \"0.6875rem\", \"--btn-p\": \"0.5rem\", \"--size\": \"calc(var(--size-field, 0.25rem) * 6)\" }, \".btn-sm\": { \"--fontsize\": \"0.75rem\", \"--btn-p\": \"0.75rem\", \"--size\": \"calc(var(--size-field, 0.25rem) * 8)\" }, \".btn-md\": { \"--fontsize\": \"0.875rem\", \"--btn-p\": \"1rem\", \"--size\": \"calc(var(--size-field, 0.25rem) * 10)\" }, \".btn-lg\": { \"--fontsize\": \"1.125rem\", \"--btn-p\": \"1.25rem\", \"--size\": \"calc(var(--size-field, 0.25rem) * 12)\" }, \".btn-xl\": { \"--fontsize\": \"1.375rem\", \"--btn-p\": \"1.5rem\", \"--size\": \"calc(var(--size-field, 0.25rem) * 14)\" }, \".btn-square\": { \"padding-inline\": \"calc(0.25rem * 0)\", width: \"var(--size)\", height: \"var(--size)\" }, \".btn-circle\": { \"border-radius\": \"calc(infinity * 1px)\", \"padding-inline\": \"calc(0.25rem * 0)\", width: \"var(--size)\", height: \"var(--size)\" }, \".btn-wide\": { width: \"100%\", \"max-width\": \"calc(0.25rem * 64)\" }, \".btn-block\": { width: \"100%\" } };\n\n// packages/daisyui/components/button/index.js\nvar button_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedbutton = addPrefix(object_default39, prefix);\n  addComponents({ ...prefixedbutton });\n};\n\n// packages/daisyui/components/list/object.js\nvar object_default40 = { \".list\": { display: \"flex\", \"flex-direction\": \"column\", \"font-size\": \"0.875rem\", \":where(.list-row)\": { \"--list-grid-cols\": \"minmax(0, auto) 1fr\", position: \"relative\", display: \"grid\", \"grid-auto-flow\": \"column\", gap: \"calc(0.25rem * 4)\", \"border-radius\": \"var(--radius-box)\", padding: \"calc(0.25rem * 4)\", \"word-break\": \"break-word\", \"grid-template-columns\": \"var(--list-grid-cols)\", \"&:has(.list-col-grow:nth-child(1))\": { \"--list-grid-cols\": \"1fr\" }, \"&:has(.list-col-grow:nth-child(2))\": { \"--list-grid-cols\": \"minmax(0, auto) 1fr\" }, \"&:has(.list-col-grow:nth-child(3))\": { \"--list-grid-cols\": \"minmax(0, auto) minmax(0, auto) 1fr\" }, \"&:has(.list-col-grow:nth-child(4))\": { \"--list-grid-cols\": \"minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr\" }, \"&:has(.list-col-grow:nth-child(5))\": { \"--list-grid-cols\": \"minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr\" }, \"&:has(.list-col-grow:nth-child(6))\": { \"--list-grid-cols\": `minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto)\n        minmax(0, auto) 1fr` }, \":not(.list-col-wrap)\": { \"grid-row-start\": \"1\" } }, \"& > :not(:last-child)\": { \"&.list-row, .list-row\": { \"&:after\": { content: '\"\"', \"border-bottom\": \"var(--border) solid\", \"inset-inline\": \"var(--radius-box)\", position: \"absolute\", bottom: \"calc(0.25rem * 0)\", \"border-color\": \"color-mix(in oklab, var(--color-base-content) 5%, transparent)\" } } } }, \".list-col-wrap\": { \"grid-row-start\": \"2\" } };\n\n// packages/daisyui/components/list/index.js\nvar list_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedlist = addPrefix(object_default40, prefix);\n  addComponents({ ...prefixedlist });\n};\n\n// packages/daisyui/components/mockup/object.js\nvar object_default41 = { \".mockup-code\": { position: \"relative\", overflow: \"hidden\", \"overflow-x\": \"auto\", \"border-radius\": \"var(--radius-box)\", \"background-color\": \"var(--color-neutral)\", \"padding-block\": \"calc(0.25rem * 5)\", color: \"var(--color-neutral-content)\", \"font-size\": \"0.875rem\", direction: \"ltr\", \"&:before\": { content: '\"\"', \"margin-bottom\": \"calc(0.25rem * 4)\", display: \"block\", height: \"calc(0.25rem * 3)\", width: \"calc(0.25rem * 3)\", \"border-radius\": \"calc(infinity * 1px)\", opacity: \"30%\", \"box-shadow\": \"1.4em 0, 2.8em 0, 4.2em 0\" }, pre: { \"padding-right\": \"calc(0.25rem * 5)\", \"&:before\": { content: '\"\"', \"margin-right\": \"2ch\" }, \"&[data-prefix]\": { \"&:before\": { content: \"attr(data-prefix)\", display: \"inline-block\", width: \"calc(0.25rem * 8)\", \"text-align\": \"right\", opacity: \"50%\" } } } }, \".mockup-window\": { position: \"relative\", display: \"flex\", \"flex-direction\": \"column\", overflow: \"hidden\", \"overflow-x\": \"auto\", \"border-radius\": \"var(--radius-box)\", \"padding-top\": \"calc(0.25rem * 5)\", \"&:before\": { content: '\"\"', \"margin-bottom\": \"calc(0.25rem * 4)\", display: \"block\", \"aspect-ratio\": \"1 / 1\", height: \"calc(0.25rem * 3)\", \"flex-shrink\": 0, \"align-self\": \"flex-start\", \"border-radius\": \"calc(infinity * 1px)\", opacity: \"30%\", \"box-shadow\": \"1.4em 0, 2.8em 0, 4.2em 0\" }, '[dir=\"rtl\"] &:before': { \"align-self\": \"flex-end\" }, \"pre[data-prefix]\": { \"&:before\": { content: \"attr(data-prefix)\", display: \"inline-block\", \"text-align\": \"right\" } } }, \".mockup-browser\": { position: \"relative\", overflow: \"hidden\", \"overflow-x\": \"auto\", \"border-radius\": \"var(--radius-box)\", \"pre[data-prefix]\": { \"&:before\": { content: \"attr(data-prefix)\", display: \"inline-block\", \"text-align\": \"right\" } }, \".mockup-browser-toolbar\": { \"margin-block\": \"calc(0.25rem * 3)\", display: \"inline-flex\", width: \"100%\", \"align-items\": \"center\", \"padding-right\": \"1.4em\", '&:where(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *)': { \"flex-direction\": \"row-reverse\" }, \"&:before\": { content: '\"\"', \"margin-right\": \"4.8rem\", display: \"inline-block\", \"aspect-ratio\": \"1 / 1\", height: \"calc(0.25rem * 3)\", \"border-radius\": \"calc(infinity * 1px)\", opacity: \"30%\", \"box-shadow\": \"1.4em 0, 2.8em 0, 4.2em 0\" }, \".input\": { \"margin-inline\": \"auto\", display: \"flex\", height: \"100%\", \"align-items\": \"center\", gap: \"calc(0.25rem * 2)\", overflow: \"hidden\", \"background-color\": \"var(--color-base-200)\", \"text-overflow\": \"ellipsis\", \"white-space\": \"nowrap\", \"font-size\": \"0.75rem\", direction: \"ltr\", \"&:before\": { content: '\"\"', width: \"calc(0.25rem * 4)\", height: \"calc(0.25rem * 4)\", opacity: \"30%\", \"background-image\": `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='currentColor' class='size-4'%3E%3Cpath fill-rule='evenodd' d='M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z' clip-rule='evenodd' /%3E%3C/svg%3E%0A\")` } } } }, \".mockup-phone\": { display: \"inline-grid\", \"justify-items\": \"center\", border: \"6px solid #6b6b6b\", \"border-radius\": \"65px\", \"background-color\": \"#000\", padding: \"11px\", overflow: \"hidden\" }, \".mockup-phone-camera\": { \"grid-column\": \"1/1\", \"grid-row\": \"1/1\", background: \"#000\", height: \"32px\", width: \"126px\", \"border-radius\": \"17px\", \"z-index\": 1, \"margin-top\": \"6px\" }, \".mockup-phone-display\": { \"grid-column\": \"1/1\", \"grid-row\": \"1/1\", overflow: \"hidden\", \"border-radius\": \"49px\", width: \"390px\", height: \"845px\" } };\n\n// packages/daisyui/components/mockup/index.js\nvar mockup_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedmockup = addPrefix(object_default41, prefix);\n  addComponents({ ...prefixedmockup });\n};\n\n// packages/daisyui/components/calendar/object.js\nvar object_default42 = { \".cally\": { \"font-size\": \"0.7rem\", \"&::part(container)\": { padding: \"0.5rem 1rem\", \"user-select\": \"none\" }, \"::part(th)\": { \"font-weight\": \"normal\", \"block-size\": \"auto\" }, \"&::part(header)\": { direction: \"ltr\" }, \"::part(head)\": { opacity: 0.5, \"font-size\": \"0.7rem\" }, \"&::part(button)\": { \"border-radius\": \"var(--radius-field)\", border: \"none\", padding: \"0.5rem\", background: \"#0000\" }, \"&::part(button):hover\": { background: \"var(--color-base-200)\" }, \"::part(day)\": { \"border-radius\": \"var(--radius-field)\", \"font-size\": \"0.7rem\" }, \"::part(button day today)\": { background: \"var(--color-primary)\", color: \"var(--color-primary-content)\" }, \"::part(selected)\": { color: \"var(--color-base-100)\", background: \"var(--color-base-content)\", \"border-radius\": \"var(--radius-field)\" }, \"::part(range-inner)\": { \"border-radius\": \"0\" }, \"::part(range-start)\": { \"border-start-end-radius\": \"0\", \"border-end-end-radius\": \"0\" }, \"::part(range-end)\": { \"border-start-start-radius\": \"0\", \"border-end-start-radius\": \"0\" }, \"::part(range-start range-end)\": { \"border-radius\": \"var(--radius-field)\" }, \"calendar-month\": { width: \"100%\" } }, \".react-day-picker\": { \"user-select\": \"none\", \"background-color\": \"var(--color-base-100)\", \"border-radius\": \"var(--radius-box)\", border: \"var(--border) solid var(--color-base-200)\", \"font-size\": \"0.75rem\", display: \"inline-block\", position: \"relative\", overflow: \"clip\", '&[dir=\"rtl\"]': { \".rdp-nav\": { \".rdp-chevron\": { \"transform-origin\": \"50%\", transform: \"rotate(180deg)\" } } }, \"*\": { \"box-sizing\": \"border-box\" }, \".rdp-day\": { width: \"2.25rem\", height: \"2.25rem\", \"text-align\": \"center\" }, \".rdp-day_button\": { cursor: \"pointer\", font: \"inherit\", color: \"inherit\", width: \"2.25rem\", height: \"2.25rem\", border: \"2px solid #0000\", \"border-radius\": \"var(--radius-field)\", background: \"0 0\", \"justify-content\": \"center\", \"align-items\": \"center\", margin: \"0\", padding: \"0\", display: \"flex\", \"&:disabled\": { cursor: \"revert\" }, \"&:hover\": { \"background-color\": \"var(--color-base-200)\" } }, \".rdp-caption_label\": { \"z-index\": 1, \"white-space\": \"nowrap\", border: \"0\", \"align-items\": \"center\", display: \"inline-flex\", position: \"relative\" }, \".rdp-button_next\": { \"border-radius\": \"var(--radius-field)\", \"&:hover\": { \"background-color\": \"var(--color-base-200)\" } }, \".rdp-button_previous\": { \"border-radius\": \"var(--radius-field)\", \"&:hover\": { \"background-color\": \"var(--color-base-200)\" } }, \".rdp-button_next, .rdp-button_previous\": { cursor: \"pointer\", font: \"inherit\", color: \"inherit\", appearance: \"none\", width: \"2.25rem\", height: \"2.25rem\", background: \"0 0\", border: \"none\", \"justify-content\": \"center\", \"align-items\": \"center\", margin: \"0\", padding: \"0\", display: \"inline-flex\", position: \"relative\", \"&:disabled\": { cursor: \"revert\", opacity: 0.5 } }, \".rdp-chevron\": { fill: \"var(--color-base-content)\", width: \"1rem\", height: \"1rem\", display: \"inline-block\" }, \".rdp-dropdowns\": { \"align-items\": \"center\", gap: \"0.5rem\", display: \"inline-flex\", position: \"relative\" }, \".rdp-dropdown\": { \"z-index\": 2, opacity: 0, appearance: \"none\", cursor: \"inherit\", \"line-height\": \"inherit\", border: \"none\", width: \"100%\", margin: \"0\", padding: \"0\", position: \"absolute\", \"inset-block\": \"0\", \"inset-inline-start\": \"0\", \"&:focus-visible\": { \"~ .rdp-caption_label\": { outline: [\"5px auto highlight\", \"5px auto -webkit-focus-ring-color\"] } } }, \".rdp-dropdown_root\": { \"align-items\": \"center\", display: \"inline-flex\", position: \"relative\", '&[data-disabled=\"true\"]': { \".rdp-chevron\": { opacity: 0.5 } } }, \".rdp-month_caption\": { height: \"2.75rem\", \"font-size\": \"0.75rem\", \"font-weight\": \"inherit\", \"place-content\": \"center\", display: \"flex\" }, \".rdp-months\": { gap: \"2rem\", \"flex-wrap\": \"wrap\", \"max-width\": \"fit-content\", padding: \"0.5rem\", display: \"flex\", position: \"relative\" }, \".rdp-month_grid\": { \"border-collapse\": \"collapse\" }, \".rdp-nav\": { height: \"2.75rem\", \"inset-block-start\": \"0\", \"inset-inline-end\": \"0\", \"justify-content\": \"space-between\", \"align-items\": \"center\", width: \"100%\", \"padding-inline\": \"0.5rem\", display: \"flex\", position: \"absolute\", top: \"0.25rem\" }, \".rdp-weekday\": { opacity: 0.6, padding: \"0.5rem 0rem\", \"text-align\": \"center\", \"font-size\": \"smaller\", \"font-weight\": 500 }, \".rdp-week_number\": { opacity: 0.6, height: \"2.25rem\", width: \"2.25rem\", border: \"none\", \"border-radius\": \"100%\", \"text-align\": \"center\", \"font-size\": \"small\", \"font-weight\": 400 }, \".rdp-today:not(.rdp-outside)\": { \".rdp-day_button\": { background: \"var(--color-primary)\", color: \"var(--color-primary-content)\" } }, \".rdp-selected\": { \"font-weight\": \"inherit\", \"font-size\": \"0.75rem\", \".rdp-day_button\": { color: \"var(--color-base-100)\", \"background-color\": \"var(--color-base-content)\", \"border-radius\": \"var(--radius-field)\", border: \"none\", \"&:hover\": { \"background-color\": \"var(--color-base-content)\" } } }, \".rdp-outside\": { opacity: 0.75 }, \".rdp-disabled\": { opacity: 0.5 }, \".rdp-hidden\": { visibility: \"hidden\", color: \"var(--color-base-content)\" }, \".rdp-range_start\": { \".rdp-day_button\": { \"border-radius\": \"var(--radius-field) 0 0 var(--radius-field)\" } }, \".rdp-range_start .rdp-day_button\": { \"background-color\": \"var(--color-base-content)\", color: \"var(--color-base-content)\" }, \".rdp-range_middle\": { \"background-color\": \"var(--color-base-200)\" }, \".rdp-range_middle .rdp-day_button\": { border: \"unset\", \"border-radius\": \"unset\", color: \"inherit\" }, \".rdp-range_end\": { color: \"var(--color-base-content)\", \".rdp-day_button\": { \"border-radius\": \"0 var(--radius-field) var(--radius-field) 0\" } }, \".rdp-range_end .rdp-day_button\": { color: \"var(--color-base-content)\", \"background-color\": \"var(--color-base-content)\" }, \".rdp-range_start.rdp-range_end\": { background: \"revert\" }, \".rdp-focusable\": { cursor: \"pointer\" }, \".rdp-footer\": { \"border-top\": \"var(--border) solid var(--color-base-200)\", padding: \"0.5rem\" } }, \".pika-single\": { \"&:is(div)\": { \"user-select\": \"none\", \"font-size\": \"0.75rem\", \"z-index\": 999, display: \"inline-block\", position: \"relative\", color: \"var(--color-base-content)\", \"background-color\": \"var(--color-base-100)\", \"border-radius\": \"var(--radius-box)\", border: \"var(--border) solid var(--color-base-200)\", padding: \"0.5rem\", \"&:before, &:after\": { content: '\"\"', display: \"table\" }, \"&:after\": { clear: \"both\" }, \"&.is-hidden\": { display: \"none\" }, \"&.is-bound\": { position: \"absolute\" }, \".pika-lendar\": { \"css-float\": \"left\" }, \".pika-title\": { position: \"relative\", \"text-align\": \"center\", select: { cursor: \"pointer\", position: \"absolute\", \"z-index\": 999, margin: \"0\", left: \"0\", top: \"5px\", opacity: 0 } }, \".pika-label\": { display: \"inline-block\", position: \"relative\", \"z-index\": 999, overflow: \"hidden\", margin: \"0\", padding: \"5px 3px\", \"background-color\": \"var(--color-base-100)\" }, \".pika-prev, .pika-next\": { display: \"block\", cursor: \"pointer\", position: \"absolute\", top: \"0\", outline: \"none\", border: \"0\", width: \"2.25rem\", height: \"2.25rem\", color: \"#0000\", \"font-size\": \"1.2em\", \"border-radius\": \"var(--radius-field)\", \"&:hover\": { \"background-color\": \"var(--color-base-200)\" }, \"&.is-disabled\": { cursor: \"default\", opacity: 0.2 }, \"&:before\": { display: \"inline-block\", width: \"2.25rem\", height: \"2.25rem\", \"line-height\": 2.25, color: \"var(--color-base-content)\" } }, \".pika-prev\": { left: \"0\", \"&:before\": { content: '\"‹\"' } }, \".pika-next\": { right: \"0\", \"&:before\": { content: '\"›\"' } }, \".pika-select\": { display: \"inline-block\" }, \".pika-table\": { width: \"100%\", \"border-collapse\": \"collapse\", \"border-spacing\": \"0\", border: \"0\", \"th, td\": { padding: \"0\" }, th: { opacity: 0.6, \"text-align\": \"center\", width: \"2.25rem\", height: \"2.25rem\" } }, \".pika-button\": { cursor: \"pointer\", display: \"block\", outline: \"none\", border: \"0\", margin: \"0\", width: \"2.25rem\", height: \"2.25rem\", padding: \"5px\", \"text-align\": [\"right\", \"center\"] }, \".pika-week\": { color: \"var(--color-base-content)\" }, \".is-today\": { \".pika-button\": { background: \"var(--color-primary)\", color: \"var(--color-primary-content)\" } }, \".is-selected, .has-event\": { \".pika-button\": { \"&, &:hover\": { color: \"var(--color-base-100)\", \"background-color\": \"var(--color-base-content)\", \"border-radius\": \"var(--radius-field)\" } } }, \".has-event\": { \".pika-button\": { background: \"var(--color-base-primary)\" } }, \".is-disabled, .is-inrange\": { \".pika-button\": { background: \"var(--color-base-primary)\" } }, \".is-startrange\": { \".pika-button\": { color: \"var(--color-base-100)\", background: \"var(--color-base-content)\", \"border-radius\": \"var(--radius-field)\" } }, \".is-endrange\": { \".pika-button\": { color: \"var(--color-base-100)\", background: \"var(--color-base-content)\", \"border-radius\": \"var(--radius-field)\" } }, \".is-disabled\": { \".pika-button\": { \"pointer-events\": \"none\", cursor: \"default\", color: \"var(--color-base-content)\", opacity: 0.3 } }, \".is-outside-current-month\": { \".pika-button\": { color: \"var(--color-base-content)\", opacity: 0.3 } }, \".is-selection-disabled\": { \"pointer-events\": \"none\", cursor: \"default\" }, \".pika-button:hover, .pika-row.pick-whole-week:hover .pika-button\": { color: \"var(--color-base-content)\", \"background-color\": \"var(--color-base-200)\", \"border-radius\": \"var(--radius-field)\" }, \".pika-table abbr\": { \"text-decoration\": \"none\", \"font-weight\": \"normal\" } } } };\n\n// packages/daisyui/components/calendar/index.js\nvar calendar_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcalendar = addPrefix(object_default42, prefix);\n  addComponents({ ...prefixedcalendar });\n};\n\n// packages/daisyui/components/indicator/object.js\nvar object_default43 = { \".indicator\": { position: \"relative\", display: \"inline-flex\", width: \"max-content\", \":where(.indicator-item)\": { \"z-index\": 1, position: \"absolute\", \"white-space\": \"nowrap\", top: \"var(--inidicator-t, 0)\", bottom: \"var(--inidicator-b, auto)\", left: \"var(--inidicator-s, auto)\", right: \"var(--inidicator-e, 0)\", translate: \"var(--inidicator-x, 50%) var(--indicator-y, -50%)\" } }, \".indicator-start\": { \"--inidicator-s\": \"0\", \"--inidicator-e\": \"auto\", \"--inidicator-x\": \"-50%\" }, \".indicator-center\": { \"--inidicator-s\": \"50%\", \"--inidicator-e\": \"50%\", \"--inidicator-x\": \"-50%\", '[dir=\"rtl\"] &': { \"--inidicator-x\": \"50%\" } }, \".indicator-end\": { \"--inidicator-s\": \"auto\", \"--inidicator-e\": \"0\", \"--inidicator-x\": \"50%\" }, \".indicator-bottom\": { \"--inidicator-t\": \"auto\", \"--inidicator-b\": \"0\", \"--indicator-y\": \"50%\" }, \".indicator-middle\": { \"--inidicator-t\": \"50%\", \"--inidicator-b\": \"50%\", \"--indicator-y\": \"-50%\" }, \".indicator-top\": { \"--inidicator-t\": \"0\", \"--inidicator-b\": \"auto\", \"--indicator-y\": \"-50%\" } };\n\n// packages/daisyui/components/indicator/index.js\nvar indicator_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedindicator = addPrefix(object_default43, prefix);\n  addComponents({ ...prefixedindicator });\n};\n\n// packages/daisyui/components/rating/object.js\nvar object_default44 = { \".rating\": { position: \"relative\", display: \"inline-flex\", \"vertical-align\": \"middle\", \"& input\": { border: \"none\", appearance: \"none\" }, \":where(*)\": { animation: \"rating 0.25s ease-out\", height: \"calc(0.25rem * 6)\", width: \"calc(0.25rem * 6)\", \"border-radius\": \"0\", \"background-color\": \"var(--color-base-content)\", opacity: \"20%\", \"&:is(input)\": { cursor: \"pointer\" } }, \"& .rating-hidden\": { width: \"calc(0.25rem * 2)\", \"background-color\": \"transparent\" }, 'input[type=\"radio\"]:checked': { \"background-image\": \"none\" }, \"*\": { '&:checked, &[aria-checked=\"true\"], &[aria-current=\"true\"], &:has(~ *:checked, ~ *[aria-checked=\"true\"], ~ *[aria-current=\"true\"])': { opacity: \"100%\" }, \"&:focus-visible\": { transition: \"scale 0.2s ease-out\", scale: \"1.1\" } }, \"& *:active:focus\": { animation: \"none\", scale: \"1.1\" }, \"&.rating-xs :where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 4)\", height: \"calc(0.25rem * 4)\" }, \"&.rating-sm :where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 5)\", height: \"calc(0.25rem * 5)\" }, \"&.rating-md :where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 6)\", height: \"calc(0.25rem * 6)\" }, \"&.rating-lg :where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 7)\", height: \"calc(0.25rem * 7)\" }, \"&.rating-xl :where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 8)\", height: \"calc(0.25rem * 8)\" } }, \".rating-half\": { \":where(*:not(.rating-hidden))\": { width: \"calc(0.25rem * 3)\" }, \"&.rating-xs *:not(.rating-hidden)\": { width: \"calc(0.25rem * 2)\" }, \"&.rating-sm *:not(.rating-hidden)\": { width: \"calc(0.25rem * 2.5)\" }, \"&.rating-md *:not(.rating-hidden)\": { width: \"calc(0.25rem * 3)\" }, \"&.rating-lg *:not(.rating-hidden)\": { width: \".875rem\" }, \"&.rating-xl *:not(.rating-hidden)\": { width: \"calc(0.25rem * 4)\" } }, \"@keyframes rating\": { \"0%, 40%\": { scale: \"1.1\", filter: \"brightness(1.05) contrast(1.05)\" } } };\n\n// packages/daisyui/components/rating/index.js\nvar rating_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedrating = addPrefix(object_default44, prefix);\n  addComponents({ ...prefixedrating });\n};\n\n// packages/daisyui/components/tab/object.js\nvar object_default45 = { \".tabs\": { display: \"flex\", \"flex-wrap\": \"wrap\", \"--tabs-height\": \"auto\", \"--tabs-direction\": \"row\", height: \"var(--tabs-height)\", \"flex-direction\": \"var(--tabs-direction)\" }, \".tab\": { position: \"relative\", display: \"inline-flex\", cursor: \"pointer\", appearance: \"none\", \"flex-wrap\": \"wrap\", \"align-items\": \"center\", \"justify-content\": \"center\", \"text-align\": \"center\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", \"&:hover\": { \"@media (hover: hover)\": { color: \"var(--color-base-content)\" } }, \"--tab-p\": \"1rem\", \"--tab-bg\": \"var(--color-base-100)\", \"--tab-border-color\": \"var(--color-base-300)\", \"--tab-radius-ss\": \"0\", \"--tab-radius-se\": \"0\", \"--tab-radius-es\": \"0\", \"--tab-radius-ee\": \"0\", \"--tab-order\": \"0\", \"--tab-radius-min\": \"calc(0.75rem - var(--border))\", \"border-color\": \"#0000\", order: \"var(--tab-order)\", height: \"calc(var(--size-field, 0.25rem) * 10)\", \"font-size\": \"0.875rem\", \"padding-inline-start\": \"var(--tab-p)\", \"padding-inline-end\": \"var(--tab-p)\", '&:is(input[type=\"radio\"])': { \"min-width\": \"fit-content\", \"&:after\": { content: \"attr(aria-label)\" } }, \"&:is(label)\": { position: \"relative\", input: { position: \"absolute\", inset: \"calc(0.25rem * 0)\", cursor: \"pointer\", appearance: \"none\", opacity: \"0%\" } }, '&:checked, &:is(label:has(:checked)), &:is(.tab-active, [aria-selected=\"true\"])': { \"& + .tab-content\": { display: \"block\", height: \"100%\" } }, '&:not(:checked, label:has(:checked), :hover, .tab-active, [aria-selected=\"true\"])': { color: \"color-mix(in oklab, var(--color-base-content) 50%, transparent)\" }, \"&:not(input):empty\": { \"flex-grow\": 1, cursor: \"default\" }, \"&:focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:focus-visible, &:is(label:has(:checked:focus-visible))\": { outline: \"2px solid currentColor\", \"outline-offset\": \"-5px\" }, \"&[disabled]\": { \"pointer-events\": \"none\", opacity: \"40%\" } }, \".tab-disabled\": { \"pointer-events\": \"none\", opacity: \"40%\" }, \".tabs-border\": { \".tab\": { \"--tab-border-color\": \"#0000 #0000 var(--tab-border-color) #0000\", position: \"relative\", \"border-radius\": \"var(--radius-field)\", \"&:before\": { \"--tw-content\": '\"\"', content: \"var(--tw-content)\", \"background-color\": \"var(--tab-border-color)\", transition: \"background-color 0.2s ease\", width: \"80%\", height: \"3px\", \"border-radius\": \"var(--radius-field)\", bottom: \"0\", left: \"10%\", position: \"absolute\" }, '&:is(.tab-active, [aria-selected=\"true\"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { \"&:before\": { \"--tab-border-color\": \"currentColor\", \"border-top\": \"3px solid\" } } } }, \".tabs-lift\": { \"--tabs-height\": \"auto\", \"--tabs-direction\": \"row\", \"> .tab\": { \"--tab-border\": \"0 0 var(--border) 0\", \"--tab-radius-ss\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-radius-se\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-radius-es\": \"0\", \"--tab-radius-ee\": \"0\", \"--tab-paddings\": \"var(--border) var(--tab-p) 0 var(--tab-p)\", \"--tab-border-colors\": \"#0000 #0000 var(--tab-border-color) #0000\", \"--tab-corner-width\": \"calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)\", \"--tab-corner-height\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-corner-position\": \"top left, top right\", \"border-width\": \"var(--tab-border)\", \"border-start-start-radius\": \"var(--tab-radius-ss)\", \"border-start-end-radius\": \"var(--tab-radius-se)\", \"border-end-start-radius\": \"var(--tab-radius-es)\", \"border-end-end-radius\": \"var(--tab-radius-ee)\", padding: \"var(--tab-paddings)\", \"border-color\": \"var(--tab-border-colors)\", '&:is(.tab-active, [aria-selected=\"true\"]):not(.tab-disabled, [disabled]), &:is(input:checked, label:has(:checked))': { \"--tab-border\": \"var(--border) var(--border) 0 var(--border)\", \"--tab-border-colors\": `var(--tab-border-color) var(--tab-border-color) #0000\n        var(--tab-border-color)`, \"--tab-paddings\": `0 calc(var(--tab-p) - var(--border)) var(--border)\n        calc(var(--tab-p) - var(--border))`, \"--tab-inset\": \"auto auto 0 auto\", \"--tab-grad\": \"calc(69% - var(--border))\", \"--radius-start\": `radial-gradient(\n        circle at top left,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )`, \"--radius-end\": `radial-gradient(\n        circle at top right,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )`, \"background-color\": \"var(--tab-bg)\", \"&:before\": { \"z-index\": 1, content: '\"\"', display: \"block\", position: \"absolute\", width: \"var(--tab-corner-width)\", height: \"var(--tab-corner-height)\", \"background-position\": \"var(--tab-corner-position)\", \"background-image\": \"var(--radius-start), var(--radius-end)\", \"background-size\": \"min(var(--radius-field), var(--tab-radius-min)) min(var(--radius-field), var(--tab-radius-min))\", \"background-repeat\": \"no-repeat\", inset: \"var(--tab-inset)\" }, \"&:first-child:before\": { \"--radius-start\": \"none\" }, '[dir=\"rtl\"] &:first-child:before': { transform: \"rotateY(180deg)\" }, \"&:last-child:before\": { \"--radius-end\": \"none\" }, '[dir=\"rtl\"] &:last-child:before': { transform: \"rotateY(180deg)\" } } }, \"&:has(.tab-content)\": { \"> .tab:first-child\": { '&:not(.tab-active, [aria-selected=\"true\"])': { \"--tab-border-colors\": `var(--tab-border-color) var(--tab-border-color) #0000\n          var(--tab-border-color)` } } }, \".tab-content\": { \"--tabcontent-margin\": \"calc(-1 * var(--border)) 0 0 0\", \"--tabcontent-radius-ss\": \"0\", \"--tabcontent-radius-se\": \"var(--radius-box)\", \"--tabcontent-radius-es\": \"var(--radius-box)\", \"--tabcontent-radius-ee\": \"var(--radius-box)\" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected=\"true\"])': { \"& + .tab-content\": { \"&:nth-child(1), &:nth-child(n + 3)\": { \"--tabcontent-radius-ss\": \"var(--radius-box)\" } } } }, \".tabs-top\": { \"--tabs-height\": \"auto\", \"--tabs-direction\": \"row\", \".tab\": { \"--tab-order\": \"0\", \"--tab-border\": \"0 0 var(--border) 0\", \"--tab-radius-ss\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-radius-se\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-radius-es\": \"0\", \"--tab-radius-ee\": \"0\", \"--tab-paddings\": \"var(--border) var(--tab-p) 0 var(--tab-p)\", \"--tab-border-colors\": \"#0000 #0000 var(--tab-border-color) #0000\", \"--tab-corner-width\": \"calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)\", \"--tab-corner-height\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-corner-position\": \"top left, top right\", '&:is(.tab-active, [aria-selected=\"true\"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { \"--tab-border\": \"var(--border) var(--border) 0 var(--border)\", \"--tab-border-colors\": `var(--tab-border-color) var(--tab-border-color) #0000\n        var(--tab-border-color)`, \"--tab-paddings\": `0 calc(var(--tab-p) - var(--border)) var(--border)\n        calc(var(--tab-p) - var(--border))`, \"--tab-inset\": \"auto auto 0 auto\", \"--radius-start\": `radial-gradient(\n        circle at top left,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )`, \"--radius-end\": `radial-gradient(\n        circle at top right,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )` } }, \"&:has(.tab-content)\": { \"> .tab:first-child\": { '&:not(.tab-active, [aria-selected=\"true\"])': { \"--tab-border-colors\": `var(--tab-border-color) var(--tab-border-color) #0000\n          var(--tab-border-color)` } } }, \".tab-content\": { \"--tabcontent-order\": \"1\", \"--tabcontent-margin\": \"calc(-1 * var(--border)) 0 0 0\", \"--tabcontent-radius-ss\": \"0\", \"--tabcontent-radius-se\": \"var(--radius-box)\", \"--tabcontent-radius-es\": \"var(--radius-box)\", \"--tabcontent-radius-ee\": \"var(--radius-box)\" }, ':checked, label:has(:checked), :is(.tab-active, [aria-selected=\"true\"])': { \"& + .tab-content\": { \"&:nth-child(1), &:nth-child(n + 3)\": { \"--tabcontent-radius-ss\": \"var(--radius-box)\" } } } }, \".tabs-bottom\": { \"--tabs-height\": \"auto\", \"--tabs-direction\": \"row\", \".tab\": { \"--tab-order\": \"1\", \"--tab-border\": \"var(--border) 0 0 0\", \"--tab-radius-ss\": \"0\", \"--tab-radius-se\": \"0\", \"--tab-radius-es\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-radius-ee\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-border-colors\": \"var(--tab-border-color) #0000 #0000 #0000\", \"--tab-paddings\": \"0 var(--tab-p) var(--border) var(--tab-p)\", \"--tab-corner-width\": \"calc(100% + min(var(--radius-field), var(--tab-radius-min)) * 2)\", \"--tab-corner-height\": \"min(var(--radius-field), var(--tab-radius-min))\", \"--tab-corner-position\": \"top left, top right\", '&:is(.tab-active, [aria-selected=\"true\"]):not(.tab-disabled, [disabled]), &:is(input:checked), &:is(label:has(:checked))': { \"--tab-border\": \"0 var(--border) var(--border) var(--border)\", \"--tab-border-colors\": `#0000 var(--tab-border-color) var(--tab-border-color)\n        var(--tab-border-color)`, \"--tab-paddings\": `var(--border) calc(var(--tab-p) - var(--border)) 0\n        calc(var(--tab-p) - var(--border))`, \"--tab-inset\": \"0 auto auto auto\", \"--radius-start\": `radial-gradient(\n        circle at bottom left,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )`, \"--radius-end\": `radial-gradient(\n        circle at bottom right,\n        #0000 var(--tab-grad),\n        var(--tab-border-color) calc(var(--tab-grad) + 0.25px),\n        var(--tab-border-color) calc(var(--tab-grad) + var(--border)),\n        var(--tab-bg) calc(var(--tab-grad) + var(--border) + 0.25px)\n      )` } }, \"&:has(.tab-content)\": { \"> .tab:first-child\": { '&:not(.tab-active, [aria-selected=\"true\"])': { \"--tab-border-colors\": `#0000 var(--tab-border-color) var(--tab-border-color)\n          var(--tab-border-color)` } } }, \".tab-content\": { \"--tabcontent-order\": \"0\", \"--tabcontent-margin\": \"0 0 calc(-1 * var(--border)) 0\", \"--tabcontent-radius-ss\": \"var(--radius-box)\", \"--tabcontent-radius-se\": \"var(--radius-box)\", \"--tabcontent-radius-es\": \"0\", \"--tabcontent-radius-ee\": \"var(--radius-box)\" }, '> :checked, > :is(label:has(:checked)), > :is(.tab-active, [aria-selected=\"true\"])': { \"& + .tab-content:not(:nth-child(2))\": { \"--tabcontent-radius-es\": \"var(--radius-box)\" } } }, \".tabs-box\": { \"background-color\": \"var(--color-base-200)\", padding: \"calc(0.25rem * 1)\", \"--tabs-box-radius\": \"calc(var(--radius-field) + var(--radius-field) + var(--radius-field))\", \"border-radius\": \"calc(var(--radius-field) + min(0.25rem, var(--tabs-box-radius)))\", \"box-shadow\": \"0 -0.5px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0.5px oklch(0% 0 0 / calc(var(--depth) * 0.05)) inset\", \".tab\": { \"border-radius\": \"var(--radius-field)\", \"border-style\": \"none\", \"&:focus-visible, &:is(label:has(:checked:focus-visible))\": { \"outline-offset\": \"2px\" } }, '> :is(.tab-active, [aria-selected=\"true\"]):not(.tab-disabled, [disabled]), > :is(input:checked), > :is(label:has(:checked))': { \"background-color\": \"var(--tab-bg, var(--color-base-100))\", \"box-shadow\": \"0 1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000)\", \"@media (forced-colors: active)\": { border: \"1px solid\" } } }, \".tab-content\": { order: [1, \"var(--tabcontent-order)\"], display: \"none\", \"border-color\": \"transparent\", \"--tabcontent-radius-ss\": \"0\", \"--tabcontent-radius-se\": \"0\", \"--tabcontent-radius-es\": \"0\", \"--tabcontent-radius-ee\": \"0\", \"--tabcontent-order\": \"1\", width: \"100%\", margin: \"var(--tabcontent-margin)\", \"border-width\": \"var(--border)\", \"border-start-start-radius\": \"var(--tabcontent-radius-ss)\", \"border-start-end-radius\": \"var(--tabcontent-radius-se)\", \"border-end-start-radius\": \"var(--tabcontent-radius-es)\", \"border-end-end-radius\": \"var(--tabcontent-radius-ee)\" }, \".tabs-xs\": { \":where(.tab)\": { height: \"calc(var(--size-field, 0.25rem) * 6)\", \"font-size\": \"0.75rem\", \"--tab-p\": \"0.375rem\", \"--tab-radius-min\": \"calc(0.5rem - var(--border))\" } }, \".tabs-sm\": { \":where(.tab)\": { height: \"calc(var(--size-field, 0.25rem) * 8)\", \"font-size\": \"0.875rem\", \"--tab-p\": \"0.5rem\", \"--tab-radius-min\": \"calc(0.5rem - var(--border))\" } }, \".tabs-md\": { \":where(.tab)\": { height: \"calc(var(--size-field, 0.25rem) * 10)\", \"font-size\": \"0.875rem\", \"--tab-p\": \"0.75rem\", \"--tab-radius-min\": \"calc(0.75rem - var(--border))\" } }, \".tabs-lg\": { \":where(.tab)\": { height: \"calc(var(--size-field, 0.25rem) * 12)\", \"font-size\": \"1.125rem\", \"--tab-p\": \"1rem\", \"--tab-radius-min\": \"calc(1.5rem - var(--border))\" } }, \".tabs-xl\": { \":where(.tab)\": { height: \"calc(var(--size-field, 0.25rem) * 14)\", \"font-size\": \"1.125rem\", \"--tab-p\": \"1.25rem\", \"--tab-radius-min\": \"calc(2rem - var(--border))\" } } };\n\n// packages/daisyui/components/tab/index.js\nvar tab_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtab = addPrefix(object_default45, prefix);\n  addComponents({ ...prefixedtab });\n};\n\n// packages/daisyui/components/filter/object.js\nvar object_default46 = { \".filter\": { display: \"flex\", \"flex-wrap\": \"wrap\", 'input[type=\"radio\"]': { width: \"auto\" }, input: { overflow: \"hidden\", opacity: \"100%\", scale: \"1\", transition: \"margin 0.1s, opacity 0.3s, padding 0.3s, border-width 0.1s\", \"&:not(:last-child)\": { \"margin-inline-end\": \"calc(0.25rem * 1)\" }, \"&.filter-reset\": { \"aspect-ratio\": \"1 / 1\", \"&::after\": { content: '\"×\"' } } }, \"&:not(:has(input:checked:not(.filter-reset)))\": { '.filter-reset, input[type=\"reset\"]': { scale: \"0\", \"border-width\": \"0\", \"margin-inline\": \"calc(0.25rem * 0)\", width: \"calc(0.25rem * 0)\", \"padding-inline\": \"calc(0.25rem * 0)\", opacity: \"0%\" } }, \"&:has(input:checked:not(.filter-reset))\": { 'input:not(:checked, .filter-reset, input[type=\"reset\"])': { scale: \"0\", \"border-width\": \"0\", \"margin-inline\": \"calc(0.25rem * 0)\", width: \"calc(0.25rem * 0)\", \"padding-inline\": \"calc(0.25rem * 0)\", opacity: \"0%\" } } } };\n\n// packages/daisyui/components/filter/index.js\nvar filter_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedfilter = addPrefix(object_default46, prefix);\n  addComponents({ ...prefixedfilter });\n};\n\n// packages/daisyui/components/chat/object.js\nvar object_default47 = { \".chat\": { display: \"grid\", \"column-gap\": \"calc(0.25rem * 3)\", \"padding-block\": \"calc(0.25rem * 1)\" }, \".chat-bubble\": { position: \"relative\", display: \"block\", width: \"fit-content\", \"border-radius\": \"var(--radius-field)\", \"background-color\": \"var(--color-base-300)\", \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 2)\", color: \"var(--color-base-content)\", \"grid-row-end\": \"3\", \"min-height\": \"2rem\", \"min-width\": \"2.5rem\", \"max-width\": \"90%\", \"&:before\": { position: \"absolute\", bottom: \"calc(0.25rem * 0)\", height: \"calc(0.25rem * 3)\", width: \"calc(0.25rem * 3)\", \"background-color\": \"inherit\", content: '\"\"', \"mask-repeat\": \"no-repeat\", \"mask-image\": \"var(--mask-chat)\", \"mask-position\": \"0px -1px\", \"mask-size\": \"13px\" } }, \".chat-bubble-primary\": { \"background-color\": \"var(--color-primary)\", color: \"var(--color-primary-content)\" }, \".chat-bubble-secondary\": { \"background-color\": \"var(--color-secondary)\", color: \"var(--color-secondary-content)\" }, \".chat-bubble-accent\": { \"background-color\": \"var(--color-accent)\", color: \"var(--color-accent-content)\" }, \".chat-bubble-neutral\": { \"background-color\": \"var(--color-neutral)\", color: \"var(--color-neutral-content)\" }, \".chat-bubble-info\": { \"background-color\": \"var(--color-info)\", color: \"var(--color-info-content)\" }, \".chat-bubble-success\": { \"background-color\": \"var(--color-success)\", color: \"var(--color-success-content)\" }, \".chat-bubble-warning\": { \"background-color\": \"var(--color-warning)\", color: \"var(--color-warning-content)\" }, \".chat-bubble-error\": { \"background-color\": \"var(--color-error)\", color: \"var(--color-error-content)\" }, \".chat-image\": { \"grid-row\": \"span 2 / span 2\", \"align-self\": \"flex-end\" }, \".chat-header\": { \"grid-row-start\": \"1\", display: \"flex\", gap: \"calc(0.25rem * 1)\", \"font-size\": \"0.6875rem\" }, \".chat-footer\": { \"grid-row-start\": \"3\", display: \"flex\", gap: \"calc(0.25rem * 1)\", \"font-size\": \"0.6875rem\" }, \".chat-start\": { \"place-items\": \"start\", \"grid-template-columns\": \"auto 1fr\", \".chat-header\": { \"grid-column-start\": \"2\" }, \".chat-footer\": { \"grid-column-start\": \"2\" }, \".chat-image\": { \"grid-column-start\": \"1\" }, \".chat-bubble\": { \"grid-column-start\": \"2\", \"border-end-start-radius\": \"0\", \"&:before\": { transform: \"rotateY(0deg)\", \"inset-inline-start\": \"-0.75rem\" }, '[dir=\"rtl\"] &:before': { transform: \"rotateY(180deg)\" } } }, \".chat-end\": { \"place-items\": \"end\", \"grid-template-columns\": \"1fr auto\", \".chat-header\": { \"grid-column-start\": \"1\" }, \".chat-footer\": { \"grid-column-start\": \"1\" }, \".chat-image\": { \"grid-column-start\": \"2\" }, \".chat-bubble\": { \"grid-column-start\": \"1\", \"border-end-end-radius\": \"0\", \"&:before\": { transform: \"rotateY(180deg)\", \"inset-inline-start\": \"100%\" }, '[dir=\"rtl\"] &:before': { transform: \"rotateY(0deg)\" } } } };\n\n// packages/daisyui/components/chat/index.js\nvar chat_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedchat = addPrefix(object_default47, prefix);\n  addComponents({ ...prefixedchat });\n};\n\n// packages/daisyui/components/radialprogress/object.js\nvar object_default48 = { \".radial-progress\": { position: \"relative\", display: \"inline-grid\", height: \"var(--size)\", width: \"var(--size)\", \"place-content\": \"center\", \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"transparent\", \"vertical-align\": \"middle\", \"box-sizing\": \"content-box\", \"--value\": \"0\", \"--size\": \"5rem\", \"--thickness\": \"calc(var(--size) / 10)\", \"--radialprogress\": \"calc(var(--value) * 1%)\", transition: \"--radialprogress 0.3s linear\", \"&:before\": { position: \"absolute\", inset: \"calc(0.25rem * 0)\", \"border-radius\": \"calc(infinity * 1px)\", content: '\"\"', background: \"radial-gradient(farthest-side, currentColor 98%, #0000) top/var(--thickness) var(--thickness) no-repeat, conic-gradient(currentColor var(--radialprogress), #0000 0)\", \"webkit-mask\": \"radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )\", mask: \"radial-gradient( farthest-side, #0000 calc(100% - var(--thickness)), #000 calc(100% + 0.5px - var(--thickness)) )\" }, \"&:after\": { position: \"absolute\", \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"currentColor\", transition: \"transform 0.3s linear\", content: '\"\"', inset: \"calc(50% - var(--thickness) / 2)\", transform: \"rotate(calc(var(--value) * 3.6deg - 90deg)) translate(calc(var(--size) / 2 - 50%))\" } } };\n\n// packages/daisyui/components/radialprogress/index.js\nvar radialprogress_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedradialprogress = addPrefix(object_default48, prefix);\n  addComponents({ ...prefixedradialprogress });\n};\n\n// packages/daisyui/components/countdown/object.js\nvar object_default49 = { \".countdown\": { display: \"inline-flex\", \"&.countdown\": { \"line-height\": \"1em\" }, \"& > *\": { display: \"inline-block\", \"overflow-y\": \"hidden\", height: \"1em\", \"&:before\": { position: \"relative\", content: '\"00\\\\A 01\\\\A 02\\\\A 03\\\\A 04\\\\A 05\\\\A 06\\\\A 07\\\\A 08\\\\A 09\\\\A 10\\\\A 11\\\\A 12\\\\A 13\\\\A 14\\\\A 15\\\\A 16\\\\A 17\\\\A 18\\\\A 19\\\\A 20\\\\A 21\\\\A 22\\\\A 23\\\\A 24\\\\A 25\\\\A 26\\\\A 27\\\\A 28\\\\A 29\\\\A 30\\\\A 31\\\\A 32\\\\A 33\\\\A 34\\\\A 35\\\\A 36\\\\A 37\\\\A 38\\\\A 39\\\\A 40\\\\A 41\\\\A 42\\\\A 43\\\\A 44\\\\A 45\\\\A 46\\\\A 47\\\\A 48\\\\A 49\\\\A 50\\\\A 51\\\\A 52\\\\A 53\\\\A 54\\\\A 55\\\\A 56\\\\A 57\\\\A 58\\\\A 59\\\\A 60\\\\A 61\\\\A 62\\\\A 63\\\\A 64\\\\A 65\\\\A 66\\\\A 67\\\\A 68\\\\A 69\\\\A 70\\\\A 71\\\\A 72\\\\A 73\\\\A 74\\\\A 75\\\\A 76\\\\A 77\\\\A 78\\\\A 79\\\\A 80\\\\A 81\\\\A 82\\\\A 83\\\\A 84\\\\A 85\\\\A 86\\\\A 87\\\\A 88\\\\A 89\\\\A 90\\\\A 91\\\\A 92\\\\A 93\\\\A 94\\\\A 95\\\\A 96\\\\A 97\\\\A 98\\\\A 99\\\\A\"', \"white-space\": \"pre\", top: \"calc(var(--value) * -1em)\", \"text-align\": \"center\", transition: \"all 1s cubic-bezier(1, 0, 0, 1)\" } } } };\n\n// packages/daisyui/components/countdown/index.js\nvar countdown_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcountdown = addPrefix(object_default49, prefix);\n  addComponents({ ...prefixedcountdown });\n};\n\n// packages/daisyui/components/tooltip/object.js\nvar object_default50 = { \".tooltip\": { position: \"relative\", display: \"inline-block\", \"--tt-bg\": \"var(--color-neutral)\", \"--tt-off\": \"calc(100% + 0.5rem)\", \"--tt-tail\": \"calc(100% + 1px + 0.25rem)\", \"> :where(.tooltip-content), &:where([data-tip]):before\": { position: \"absolute\", \"max-width\": \"20rem\", \"border-radius\": \"var(--radius-field)\", \"padding-inline\": \"calc(0.25rem * 2)\", \"padding-block\": \"calc(0.25rem * 1)\", \"text-align\": \"center\", \"white-space\": \"normal\", color: \"var(--color-neutral-content)\", opacity: \"0%\", \"font-size\": \"0.875rem\", \"line-height\": 1.25, transition: \"opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms\", \"background-color\": \"var(--tt-bg)\", width: \"max-content\", \"pointer-events\": \"none\", \"z-index\": 1, \"--tw-content\": \"attr(data-tip)\", content: \"var(--tw-content)\" }, \"&:after\": { position: [\"absolute\", \"absolute\"], opacity: \"0%\", \"background-color\": \"var(--tt-bg)\", transition: \"opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 75ms\", content: '\"\"', \"pointer-events\": \"none\", width: \"0.625rem\", height: \"0.25rem\", display: \"block\", \"mask-repeat\": \"no-repeat\", \"mask-position\": \"-1px 0\", \"--mask-tooltip\": `url(\"data:image/svg+xml,%3Csvg width='10' height='4' viewBox='0 0 8 4' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.500009 1C3.5 1 3.00001 4 5.00001 4C7 4 6.5 1 9.5 1C10 1 10 0.499897 10 0H0C-1.99338e-08 0.5 0 1 0.500009 1Z' fill='black'/%3E%3C/svg%3E%0A\")`, \"mask-image\": \"var(--mask-tooltip)\" }, '&.tooltip-open, &[data-tip]:not([data-tip=\"\"]):hover, &:not(:has(.tooltip-content:empty)):has(.tooltip-content):hover, &:has(:focus-visible)': { \"> .tooltip-content, &[data-tip]:before, &:after\": { opacity: \"100%\", \"--tt-pos\": \"0rem\", transition: \"opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0s, transform 0.2s cubic-bezier(0.4, 0, 0.2, 1) 0ms\" } } }, \".tooltip, .tooltip-top\": { \"> .tooltip-content, &[data-tip]:before\": { transform: \"translateX(-50%) translateY(var(--tt-pos, 0.25rem))\", inset: \"auto auto var(--tt-off) 50%\" }, \"&:after\": { transform: \"translateX(-50%) translateY(var(--tt-pos, 0.25rem))\", inset: \"auto auto var(--tt-tail) 50%\" } }, \".tooltip-bottom\": { \"> .tooltip-content, &[data-tip]:before\": { transform: \"translateX(-50%) translateY(var(--tt-pos, -0.25rem))\", inset: \"var(--tt-off) auto auto 50%\" }, \"&:after\": { transform: \"translateX(-50%) translateY(var(--tt-pos, -0.25rem)) rotate(180deg)\", inset: \"var(--tt-tail) auto auto 50%\" } }, \".tooltip-left\": { \"> .tooltip-content, &[data-tip]:before\": { transform: \"translateX(calc(var(--tt-pos, 0.25rem) - 0.25rem)) translateY(-50%)\", inset: \"50% var(--tt-off) auto auto\" }, \"&:after\": { transform: \"translateX(var(--tt-pos, 0.25rem)) translateY(-50%) rotate(-90deg)\", inset: \"50% calc(var(--tt-tail) + 1px) auto auto\" } }, \".tooltip-right\": { \"> .tooltip-content, &[data-tip]:before\": { transform: \"translateX(calc(var(--tt-pos, -0.25rem) + 0.25rem)) translateY(-50%)\", inset: \"50% auto auto var(--tt-off)\" }, \"&:after\": { transform: \"translateX(var(--tt-pos, -0.25rem)) translateY(-50%) rotate(90deg)\", inset: \"50% auto auto calc(var(--tt-tail) + 1px)\" } }, \".tooltip-primary\": { \"--tt-bg\": \"var(--color-primary)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-primary-content)\" } }, \".tooltip-secondary\": { \"--tt-bg\": \"var(--color-secondary)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-secondary-content)\" } }, \".tooltip-accent\": { \"--tt-bg\": \"var(--color-accent)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-accent-content)\" } }, \".tooltip-info\": { \"--tt-bg\": \"var(--color-info)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-info-content)\" } }, \".tooltip-success\": { \"--tt-bg\": \"var(--color-success)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-success-content)\" } }, \".tooltip-warning\": { \"--tt-bg\": \"var(--color-warning)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-warning-content)\" } }, \".tooltip-error\": { \"--tt-bg\": \"var(--color-error)\", \"> .tooltip-content, &[data-tip]:before\": { color: \"var(--color-error-content)\" } } };\n\n// packages/daisyui/components/tooltip/index.js\nvar tooltip_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtooltip = addPrefix(object_default50, prefix);\n  addComponents({ ...prefixedtooltip });\n};\n\n// packages/daisyui/components/timeline/object.js\nvar object_default51 = { \".timeline\": { position: \"relative\", display: \"flex\", \"> li\": { position: \"relative\", display: \"grid\", \"flex-shrink\": 0, \"align-items\": \"center\", \"grid-template-rows\": \"var(--timeline-row-start, minmax(0, 1fr)) auto var( --timeline-row-end, minmax(0, 1fr) )\", \"grid-template-columns\": \"var(--timeline-col-start, minmax(0, 1fr)) auto var( --timeline-col-end, minmax(0, 1fr) )\", \"> hr\": { border: \"none\", width: \"100%\", \"&:first-child\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"2\" }, \"&:last-child\": { \"grid-column-start\": \"3\", \"grid-column-end\": \"none\", \"grid-row-start\": \"2\", \"grid-row-end\": \"auto\" }, \"@media print\": { border: \"0.1px solid var(--color-base-300)\" } } }, \":where(hr)\": { height: \"calc(0.25rem * 1)\", \"background-color\": \"var(--color-base-300)\" }, \"&:has(.timeline-middle hr)\": { \"&:first-child\": { \"border-start-start-radius\": \"0\", \"border-end-start-radius\": \"0\", \"border-start-end-radius\": \"var(--radius-selector)\", \"border-end-end-radius\": \"var(--radius-selector)\" }, \"&:last-child\": { \"border-start-start-radius\": \"var(--radius-selector)\", \"border-end-start-radius\": \"var(--radius-selector)\", \"border-start-end-radius\": \"0\", \"border-end-end-radius\": \"0\" } }, \"&:not(:has(.timeline-middle))\": { \":first-child hr:last-child\": { \"border-start-start-radius\": \"var(--radius-selector)\", \"border-end-start-radius\": \"var(--radius-selector)\", \"border-start-end-radius\": \"0\", \"border-end-end-radius\": \"0\" }, \":last-child hr:first-child\": { \"border-start-start-radius\": \"0\", \"border-end-start-radius\": \"0\", \"border-start-end-radius\": \"var(--radius-selector)\", \"border-end-end-radius\": \"var(--radius-selector)\" } } }, \".timeline-box\": { border: \"var(--border) solid\", \"border-radius\": \"var(--radius-box)\", \"border-color\": \"var(--color-base-300)\", \"background-color\": \"var(--color-base-100)\", \"padding-inline\": \"calc(0.25rem * 4)\", \"padding-block\": \"calc(0.25rem * 2)\", \"font-size\": \"0.75rem\", \"box-shadow\": \"0 1px 2px 0 oklch(0% 0 0/0.05)\" }, \".timeline-start\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"4\", \"grid-row-start\": \"1\", \"grid-row-end\": \"2\", margin: \"calc(0.25rem * 1)\", \"align-self\": \"flex-end\", \"justify-self\": \"center\" }, \".timeline-middle\": { \"grid-column-start\": \"2\", \"grid-row-start\": \"2\" }, \".timeline-end\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"4\", \"grid-row-start\": \"3\", \"grid-row-end\": \"4\", margin: \"calc(0.25rem * 1)\", \"align-self\": \"flex-start\", \"justify-self\": \"center\" }, \".timeline-compact\": { \"--timeline-row-start\": \"0\", \".timeline-start\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"4\", \"grid-row-start\": \"3\", \"grid-row-end\": \"4\", \"align-self\": \"flex-start\", \"justify-self\": \"center\" }, \"li:has(.timeline-start)\": { \".timeline-end\": { \"grid-column-start\": \"none\", \"grid-row-start\": \"auto\" } }, \"&.timeline-vertical\": { \"> li\": { \"--timeline-col-start\": \"0\" }, \".timeline-start\": { \"grid-column-start\": \"3\", \"grid-column-end\": \"4\", \"grid-row-start\": \"1\", \"grid-row-end\": \"4\", \"align-self\": \"center\", \"justify-self\": \"flex-start\" }, \"li:has(.timeline-start)\": { \".timeline-end\": { \"grid-column-start\": \"auto\", \"grid-row-start\": \"none\" } } } }, \".timeline-snap-icon\": { \"> li\": { \"--timeline-col-start\": \"0.5rem\", \"--timeline-row-start\": \"minmax(0, 1fr)\" } }, \".timeline-vertical\": { \"flex-direction\": \"column\", \"> li\": { \"justify-items\": \"center\", \"--timeline-row-start\": \"minmax(0, 1fr)\", \"--timeline-row-end\": \"minmax(0, 1fr)\", \"> hr\": { height: \"100%\", width: \"calc(0.25rem * 1)\", \"&:first-child\": { \"grid-column-start\": \"2\", \"grid-row-start\": \"1\" }, \"&:last-child\": { \"grid-column-start\": \"2\", \"grid-column-end\": \"auto\", \"grid-row-start\": \"3\", \"grid-row-end\": \"none\" } } }, \".timeline-start\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"2\", \"grid-row-start\": \"1\", \"grid-row-end\": \"4\", \"align-self\": \"center\", \"justify-self\": \"flex-end\" }, \".timeline-end\": { \"grid-column-start\": \"3\", \"grid-column-end\": \"4\", \"grid-row-start\": \"1\", \"grid-row-end\": \"4\", \"align-self\": \"center\", \"justify-self\": \"flex-start\" }, \"&:has(.timeline-middle)\": { \"> li\": { \"> hr\": { \"&:first-child\": { \"border-top-left-radius\": \"0\", \"border-top-right-radius\": \"0\", \"border-bottom-right-radius\": \"var(--radius-selector)\", \"border-bottom-left-radius\": \"var(--radius-selector)\" }, \"&:last-child\": { \"border-top-left-radius\": \"var(--radius-selector)\", \"border-top-right-radius\": \"var(--radius-selector)\", \"border-bottom-right-radius\": \"0\", \"border-bottom-left-radius\": \"0\" } } } }, \"&:not(:has(.timeline-middle))\": { \":first-child\": { \"> hr:last-child\": { \"border-top-left-radius\": \"var(--radius-selector)\", \"border-top-right-radius\": \"var(--radius-selector)\", \"border-bottom-right-radius\": \"0\", \"border-bottom-left-radius\": \"0\" } }, \":last-child\": { \"> hr:first-child\": { \"border-top-left-radius\": \"0\", \"border-top-right-radius\": \"0\", \"border-bottom-right-radius\": \"var(--radius-selector)\", \"border-bottom-left-radius\": \"var(--radius-selector)\" } } }, \"&.timeline-snap-icon\": { \"> li\": { \"--timeline-col-start\": \"minmax(0, 1fr)\", \"--timeline-row-start\": \"0.5rem\" } } }, \".timeline-horizontal\": { \"flex-direction\": \"row\", \"> li\": { \"align-items\": \"center\", \"> hr\": { height: \"calc(0.25rem * 1)\", width: \"100%\", \"&:first-child\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"2\" }, \"&:last-child\": { \"grid-column-start\": \"3\", \"grid-column-end\": \"none\", \"grid-row-start\": \"2\", \"grid-row-end\": \"auto\" } } }, \".timeline-start\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"4\", \"grid-row-start\": \"1\", \"grid-row-end\": \"2\", \"align-self\": \"flex-end\", \"justify-self\": \"center\" }, \".timeline-end\": { \"grid-column-start\": \"1\", \"grid-column-end\": \"4\", \"grid-row-start\": \"3\", \"grid-row-end\": \"4\", \"align-self\": \"flex-start\", \"justify-self\": \"center\" }, \"&:has(.timeline-middle)\": { \"> li\": { \"> hr\": { \"&:first-child\": { \"border-start-start-radius\": \"0\", \"border-end-start-radius\": \"0\", \"border-start-end-radius\": \"var(--radius-selector)\", \"border-end-end-radius\": \"var(--radius-selector)\" }, \"&:last-child\": { \"border-start-start-radius\": \"var(--radius-selector)\", \"border-end-start-radius\": \"var(--radius-selector)\", \"border-start-end-radius\": \"0\", \"border-end-end-radius\": \"0\" } } } }, \"&:not(:has(.timeline-middle))\": { \":first-child\": { \"> hr:last-child\": { \"border-start-start-radius\": \"var(--radius-selector)\", \"border-end-start-radius\": \"var(--radius-selector)\", \"border-start-end-radius\": \"0\", \"border-end-end-radius\": \"0\" } }, \":last-child\": { \"> hr:first-child\": { \"border-start-start-radius\": \"0\", \"border-end-start-radius\": \"0\", \"border-start-end-radius\": \"var(--radius-selector)\", \"border-end-end-radius\": \"var(--radius-selector)\" } } } } };\n\n// packages/daisyui/components/timeline/index.js\nvar timeline_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtimeline = addPrefix(object_default51, prefix);\n  addComponents({ ...prefixedtimeline });\n};\n\n// packages/daisyui/components/textarea/object.js\nvar object_default52 = { \".textarea\": { border: \"var(--border) solid #0000\", \"min-height\": \"calc(0.25rem * 20)\", \"flex-shrink\": 1, appearance: \"none\", \"border-radius\": \"var(--radius-field)\", \"background-color\": \"var(--color-base-100)\", \"padding-block\": \"calc(0.25rem * 2)\", \"vertical-align\": \"middle\", width: \"clamp(3rem, 20rem, 100%)\", \"padding-inline-start\": \"0.75rem\", \"padding-inline-end\": \"0.75rem\", \"font-size\": \"0.875rem\", \"border-color\": \"var(--input-color)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset\", \"--input-color\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", textarea: { appearance: \"none\", \"background-color\": \"transparent\", border: \"none\", \"&:focus, &:focus-within\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } } }, \"&:focus, &:focus-within\": { \"--input-color\": \"var(--color-base-content)\", \"box-shadow\": \"0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000)\", outline: \"2px solid var(--input-color)\", \"outline-offset\": \"2px\", isolation: \"isolate\" }, \"&:has(> textarea[disabled]), &:is(:disabled, [disabled])\": { cursor: \"not-allowed\", \"border-color\": \"var(--color-base-200)\", \"background-color\": \"var(--color-base-200)\", color: \"color-mix(in oklab, var(--color-base-content) 40%, transparent)\", \"&::placeholder\": { color: \"color-mix(in oklab, var(--color-base-content) 20%, transparent)\" }, \"box-shadow\": \"none\" }, \"&:has(> textarea[disabled]) > textarea[disabled]\": { cursor: \"not-allowed\" } }, \".textarea-ghost\": { \"background-color\": \"transparent\", \"box-shadow\": \"none\", \"border-color\": \"#0000\", \"&:focus, &:focus-within\": { \"background-color\": \"var(--color-base-100)\", color: \"var(--color-base-content)\", \"border-color\": \"#0000\", \"box-shadow\": \"none\" } }, \".textarea-neutral\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-neutral)\" } }, \".textarea-primary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-primary)\" } }, \".textarea-secondary\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-secondary)\" } }, \".textarea-accent\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-accent)\" } }, \".textarea-info\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-info)\" } }, \".textarea-success\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-success)\" } }, \".textarea-warning\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-warning)\" } }, \".textarea-error\": { \"&, &:focus, &:focus-within\": { \"--input-color\": \"var(--color-error)\" } }, \".textarea-xs\": { \"font-size\": \"0.6875rem\" }, \".textarea-sm\": { \"font-size\": \"0.75rem\" }, \".textarea-md\": { \"font-size\": \"0.875rem\" }, \".textarea-lg\": { \"font-size\": \"1.125rem\" }, \".textarea-xl\": { \"font-size\": \"1.375rem\" } };\n\n// packages/daisyui/components/textarea/index.js\nvar textarea_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedtextarea = addPrefix(object_default52, prefix);\n  addComponents({ ...prefixedtextarea });\n};\n\n// packages/daisyui/components/range/object.js\nvar object_default53 = { \".range\": { appearance: \"none\", \"webkit-appearance\": \"none\", \"--range-thumb\": \"var(--color-base-100)\", \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 6)\", \"--range-progress\": \"currentColor\", \"--range-fill\": \"1\", \"--range-p\": \"0.25rem\", \"--range-bg\": \"color-mix(in oklab, currentColor 10%, #0000)\", cursor: \"pointer\", overflow: \"hidden\", \"background-color\": \"transparent\", \"vertical-align\": \"middle\", width: \"clamp(3rem, 20rem, 100%)\", \"--radius-selector-max\": `calc(\n    var(--radius-selector) + var(--radius-selector) + var(--radius-selector)\n  )`, \"border-radius\": \"calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))\", border: \"none\", height: \"var(--range-thumb-size)\", '[dir=\"rtl\"] &': { \"--range-dir\": \"-1\" }, \"&:focus\": { outline: \"none\" }, \"&:focus-visible\": { outline: \"2px solid\", \"outline-offset\": \"2px\" }, \"&::-webkit-slider-runnable-track\": { width: \"100%\", \"background-color\": \"var(--range-bg)\", \"border-radius\": \"var(--radius-selector)\", height: \"calc(var(--range-thumb-size) * 0.5)\" }, \"@media (forced-colors: active)\": [{ \"&::-webkit-slider-runnable-track\": { border: \"1px solid\" } }, { \"&::-moz-range-track\": { border: \"1px solid\" } }], \"&::-webkit-slider-thumb\": { position: \"relative\", \"box-sizing\": \"border-box\", \"border-radius\": \"calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))\", \"background-color\": \"currentColor\", height: \"var(--range-thumb-size)\", width: \"var(--range-thumb-size)\", border: \"var(--range-p) solid\", appearance: \"none\", \"webkit-appearance\": \"none\", top: \"50%\", color: \"var(--range-progress)\", transform: \"translateY(-50%)\", \"box-shadow\": \"0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))\" }, \"&::-moz-range-track\": { width: \"100%\", \"background-color\": \"var(--range-bg)\", \"border-radius\": \"var(--radius-selector)\", height: \"calc(var(--range-thumb-size) * 0.5)\" }, \"&::-moz-range-thumb\": { position: \"relative\", \"box-sizing\": \"border-box\", \"border-radius\": \"calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)))\", \"background-color\": \"currentColor\", height: \"var(--range-thumb-size)\", width: \"var(--range-thumb-size)\", border: \"var(--range-p) solid\", top: \"50%\", color: \"var(--range-progress)\", \"box-shadow\": \"0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill))\" }, \"&:disabled\": { cursor: \"not-allowed\", opacity: \"30%\" } }, \".range-primary\": { color: \"var(--color-primary)\", \"--range-thumb\": \"var(--color-primary-content)\" }, \".range-secondary\": { color: \"var(--color-secondary)\", \"--range-thumb\": \"var(--color-secondary-content)\" }, \".range-accent\": { color: \"var(--color-accent)\", \"--range-thumb\": \"var(--color-accent-content)\" }, \".range-neutral\": { color: \"var(--color-neutral)\", \"--range-thumb\": \"var(--color-neutral-content)\" }, \".range-success\": { color: \"var(--color-success)\", \"--range-thumb\": \"var(--color-success-content)\" }, \".range-warning\": { color: \"var(--color-warning)\", \"--range-thumb\": \"var(--color-warning-content)\" }, \".range-info\": { color: \"var(--color-info)\", \"--range-thumb\": \"var(--color-info-content)\" }, \".range-error\": { color: \"var(--color-error)\", \"--range-thumb\": \"var(--color-error-content)\" }, \".range-xs\": { \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 4)\" }, \".range-sm\": { \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 5)\" }, \".range-md\": { \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 6)\" }, \".range-lg\": { \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 7)\" }, \".range-xl\": { \"--range-thumb-size\": \"calc(var(--size-selector, 0.25rem) * 8)\" } };\n\n// packages/daisyui/components/range/index.js\nvar range_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedrange = addPrefix(object_default53, prefix);\n  addComponents({ ...prefixedrange });\n};\n\n// packages/daisyui/components/dock/object.js\nvar object_default54 = { \".dock\": { position: \"fixed\", right: \"calc(0.25rem * 0)\", bottom: \"calc(0.25rem * 0)\", left: \"calc(0.25rem * 0)\", \"z-index\": 1, display: \"flex\", width: \"100%\", \"flex-direction\": \"row\", \"align-items\": \"center\", \"justify-content\": \"space-around\", \"background-color\": \"var(--color-base-100)\", padding: \"calc(0.25rem * 2)\", color: \"currentColor\", \"border-top\": \"0.5px solid color-mix(in oklab, var(--color-base-content) 5%, #0000)\", height: [\"4rem\", \"calc(4rem + env(safe-area-inset-bottom))\"], \"padding-bottom\": \"env(safe-area-inset-bottom)\", \"> *\": { position: \"relative\", \"margin-bottom\": \"calc(0.25rem * 2)\", display: \"flex\", height: \"100%\", \"max-width\": \"calc(0.25rem * 32)\", \"flex-shrink\": 1, \"flex-basis\": \"100%\", cursor: \"pointer\", \"flex-direction\": \"column\", \"align-items\": \"center\", \"justify-content\": \"center\", gap: \"1px\", \"border-radius\": \"var(--radius-box)\", \"background-color\": \"transparent\", transition: \"opacity 0.2s ease-out\", \"@media (hover: hover)\": { \"&:hover\": { opacity: \"80%\" } }, '&[aria-disabled=\"true\"], &[disabled]': { \"&, &:hover\": { \"pointer-events\": \"none\", color: \"color-mix(in oklab, var(--color-base-content) 10%, transparent)\", opacity: \"100%\" } }, \".dock-label\": { \"font-size\": \"0.6875rem\" }, \"&:after\": { content: '\"\"', position: \"absolute\", height: \"calc(0.25rem * 1)\", width: \"calc(0.25rem * 6)\", \"border-radius\": \"calc(infinity * 1px)\", \"background-color\": \"transparent\", bottom: \"0.2rem\", \"border-top\": \"3px solid transparent\", transition: \"background-color 0.1s ease-out, text-color 0.1s ease-out, width 0.1s ease-out\" } } }, \".dock-active\": { \"&:after\": { width: \"calc(0.25rem * 10)\", \"background-color\": \"currentColor\", color: \"currentColor\" } }, \".dock-xs\": { height: [\"3rem\", \"calc(3rem + env(safe-area-inset-bottom))\"], \".dock-active\": { \"&:after\": { bottom: \"-0.1rem\" } }, \".dock-label\": { \"font-size\": \"0.625rem\" } }, \".dock-sm\": { height: [\"calc(0.25rem * 14)\", \"3.5rem\", \"calc(3.5rem + env(safe-area-inset-bottom))\"], \".dock-active\": { \"&:after\": { bottom: \"-0.1rem\" } }, \".dock-label\": { \"font-size\": \"0.625rem\" } }, \".dock-md\": { height: [\"4rem\", \"calc(4rem + env(safe-area-inset-bottom))\"], \".dock-label\": { \"font-size\": \"0.6875rem\" } }, \".dock-lg\": { height: [\"4.5rem\", \"calc(4.5rem + env(safe-area-inset-bottom))\"], \".dock-active\": { \"&:after\": { bottom: \"0.4rem\" } }, \".dock-label\": { \"font-size\": \"0.6875rem\" } }, \".dock-xl\": { height: [\"5rem\", \"calc(5rem + env(safe-area-inset-bottom))\"], \".dock-active\": { \"&:after\": { bottom: \"0.4rem\" } }, \".dock-label\": { \"font-size\": \"0.75rem\" } } };\n\n// packages/daisyui/components/dock/index.js\nvar dock_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixeddock = addPrefix(object_default54, prefix);\n  addComponents({ ...prefixeddock });\n};\n\n// packages/daisyui/components/breadcrumbs/object.js\nvar object_default55 = { \".breadcrumbs\": { \"max-width\": \"100%\", \"overflow-x\": \"auto\", \"padding-block\": \"calc(0.25rem * 2)\", \"> menu, > ul, > ol\": { display: \"flex\", \"min-height\": \"min-content\", \"align-items\": \"center\", \"white-space\": \"nowrap\", \"> li\": { display: \"flex\", \"align-items\": \"center\", \"> *\": { display: \"flex\", cursor: \"pointer\", \"align-items\": \"center\", gap: \"calc(0.25rem * 2)\", \"&:hover\": { \"@media (hover: hover)\": { \"text-decoration-line\": \"underline\" } }, \"&:focus\": { \"--tw-outline-style\": \"none\", \"outline-style\": \"none\", \"@media (forced-colors: active)\": { outline: \"2px solid transparent\", \"outline-offset\": \"2px\" } }, \"&:focus-visible\": { outline: \"2px solid currentColor\", \"outline-offset\": \"2px\" } }, \"& + *:before\": { content: '\"\"', \"margin-right\": \"calc(0.25rem * 3)\", \"margin-left\": \"calc(0.25rem * 2)\", display: \"block\", height: \"calc(0.25rem * 1.5)\", width: \"calc(0.25rem * 1.5)\", opacity: \"40%\", rotate: \"45deg\", \"border-top\": \"1px solid\", \"border-right\": \"1px solid\", \"background-color\": \"#0000\" }, '[dir=\"rtl\"] & + *:before': { rotate: \"-135deg\" } } } } };\n\n// packages/daisyui/components/breadcrumbs/index.js\nvar breadcrumbs_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedbreadcrumbs = addPrefix(object_default55, prefix);\n  addComponents({ ...prefixedbreadcrumbs });\n};\n\n// packages/daisyui/components/radio/object.js\nvar object_default56 = { \".radio\": { position: \"relative\", \"flex-shrink\": 0, cursor: \"pointer\", appearance: \"none\", \"border-radius\": \"calc(infinity * 1px)\", padding: \"calc(0.25rem * 1)\", \"vertical-align\": \"middle\", border: \"var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000))\", \"box-shadow\": \"0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset\", \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\", width: \"var(--size)\", height: \"var(--size)\", color: \"var(--input-color, currentColor)\", \"&:before\": { display: \"block\", width: \"100%\", height: \"100%\", \"border-radius\": \"calc(infinity * 1px)\", \"--tw-content\": '\"\"', content: \"var(--tw-content)\", \"background-size\": \"auto, calc(var(--noise) * 100%)\", \"background-image\": \"none, var(--fx-noise)\" }, \"&:focus-visible\": { outline: \"2px solid currentColor\" }, '&:checked, &[aria-checked=\"true\"]': { animation: \"radio 0.2s ease-out\", \"border-color\": \"currentColor\", \"background-color\": \"var(--color-base-100)\", \"&:before\": { \"background-color\": \"currentColor\", \"box-shadow\": \"0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1))\" }, \"@media (forced-colors: active)\": { \"&:before\": { \"outline-style\": \"var(--tw-outline-style)\", \"outline-width\": \"1px\", \"outline-offset\": \"calc(1px * -1)\" } }, \"@media print\": { \"&:before\": { outline: \"0.25rem solid\", \"outline-offset\": \"-1rem\" } } } }, \".radio-primary\": { \"--input-color\": \"var(--color-primary)\" }, \".radio-secondary\": { \"--input-color\": \"var(--color-secondary)\" }, \".radio-accent\": { \"--input-color\": \"var(--color-accent)\" }, \".radio-neutral\": { \"--input-color\": \"var(--color-neutral)\" }, \".radio-info\": { \"--input-color\": \"var(--color-info)\" }, \".radio-success\": { \"--input-color\": \"var(--color-success)\" }, \".radio-warning\": { \"--input-color\": \"var(--color-warning)\" }, \".radio-error\": { \"--input-color\": \"var(--color-error)\" }, \".radio:disabled\": { cursor: \"not-allowed\", opacity: \"20%\" }, \".radio-xs\": { padding: \"0.125rem\", '&:is([type=\"radio\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 4)\" } }, \".radio-sm\": { padding: \"0.1875rem\", '&:is([type=\"radio\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 5)\" } }, \".radio-md\": { padding: \"0.25rem\", '&:is([type=\"radio\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 6)\" } }, \".radio-lg\": { padding: \"0.3125rem\", '&:is([type=\"radio\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 7)\" } }, \".radio-xl\": { padding: \"0.375rem\", '&:is([type=\"radio\"])': { \"--size\": \"calc(var(--size-selector, 0.25rem) * 8)\" } }, \"@keyframes radio\": { \"0%\": { padding: \"5px\" }, \"50%\": { padding: \"3px\" } } };\n\n// packages/daisyui/components/radio/index.js\nvar radio_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedradio = addPrefix(object_default56, prefix);\n  addComponents({ ...prefixedradio });\n};\n\n// packages/daisyui/components/skeleton/object.js\nvar object_default57 = { \".skeleton\": { \"border-radius\": \"var(--radius-box)\", \"background-color\": \"var(--color-base-300)\", \"@media (prefers-reduced-motion: reduce)\": { \"transition-duration\": \"15s\" }, \"will-change\": \"background-position\", animation: \"skeleton 1.8s ease-in-out infinite\", \"background-image\": \"linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% )\", \"background-size\": \"200% auto\", \"background-repeat\": \"no-repeat\", \"background-position-x\": \"-50%\" }, \"@keyframes skeleton\": { \"0%\": { \"background-position\": \"150%\" }, \"100%\": { \"background-position\": \"-50%\" } } };\n\n// packages/daisyui/components/skeleton/index.js\nvar skeleton_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedskeleton = addPrefix(object_default57, prefix);\n  addComponents({ ...prefixedskeleton });\n};\n\n// packages/daisyui/components/loading/object.js\nvar object_default58 = { \".loading\": { \"pointer-events\": \"none\", display: \"inline-block\", \"aspect-ratio\": \"1 / 1\", \"background-color\": \"currentColor\", \"vertical-align\": \"middle\", width: \"calc(var(--size-selector, 0.25rem) * 6)\", \"mask-size\": \"100%\", \"mask-repeat\": \"no-repeat\", \"mask-position\": \"center\", \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E\")` }, \".loading-spinner\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='24' height='24' stroke='black' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform-origin='center'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3' stroke-linecap='round'%3E%3CanimateTransform attributeName='transform' type='rotate' from='0 12 12' to='360 12 12' dur='2s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dasharray' values='0,150;42,150;42,150' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-dashoffset' values='0;-16;-59' keyTimes='0;0.475;1' dur='1.5s' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E\")` }, \".loading-dots\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3C/circle%3E%3Ccircle cx='12' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.1s'/%3E%3C/circle%3E%3Ccircle cx='20' cy='12' r='3'%3E%3Canimate attributeName='cy' values='12;6;12;12' keyTimes='0;0.286;0.571;1' dur='1.05s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1' begin='0.2s'/%3E%3C/circle%3E%3C/svg%3E\")` }, \".loading-ring\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='44' height='44' viewBox='0 0 44 44' xmlns='http://www.w3.org/2000/svg' stroke='white'%3E%3Cg fill='none' fill-rule='evenodd' stroke-width='2'%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='0s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='0s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3Ccircle cx='22' cy='22' r='1'%3E%3Canimate attributeName='r' begin='-0.9s' dur='1.8s' values='1;20' calcMode='spline' keyTimes='0;1' keySplines='0.165,0.84,0.44,1' repeatCount='indefinite'/%3E%3Canimate attributeName='stroke-opacity' begin='-0.9s' dur='1.8s' values='1;0' calcMode='spline' keyTimes='0;1' keySplines='0.3,0.61,0.355,1' repeatCount='indefinite'/%3E%3C/circle%3E%3C/g%3E%3C/svg%3E\")` }, \".loading-ball\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cellipse cx='12' cy='5' rx='4' ry='4'%3E%3Canimate attributeName='cy' values='5;20;20.5;20;5' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite' keySplines='.33,0,.66,.33;.33,.66,.66,1'/%3E%3Canimate attributeName='rx' values='4;4;4.8;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='ry' values='4;4;3;4;4' keyTimes='0;0.469;0.5;0.531;1' dur='.8s' repeatCount='indefinite'/%3E%3C/ellipse%3E%3C/svg%3E\")` }, \".loading-bars\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite'/%3E%3C/rect%3E%3Crect x='9' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.65s'/%3E%3C/rect%3E%3Crect x='17' y='1' width='6' height='22'%3E%3Canimate attributeName='y' values='1;5;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='height' values='22;14;22' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3Canimate attributeName='opacity' values='1;0.2;1' keyTimes='0;0.938;1' dur='.8s' repeatCount='indefinite' begin='-0.5s'/%3E%3C/rect%3E%3C/svg%3E\")` }, \".loading-infinity\": { \"mask-image\": `url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' style='shape-rendering:auto;' width='200px' height='200px' viewBox='0 0 100 100' preserveAspectRatio='xMidYMid'%3E%3Cpath fill='none' stroke='black' stroke-width='10' stroke-dasharray='205.271 51.318' d='M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z' stroke-linecap='round' style='transform:scale(0.8);transform-origin:50px 50px'%3E%3Canimate attributeName='stroke-dashoffset' repeatCount='indefinite' dur='2s' keyTimes='0;1' values='0;256.589'/%3E%3C/path%3E%3C/svg%3E\")` }, \".loading-xs\": { width: \"calc(var(--size-selector, 0.25rem) * 4)\" }, \".loading-sm\": { width: \"calc(var(--size-selector, 0.25rem) * 5)\" }, \".loading-md\": { width: \"calc(var(--size-selector, 0.25rem) * 6)\" }, \".loading-lg\": { width: \"calc(var(--size-selector, 0.25rem) * 7)\" }, \".loading-xl\": { width: \"calc(var(--size-selector, 0.25rem) * 8)\" } };\n\n// packages/daisyui/components/loading/index.js\nvar loading_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedloading = addPrefix(object_default58, prefix);\n  addComponents({ ...prefixedloading });\n};\n\n// packages/daisyui/components/validator/object.js\nvar object_default59 = { \".validator\": { \"&:user-valid, &:has(:user-valid)\": { '&, &:focus, &:checked, &[aria-checked=\"true\"], &:focus-within': { \"--input-color\": \"var(--color-success)\" } }, \"&:user-invalid, &:has(:user-invalid), &[aria-invalid]\": { '&, &:focus, &:checked, &[aria-checked=\"true\"], &:focus-within': { \"--input-color\": \"var(--color-error)\" }, \"& ~ .validator-hint\": { visibility: \"visible\", display: \"block\", color: \"var(--color-error)\" } } }, \".validator-hint\": { visibility: \"hidden\", \"margin-top\": \"calc(0.25rem * 2)\", \"font-size\": \"0.75rem\" } };\n\n// packages/daisyui/components/validator/index.js\nvar validator_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedvalidator = addPrefix(object_default59, prefix);\n  addComponents({ ...prefixedvalidator });\n};\n\n// packages/daisyui/components/collapse/object.js\nvar object_default60 = { \".collapse:not(td, tr, colgroup)\": { visibility: \"visible\" }, \".collapse\": { position: \"relative\", display: \"grid\", overflow: \"hidden\", \"border-radius\": \"var(--radius-box, 1rem)\", width: \"100%\", \"grid-template-rows\": \"max-content 0fr\", transition: \"grid-template-rows 0.2s\", isolation: \"isolate\", '> input:is([type=\"checkbox\"], [type=\"radio\"])': { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", appearance: \"none\", opacity: 0, \"z-index\": 1, width: \"100%\", padding: \"1rem\", \"padding-inline-end\": \"3rem\", \"min-height\": \"3.75rem\", transition: \"background-color 0.2s ease-out\" }, '&:is([open], :focus:not(.collapse-close)), &:not(.collapse-close):has(> input:is([type=\"checkbox\"], [type=\"radio\"]):checked)': { \"grid-template-rows\": \"max-content 1fr\" }, '&:is([open], :focus:not(.collapse-close)) > .collapse-content, &:not(.collapse-close) > :where(input:is([type=\"checkbox\"], [type=\"radio\"]):checked ~ .collapse-content)': { visibility: \"visible\", \"min-height\": \"fit-content\" }, '&:focus-visible, &:has(> input:is([type=\"checkbox\"], [type=\"radio\"]):focus-visible)': { \"outline-color\": \"var(--color-base-content)\", \"outline-style\": \"solid\", \"outline-width\": \"2px\", \"outline-offset\": \"2px\" }, \"&:not(.collapse-close)\": { '> input[type=\"checkbox\"], > input[type=\"radio\"]:not(:checked), > .collapse-title': { cursor: \"pointer\" } }, \"&:focus:not(.collapse-close, .collapse[open]) > .collapse-title\": { cursor: \"unset\" }, '&:is([open], :focus:not(.collapse-close)) > :where(.collapse-content), &:not(.collapse-close) > :where(input:is([type=\"checkbox\"], [type=\"radio\"]):checked ~ .collapse-content)': { \"padding-bottom\": \"1rem\", transition: \"padding 0.2s ease-out, background-color 0.2s ease-out\" }, \"&:is([open])\": { \"&.collapse-arrow\": { \"> .collapse-title:after\": { transform: \"translateY(-50%) rotate(225deg)\" } } }, \"&.collapse-open\": { \"&.collapse-arrow\": { \"> .collapse-title:after\": { transform: \"translateY(-50%) rotate(225deg)\" } }, \"&.collapse-plus\": { \"> .collapse-title:after\": { content: '\"−\"' } } }, \"&.collapse-arrow:focus:not(.collapse-close)\": { \"> .collapse-title:after\": { transform: \"translateY(-50%) rotate(225deg)\" } }, \"&.collapse-arrow:not(.collapse-close)\": { '> input:is([type=\"checkbox\"], [type=\"radio\"]):checked ~ .collapse-title:after': { transform: \"translateY(-50%) rotate(225deg)\" } }, \"&[open]\": { \"&.collapse-plus\": { \"> .collapse-title:after\": { content: '\"−\"' } } }, \"&.collapse-plus:focus:not(.collapse-close)\": { \"> .collapse-title:after\": { content: '\"−\"' } }, \"&.collapse-plus:not(.collapse-close)\": { '> input:is([type=\"checkbox\"], [type=\"radio\"]):checked ~ .collapse-title:after': { content: '\"−\"' } } }, \".collapse-title, .collapse-content\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\" }, \".collapse-content\": { visibility: \"hidden\", \"grid-column-start\": \"1\", \"grid-row-start\": \"2\", \"min-height\": \"0\", \"padding-left\": \"1rem\", \"padding-right\": \"1rem\", cursor: \"unset\", transition: \"visibility 0.2s, padding 0.2s ease-out, background-color 0.2s ease-out\" }, \".collapse:is(details)\": { width: \"100%\", \"& summary\": { position: \"relative\", display: \"block\", \"&::-webkit-details-marker\": { display: \"none\" } } }, \".collapse:is(details) summary\": { outline: \"none\" }, \".collapse-arrow\": { \"> .collapse-title:after\": { position: \"absolute\", display: \"block\", height: \"0.5rem\", width: \"0.5rem\", transform: \"translateY(-100%) rotate(45deg)\", \"transition-property\": \"all\", \"transition-timing-function\": \"cubic-bezier(0.4, 0, 0.2, 1)\", \"transition-duration\": \"0.2s\", top: \"1.9rem\", \"inset-inline-end\": \"1.4rem\", content: '\"\"', \"transform-origin\": \"75% 75%\", \"box-shadow\": \"2px 2px\", \"pointer-events\": \"none\" } }, \".collapse-plus\": { \"> .collapse-title:after\": { position: \"absolute\", display: \"block\", height: \"0.5rem\", width: \"0.5rem\", \"transition-property\": \"all\", \"transition-duration\": \"300ms\", \"transition-timing-function\": \"cubic-bezier(0.4, 0, 0.2, 1)\", top: \"0.9rem\", \"inset-inline-end\": \"1.4rem\", content: '\"+\"', \"pointer-events\": \"none\" } }, \".collapse-title\": { position: \"relative\", width: \"100%\", padding: \"1rem\", \"padding-inline-end\": \"3rem\", \"min-height\": \"3.75rem\", transition: \"background-color 0.2s ease-out\" }, \".collapse-open\": { \"grid-template-rows\": \"max-content 1fr\", \"> .collapse-content\": { visibility: \"visible\", \"min-height\": \"fit-content\", \"padding-bottom\": \"1rem\", transition: \"padding 0.2s ease-out, background-color 0.2s ease-out\" } } };\n\n// packages/daisyui/components/collapse/index.js\nvar collapse_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedcollapse = addPrefix(object_default60, prefix);\n  addComponents({ ...prefixedcollapse });\n};\n\n// packages/daisyui/components/swap/object.js\nvar object_default61 = { \".swap\": { position: \"relative\", display: \"inline-grid\", cursor: \"pointer\", \"place-content\": \"center\", \"vertical-align\": \"middle\", \"webkit-user-select\": \"none\", \"user-select\": \"none\", input: { appearance: \"none\", border: \"none\" }, \"> *\": { \"grid-column-start\": \"1\", \"grid-row-start\": \"1\", \"transition-property\": \"transform, rotate, opacity\", \"transition-duration\": \"0.2s\", \"transition-timing-function\": \"cubic-bezier(0, 0, 0.2, 1)\" }, \".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on\": { opacity: \"0%\" }, \"input:is(:checked, :indeterminate)\": { \"& ~ .swap-off\": { opacity: \"0%\" } }, \"input:checked ~ .swap-on, input:indeterminate ~ .swap-indeterminate\": { opacity: \"100%\", \"backface-visibility\": \"visible\" } }, \".swap-active\": { \".swap-off\": { opacity: \"0%\" }, \".swap-on\": { opacity: \"100%\" } }, \".swap-rotate\": { \".swap-on, input:indeterminate ~ .swap-on\": { rotate: \"45deg\" }, \"input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on\": { rotate: \"0deg\" }, \"input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off\": { rotate: \"calc(45deg * -1)\" } }, \".swap-flip\": { \"transform-style\": \"preserve-3d\", perspective: \"20rem\", \".swap-on, .swap-indeterminate, input:indeterminate ~ .swap-on\": { transform: \"rotateY(180deg)\", \"backface-visibility\": \"hidden\" }, \"input:is(:checked, :indeterminate) ~ .swap-on, &.swap-active .swap-on\": { transform: \"rotateY(0deg)\" }, \"input:is(:checked, :indeterminate) ~ .swap-off, &.swap-active .swap-off\": { transform: \"rotateY(-180deg)\", \"backface-visibility\": \"hidden\", opacity: \"100%\" } } };\n\n// packages/daisyui/components/swap/index.js\nvar swap_default = ({ addComponents, prefix = \"\" }) => {\n  const prefixedswap = addPrefix(object_default61, prefix);\n  addComponents({ ...prefixedswap });\n};\n\n// packages/daisyui/utilities/typography/object.js\nvar object_default62 = { \":root .prose\": { \"--tw-prose-body\": \"color-mix(in oklab, var(--color-base-content) 80%, #0000)\", \"--tw-prose-headings\": \"var(--color-base-content)\", \"--tw-prose-lead\": \"var(--color-base-content)\", \"--tw-prose-links\": \"var(--color-base-content)\", \"--tw-prose-bold\": \"var(--color-base-content)\", \"--tw-prose-counters\": \"var(--color-base-content)\", \"--tw-prose-bullets\": \"color-mix(in oklab, var(--color-base-content) 50%, #0000)\", \"--tw-prose-hr\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"--tw-prose-quotes\": \"var(--color-base-content)\", \"--tw-prose-quote-borders\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"--tw-prose-captions\": \"color-mix(in oklab, var(--color-base-content) 50%, #0000)\", \"--tw-prose-code\": \"var(--color-base-content)\", \"--tw-prose-pre-code\": \"var(--color-neutral-content)\", \"--tw-prose-pre-bg\": \"var(--color-neutral)\", \"--tw-prose-th-borders\": \"color-mix(in oklab, var(--color-base-content) 50%, #0000)\", \"--tw-prose-td-borders\": \"color-mix(in oklab, var(--color-base-content) 20%, #0000)\", \"--tw-prose-kbd\": \"color-mix(in oklab, var(--color-base-content) 80%, #0000)\", \":where(code):not(pre > code)\": { \"background-color\": \"var(--color-base-200)\", \"border-radius\": \"var(--radius-selector)\", border: \"var(--border) solid var(--color-base-300)\", \"padding-inline\": \"0.5em\", \"font-weight\": \"inherit\", \"&:before, &:after\": { display: \"none\" } } } };\n\n// packages/daisyui/utilities/typography/index.js\nvar typography_default = ({ addUtilities, prefix = \"\" }) => {\n  const prefixedtypography = addPrefix(object_default62, prefix);\n  addUtilities({ ...prefixedtypography });\n};\n\n// packages/daisyui/utilities/glass/object.js\nvar object_default63 = { \".glass\": { border: \"none\", \"backdrop-filter\": \"blur(var(--glass-blur, 40px))\", \"background-color\": \"#0000\", \"background-image\": \"linear-gradient( 135deg, oklch(100% 0 0 / var(--glass-opacity, 30%)) 0%, oklch(0% 0 0 / 0%) 100% ), linear-gradient( var(--glass-reflect-degree, 100deg), oklch(100% 0 0 / var(--glass-reflect-opacity, 5%)) 25%, oklch(0% 0 0 / 0%) 25% )\", \"box-shadow\": \"0 0 0 1px oklch(100% 0 0 / var(--glass-border-opacity, 20%)) inset, 0 0 0 2px oklch(0% 0 0 / 5%)\", \"text-shadow\": \"0 1px oklch(0% 0 0 / var(--glass-text-shadow-opacity, 5%))\" } };\n\n// packages/daisyui/utilities/glass/index.js\nvar glass_default = ({ addUtilities, prefix = \"\" }) => {\n  const prefixedglass = addPrefix(object_default63, prefix);\n  addUtilities({ ...prefixedglass });\n};\n\n// packages/daisyui/utilities/join/object.js\nvar object_default64 = { \".join\": { display: \"inline-flex\", \"align-items\": \"stretch\", \"--join-ss\": \"0\", \"--join-se\": \"0\", \"--join-es\": \"0\", \"--join-ee\": \"0\", \":where(.join-item)\": { \"border-start-start-radius\": \"var(--join-ss, 0)\", \"border-start-end-radius\": \"var(--join-se, 0)\", \"border-end-start-radius\": \"var(--join-es, 0)\", \"border-end-end-radius\": \"var(--join-ee, 0)\", \"*\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" } }, \"> .join-item:where(:first-child)\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"0\" }, \":first-child:not(:last-child)\": { \":where(.join-item)\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"0\" } }, \"> .join-item:where(:last-child)\": { \"--join-ss\": \"0\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"var(--radius-field)\" }, \":last-child:not(:first-child)\": { \":where(.join-item)\": { \"--join-ss\": \"0\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"var(--radius-field)\" } }, \"> .join-item:where(:only-child)\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" }, \":only-child\": { \":where(.join-item)\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" } } }, \".join-item\": { \"&:where(*:not(:first-child, :disabled, [disabled], .btn-disabled))\": { \"margin-inline-start\": \"calc(var(--border, 1px) * -1)\", \"margin-block-start\": \"0\" } }, \".join-vertical\": { \"flex-direction\": \"column\", \"> .join-item:first-child\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"0\" }, \":first-child:not(:last-child)\": { \".join-item\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"0\" } }, \"> .join-item:last-child\": { \"--join-ss\": \"0\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" }, \":last-child:not(:first-child)\": { \".join-item\": { \"--join-ss\": \"0\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" } }, \"> .join-item:only-child\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" }, \":only-child\": { \".join-item\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" } }, \".join-item\": { \"&:where(*:not(:first-child))\": { \"margin-inline-start\": \"0\", \"margin-block-start\": \"calc(var(--border, 1px) * -1)\" } } }, \".join-horizontal\": { \"flex-direction\": \"row\", \"> .join-item:first-child\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"0\" }, \":first-child:not(:last-child)\": { \".join-item\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"0\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"0\" } }, \"> .join-item:last-child\": { \"--join-ss\": \"0\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"var(--radius-field)\" }, \":last-child:not(:first-child)\": { \".join-item\": { \"--join-ss\": \"0\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"0\", \"--join-ee\": \"var(--radius-field)\" } }, \"> .join-item:only-child\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" }, \":only-child\": { \".join-item\": { \"--join-ss\": \"var(--radius-field)\", \"--join-se\": \"var(--radius-field)\", \"--join-es\": \"var(--radius-field)\", \"--join-ee\": \"var(--radius-field)\" } }, \".join-item\": { \"&:where(*:not(:first-child))\": { \"margin-inline-start\": \"calc(var(--border, 1px) * -1)\", \"margin-block-start\": \"0\" } } } };\n\n// packages/daisyui/utilities/join/index.js\nvar join_default = ({ addUtilities, prefix = \"\" }) => {\n  const prefixedjoin = addPrefix(object_default64, prefix);\n  addUtilities({ ...prefixedjoin });\n};\n\n// packages/daisyui/utilities/radius/object.js\nvar object_default65 = { \".rounded-box\": { \"border-radius\": \"var(--radius-box)\" }, \".rounded-field\": { \"border-radius\": \"var(--radius-field)\" }, \".rounded-selector\": { \"border-radius\": \"var(--radius-selector)\" }, \".rounded-t-box\": { \"border-top-left-radius\": \"var(--radius-box)\", \"border-top-right-radius\": \"var(--radius-box)\" }, \".rounded-b-box\": { \"border-bottom-left-radius\": \"var(--radius-box)\", \"border-bottom-right-radius\": \"var(--radius-box)\" }, \".rounded-l-box\": { \"border-top-left-radius\": \"var(--radius-box)\", \"border-bottom-left-radius\": \"var(--radius-box)\" }, \".rounded-r-box\": { \"border-top-right-radius\": \"var(--radius-box)\", \"border-bottom-right-radius\": \"var(--radius-box)\" }, \".rounded-tl-box\": { \"border-top-left-radius\": \"var(--radius-box)\" }, \".rounded-tr-box\": { \"border-top-right-radius\": \"var(--radius-box)\" }, \".rounded-br-box\": { \"border-bottom-right-radius\": \"var(--radius-box)\" }, \".rounded-bl-box\": { \"border-bottom-left-radius\": \"var(--radius-box)\" }, \".rounded-t-field\": { \"border-top-left-radius\": \"var(--radius-field)\", \"border-top-right-radius\": \"var(--radius-field)\" }, \".rounded-b-field\": { \"border-bottom-left-radius\": \"var(--radius-field)\", \"border-bottom-right-radius\": \"var(--radius-field)\" }, \".rounded-l-field\": { \"border-top-left-radius\": \"var(--radius-field)\", \"border-bottom-left-radius\": \"var(--radius-field)\" }, \".rounded-r-field\": { \"border-top-right-radius\": \"var(--radius-field)\", \"border-bottom-right-radius\": \"var(--radius-field)\" }, \".rounded-tl-field\": { \"border-top-left-radius\": \"var(--radius-field)\" }, \".rounded-tr-field\": { \"border-top-right-radius\": \"var(--radius-field)\" }, \".rounded-br-field\": { \"border-bottom-right-radius\": \"var(--radius-field)\" }, \".rounded-bl-field\": { \"border-bottom-left-radius\": \"var(--radius-field)\" }, \".rounded-t-selector\": { \"border-top-left-radius\": \"var(--radius-selector)\", \"border-top-right-radius\": \"var(--radius-selector)\" }, \".rounded-b-selector\": { \"border-bottom-left-radius\": \"var(--radius-selector)\", \"border-bottom-right-radius\": \"var(--radius-selector)\" }, \".rounded-l-selector\": { \"border-top-left-radius\": \"var(--radius-selector)\", \"border-bottom-left-radius\": \"var(--radius-selector)\" }, \".rounded-r-selector\": { \"border-top-right-radius\": \"var(--radius-selector)\", \"border-bottom-right-radius\": \"var(--radius-selector)\" }, \".rounded-tl-selector\": { \"border-top-left-radius\": \"var(--radius-selector)\" }, \".rounded-tr-selector\": { \"border-top-right-radius\": \"var(--radius-selector)\" }, \".rounded-br-selector\": { \"border-bottom-right-radius\": \"var(--radius-selector)\" }, \".rounded-bl-selector\": { \"border-bottom-left-radius\": \"var(--radius-selector)\" } };\n\n// packages/daisyui/utilities/radius/index.js\nvar radius_default = ({ addUtilities, prefix = \"\" }) => {\n  const prefixedradius = addPrefix(object_default65, prefix);\n  addUtilities({ ...prefixedradius });\n};\n\n// packages/daisyui/imports.js\nvar base = { rootscrolllock: rootscrolllock_default, rootcolor: rootcolor_default, scrollbar: scrollbar_default, properties: properties_default, rootscrollgutter: rootscrollgutter_default, svg: svg_default };\nvar components = { drawer: drawer_default, link: link_default, stat: stat_default, carousel: carousel_default, divider: divider_default, mask: mask_default, fieldset: fieldset_default, dropdown: dropdown_default, card: card_default, steps: steps_default, alert: alert_default, kbd: kbd_default, select: select_default, progress: progress_default, fileinput: fileinput_default, modal: modal_default, footer: footer_default, table: table_default, avatar: avatar_default, input: input_default, checkbox: checkbox_default, badge: badge_default, status: status_default, diff: diff_default, hero: hero_default, toggle: toggle_default, stack: stack_default, navbar: navbar_default, label: label_default, menu: menu_default, toast: toast_default, button: button_default, list: list_default, mockup: mockup_default, calendar: calendar_default, indicator: indicator_default, rating: rating_default, tab: tab_default, filter: filter_default, chat: chat_default, radialprogress: radialprogress_default, countdown: countdown_default, tooltip: tooltip_default, timeline: timeline_default, textarea: textarea_default, range: range_default, dock: dock_default, breadcrumbs: breadcrumbs_default, radio: radio_default, skeleton: skeleton_default, loading: loading_default, validator: validator_default, collapse: collapse_default, swap: swap_default };\nvar utilities = { typography: typography_default, glass: glass_default, join: join_default, radius: radius_default };\n\n// packages/daisyui/index.js\nvar version = \"5.0.35\";\nvar daisyui_default = plugin.withOptions((options) => {\n  return ({ addBase, addComponents, addUtilities }) => {\n    const {\n      include,\n      exclude,\n      prefix = \"\"\n    } = pluginOptionsHandler(options, addBase, object_default, version);\n    const shouldIncludeItem = (name) => {\n      if (include && exclude) {\n        return include.includes(name) && !exclude.includes(name);\n      }\n      if (include) {\n        return include.includes(name);\n      }\n      if (exclude) {\n        return !exclude.includes(name);\n      }\n      return true;\n    };\n    Object.entries(base).forEach(([name, item]) => {\n      if (!shouldIncludeItem(name))\n        return;\n      item({ addBase, prefix });\n    });\n    Object.entries(components).forEach(([name, item]) => {\n      if (!shouldIncludeItem(name))\n        return;\n      item({ addComponents, prefix });\n    });\n    Object.entries(utilities).forEach(([name, item]) => {\n      if (!shouldIncludeItem(name))\n        return;\n      item({ addUtilities, prefix });\n    });\n  };\n}, () => ({\n  theme: {\n    extend: variables_default\n  }\n}));\n\n\n/*\n\n  MIT License\n\n  Copyright (c) 2020 Pouya Saadeghi – https://daisyui.com\n\n  Permission is hereby granted, free of charge, to any person obtaining a copy\n  of this software and associated documentation files (the \"Software\"), to deal\n  in the Software without restriction, including without limitation the rights\n  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n  copies of the Software, and to permit persons to whom the Software is\n  furnished to do so, subject to the following conditions:\n\n  The above copyright notice and this permission notice shall be included in all\n  copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n  SOFTWARE.\n\n*/\n"
  },
  {
    "path": "installer/templates/phx_assets/heroicons.js.eex",
    "content": "const plugin = require(\"tailwindcss/plugin\")\nconst fs = require(\"fs\")\nconst path = require(\"path\")\n\nmodule.exports = plugin(function({matchComponents, theme}) {\n  let iconsDir = path.join(__dirname, \"..<%= if @in_umbrella, do: \"/../..\" %>/../deps/heroicons/optimized\")\n  let values = {}\n  let icons = [\n    [\"\", \"/24/outline\"],\n    [\"-solid\", \"/24/solid\"],\n    [\"-mini\", \"/20/solid\"],\n    [\"-micro\", \"/16/solid\"]\n  ]\n  icons.forEach(([suffix, dir]) => {\n    fs.readdirSync(path.join(iconsDir, dir)).forEach(file => {\n      let name = path.basename(file, \".svg\") + suffix\n      values[name] = {name, fullPath: path.join(iconsDir, dir, file)}\n    })\n  })\n  matchComponents({\n    \"hero\": ({name, fullPath}) => {\n      let content = fs.readFileSync(fullPath).toString().replace(/\\r?\\n|\\r/g, \"\")\n      content = encodeURIComponent(content)\n      let size = theme(\"spacing.6\")\n      if (name.endsWith(\"-mini\")) {\n        size = theme(\"spacing.5\")\n      } else if (name.endsWith(\"-micro\")) {\n        size = theme(\"spacing.4\")\n      }\n      return {\n        [`--hero-${name}`]: `url('data:image/svg+xml;utf8,${content}')`,\n        \"-webkit-mask\": `var(--hero-${name})`,\n        \"mask\": `var(--hero-${name})`,\n        \"mask-repeat\": \"no-repeat\",\n        \"background-color\": \"currentColor\",\n        \"vertical-align\": \"middle\",\n        \"display\": \"inline-block\",\n        \"width\": size,\n        \"height\": size\n      }\n    }\n  }, {values})\n})\n"
  },
  {
    "path": "installer/templates/phx_assets/logo.svg.eex",
    "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 71 48\" fill=\"currentColor\" aria-hidden=\"true\">\n  <path\n    d=\"m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.077.057c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728a13 13 0 0 0 1.182 1.106c1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zl-.006.006-.036-.004.021.018.012.053Za.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zl-.008.01.005.026.024.014Z\"\n    fill=\"#FD4F00\"\n  />\n</svg>\n"
  },
  {
    "path": "installer/templates/phx_assets/topbar.js.eex",
    "content": "/**\n * @license MIT\n * topbar 3.0.0\n * http://buunguyen.github.io/topbar\n * Copyright (c) 2024 Buu Nguyen\n */\n(function (window, document) {\n  \"use strict\";\n\n  var canvas,\n    currentProgress,\n    showing,\n    progressTimerId = null,\n    fadeTimerId = null,\n    delayTimerId = null,\n    addEvent = function (elem, type, handler) {\n      if (elem.addEventListener) elem.addEventListener(type, handler, false);\n      else if (elem.attachEvent) elem.attachEvent(\"on\" + type, handler);\n      else elem[\"on\" + type] = handler;\n    },\n    options = {\n      autoRun: true,\n      barThickness: 3,\n      barColors: {\n        0: \"rgba(26,  188, 156, .9)\",\n        \".25\": \"rgba(52,  152, 219, .9)\",\n        \".50\": \"rgba(241, 196, 15,  .9)\",\n        \".75\": \"rgba(230, 126, 34,  .9)\",\n        \"1.0\": \"rgba(211, 84,  0,   .9)\",\n      },\n      shadowBlur: 10,\n      shadowColor: \"rgba(0,   0,   0,   .6)\",\n      className: null,\n    },\n    repaint = function () {\n      canvas.width = window.innerWidth;\n      canvas.height = options.barThickness * 5; // need space for shadow\n\n      var ctx = canvas.getContext(\"2d\");\n      ctx.shadowBlur = options.shadowBlur;\n      ctx.shadowColor = options.shadowColor;\n\n      var lineGradient = ctx.createLinearGradient(0, 0, canvas.width, 0);\n      for (var stop in options.barColors)\n        lineGradient.addColorStop(stop, options.barColors[stop]);\n      ctx.lineWidth = options.barThickness;\n      ctx.beginPath();\n      ctx.moveTo(0, options.barThickness / 2);\n      ctx.lineTo(\n        Math.ceil(currentProgress * canvas.width),\n        options.barThickness / 2\n      );\n      ctx.strokeStyle = lineGradient;\n      ctx.stroke();\n    },\n    createCanvas = function () {\n      canvas = document.createElement(\"canvas\");\n      var style = canvas.style;\n      style.position = \"fixed\";\n      style.top = style.left = style.right = style.margin = style.padding = 0;\n      style.zIndex = 100001;\n      style.display = \"none\";\n      if (options.className) canvas.classList.add(options.className);\n      addEvent(window, \"resize\", repaint);\n    },\n    topbar = {\n      config: function (opts) {\n        for (var key in opts)\n          if (options.hasOwnProperty(key)) options[key] = opts[key];\n      },\n      show: function (delay) {\n        if (showing) return;\n        if (delay) {\n          if (delayTimerId) return;\n          delayTimerId = setTimeout(() => topbar.show(), delay);\n        } else {\n          showing = true;\n          if (fadeTimerId !== null) window.cancelAnimationFrame(fadeTimerId);\n          if (!canvas) createCanvas();\n          if (!canvas.parentElement) document.body.appendChild(canvas);\n          canvas.style.opacity = 1;\n          canvas.style.display = \"block\";\n          topbar.progress(0);\n          if (options.autoRun) {\n            (function loop() {\n              progressTimerId = window.requestAnimationFrame(loop);\n              topbar.progress(\n                \"+\" + 0.05 * Math.pow(1 - Math.sqrt(currentProgress), 2)\n              );\n            })();\n          }\n        }\n      },\n      progress: function (to) {\n        if (typeof to === \"undefined\") return currentProgress;\n        if (typeof to === \"string\") {\n          to =\n            (to.indexOf(\"+\") >= 0 || to.indexOf(\"-\") >= 0\n              ? currentProgress\n              : 0) + parseFloat(to);\n        }\n        currentProgress = to > 1 ? 1 : to;\n        repaint();\n        return currentProgress;\n      },\n      hide: function () {\n        clearTimeout(delayTimerId);\n        delayTimerId = null;\n        if (!showing) return;\n        showing = false;\n        if (progressTimerId != null) {\n          window.cancelAnimationFrame(progressTimerId);\n          progressTimerId = null;\n        }\n        (function loop() {\n          if (topbar.progress(\"+.1\") >= 1) {\n            canvas.style.opacity -= 0.05;\n            if (canvas.style.opacity <= 0.05) {\n              canvas.style.display = \"none\";\n              fadeTimerId = null;\n              return;\n            }\n          }\n          fadeTimerId = window.requestAnimationFrame(loop);\n        })();\n      },\n    };\n\n  if (typeof module === \"object\" && typeof module.exports === \"object\") {\n    module.exports = topbar;\n  } else if (typeof define === \"function\" && define.amd) {\n    define(function () {\n      return topbar;\n    });\n  } else {\n    this.topbar = topbar;\n  }\n}.call(this, window, document));\n"
  },
  {
    "path": "installer/templates/phx_assets/tsconfig.json.eex",
    "content": "// This file is needed on most editors to enable the intelligent autocompletion\n// of LiveView's JavaScript API methods. You can safely delete it if you don't need it.\n//\n// Note: This file assumes a basic esbuild setup without node_modules.\n// We include a generic paths alias to deps to mimic how esbuild resolves\n// the Phoenix and LiveView JavaScript assets.\n// If you have a package.json in your project, you should remove the\n// paths configuration and instead add the phoenix dependencies to the\n// dependencies section of your package.json:\n//\n// {\n//   ...\n//   \"dependencies\": {\n//     ...,\n//     \"phoenix\": \"../deps/phoenix\",\n//     \"phoenix_html\": \"../deps/phoenix_html\",\n//     \"phoenix_live_view\": \"../deps/phoenix_live_view\"\n//   }\n// }\n//\n// Feel free to adjust this configuration however you need.\n{\n  \"compilerOptions\": {\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"*\": [\"../deps/*\"]\n    },\n    \"allowJs\": true,\n    \"noEmit\": true\n  },\n  \"include\": [\"js/**/*\"]\n}\n"
  },
  {
    "path": "installer/templates/phx_ecto/data_case.ex.eex",
    "content": "defmodule <%= @app_module %>.DataCase do\n  @moduledoc \"\"\"\n  This module defines the setup for tests requiring\n  access to the application's data layer.\n\n  You may define functions here to be used as helpers in\n  your tests.\n\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use <%= @app_module %>.DataCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      alias <%= @app_module %>.Repo\n\n      import Ecto\n      import Ecto.Changeset\n      import Ecto.Query\n      import <%= @app_module %>.DataCase\n    end\n  end\n\n  setup tags do\n    <%= @app_module %>.DataCase.setup_sandbox(tags)\n    :ok\n  end\n\n  @doc \"\"\"\n  Sets up the sandbox based on the test tags.\n  \"\"\"\n  def setup_sandbox(tags) do\n<%= @adapter_config[:test_setup] %>\n  end\n\n  @doc \"\"\"\n  A helper that transforms changeset errors into a map of messages.\n\n      assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n      assert \"password is too short\" in errors_on(changeset).password\n      assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n  \"\"\"\n  def errors_on(changeset) do\n    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n      Regex.replace(~r\"%{(\\w+)}\", message, fn _, key ->\n        opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_ecto/formatter.exs.eex",
    "content": "[\n  import_deps: [:ecto_sql],\n  inputs: [\"*.exs\"]\n]\n"
  },
  {
    "path": "installer/templates/phx_ecto/repo.ex.eex",
    "content": "defmodule <%= @app_module %>.Repo do\n  use Ecto.Repo,\n    otp_app: :<%= @app_name %>,\n    adapter: <%= inspect @adapter_module %>\nend\n"
  },
  {
    "path": "installer/templates/phx_ecto/seeds.exs.eex",
    "content": "# Script for populating the database. You can run it as:\n#\n#     mix run priv/repo/seeds.exs\n#\n# Inside the script, you can read and write to any of your\n# repositories directly:\n#\n#     <%= @app_module %>.Repo.insert!(%<%= @app_module %>.SomeSchema{})\n#\n# We recommend using the bang functions (`insert!`, `update!`\n# and so on) as they will fail if something goes wrong.\n"
  },
  {
    "path": "installer/templates/phx_gettext/en/LC_MESSAGES/errors.po.eex",
    "content": "## `msgid`s in this file come from POT (.pot) files.\n##\n## Do not add, change, or remove `msgid`s manually here as\n## they're tied to the ones in the corresponding POT file\n## (with the same domain).\n##\n## Use `mix gettext.extract --merge` or `mix gettext.merge`\n## to merge POT files into PO files.\nmsgid \"\"\nmsgstr \"\"\n\"Language: en\\n\"<%= if @ecto do %>\n\n## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be %{count} byte(s)\"\nmsgid_plural \"should be %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} byte(s)\"\nmsgid_plural \"should be at least %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} byte(s)\"\nmsgid_plural \"should be at most %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"<% end %>\n"
  },
  {
    "path": "installer/templates/phx_gettext/errors.pot.eex",
    "content": "## This is a PO Template file.\n##\n## `msgid`s here are often extracted from source code.\n## Add new translations manually only if they're dynamic\n## translations that can't be statically extracted.\n##\n## Run `mix gettext.extract` to bring this file up to\n## date. Leave `msgstr`s empty as changing them here has no\n## effect: edit them in PO (`.po`) files instead.\n<%= if @ecto do %>## From Ecto.Changeset.cast/4\nmsgid \"can't be blank\"\nmsgstr \"\"\n\n## From Ecto.Changeset.unique_constraint/3\nmsgid \"has already been taken\"\nmsgstr \"\"\n\n## From Ecto.Changeset.put_change/3\nmsgid \"is invalid\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_acceptance/3\nmsgid \"must be accepted\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_format/3\nmsgid \"has invalid format\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_subset/3\nmsgid \"has an invalid entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_exclusion/3\nmsgid \"is reserved\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_confirmation/3\nmsgid \"does not match confirmation\"\nmsgstr \"\"\n\n## From Ecto.Changeset.no_assoc_constraint/3\nmsgid \"is still associated with this entry\"\nmsgstr \"\"\n\nmsgid \"are still associated with this entry\"\nmsgstr \"\"\n\n## From Ecto.Changeset.validate_length/3\nmsgid \"should have %{count} item(s)\"\nmsgid_plural \"should have %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be %{count} character(s)\"\nmsgid_plural \"should be %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be %{count} byte(s)\"\nmsgid_plural \"should be %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at least %{count} item(s)\"\nmsgid_plural \"should have at least %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} character(s)\"\nmsgid_plural \"should be at least %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at least %{count} byte(s)\"\nmsgid_plural \"should be at least %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should have at most %{count} item(s)\"\nmsgid_plural \"should have at most %{count} item(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} character(s)\"\nmsgid_plural \"should be at most %{count} character(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\nmsgid \"should be at most %{count} byte(s)\"\nmsgid_plural \"should be at most %{count} byte(s)\"\nmsgstr[0] \"\"\nmsgstr[1] \"\"\n\n## From Ecto.Changeset.validate_number/3\nmsgid \"must be less than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than %{number}\"\nmsgstr \"\"\n\nmsgid \"must be less than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be greater than or equal to %{number}\"\nmsgstr \"\"\n\nmsgid \"must be equal to %{number}\"\nmsgstr \"\"<% end %>\n"
  },
  {
    "path": "installer/templates/phx_gettext/gettext.ex.eex",
    "content": "defmodule <%= @web_namespace %>.Gettext do\n  @moduledoc \"\"\"\n  A module providing Internationalization with a gettext-based API.\n\n  By using [Gettext](https://hexdocs.pm/gettext), your module compiles translations\n  that you can use in your application. To use this Gettext backend module,\n  call `use Gettext` and pass it as an option:\n\n      use Gettext, backend: <%= @web_namespace %>.Gettext\n\n      # Simple translation\n      gettext(\"Here is the string to translate\")\n\n      # Plural translation\n      ngettext(\"Here is the string to translate\",\n               \"Here are the strings to translate\",\n               3)\n\n      # Domain-based translation\n      dgettext(\"errors\", \"Here is the error message to translate\")\n\n  See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n  \"\"\"\n  use Gettext.Backend, otp_app: :<%= @web_app_name %>\nend\n"
  },
  {
    "path": "installer/templates/phx_mailer/lib/app_name/mailer.ex.eex",
    "content": "defmodule <%= @app_module %>.Mailer do\n  use Swoosh.Mailer, otp_app: :<%= @app_name %>\nend\n"
  },
  {
    "path": "installer/templates/phx_single/README.md.eex",
    "content": "# <%= @app_module %>\n\nTo start your Phoenix server:\n\n* Run `mix setup` to install and setup dependencies\n* Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`\n\nNow you can visit [`localhost:4000`](http://localhost:4000) from your browser.\n\nReady to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).\n\n## Learn more\n\n* Official website: https://www.phoenixframework.org/\n* Guides: https://hexdocs.pm/phoenix/overview.html\n* Docs: https://hexdocs.pm/phoenix\n* Forum: https://elixirforum.com/c/phoenix-forum\n* Source: https://github.com/phoenixframework/phoenix\n"
  },
  {
    "path": "installer/templates/phx_single/config/config.exs.eex",
    "content": "# This file is responsible for configuring your application\n# and its dependencies with the aid of the Config module.\n#\n# This configuration file is loaded before any dependency and\n# is restricted to this project.\n\n# General application configuration\nimport Config\n\n<%= if @namespaced? || @ecto || @generators do %>\nconfig :<%= @app_name %><%= if @namespaced? do %>,\n  namespace: <%= @app_module %><% end %><%= if @ecto do %>,\n  ecto_repos: [<%= @app_module %>.Repo]<% end %><%= if @generators do %>,\n  generators: <%= inspect @generators %><% end %><% end %>\n\n# Configure the endpoint\nconfig :<%= @app_name %>, <%= @endpoint_module %>,\n  url: [host: \"localhost\"],\n  adapter: <%= inspect @web_adapter_module %>,\n  render_errors: [\n    formats: [<%= if @html do%>html: <%= @web_namespace %>.ErrorHTML, <% end %>json: <%= @web_namespace %>.ErrorJSON],\n    layout: false\n  ],\n  pubsub_server: <%= @app_module %>.PubSub,\n  live_view: [signing_salt: \"<%= @lv_signing_salt %>\"]<%= if @mailer do %>\n\n# Configure the mailer\n#\n# By default it uses the \"Local\" adapter which stores the emails\n# locally. You can see the emails in your browser, at \"/dev/mailbox\".\n#\n# For production it's recommended to configure a different adapter\n# at the `config/runtime.exs`.\nconfig :<%= @app_name %>, <%= @app_module %>.Mailer, adapter: Swoosh.Adapters.Local<% end %><%= if @javascript do %>\n\n# Configure esbuild (the version is required)\nconfig :esbuild,\n  version: \"0.25.4\",\n  <%= @app_name %>: [\n    args:\n      ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),\n    cd: Path.expand(\"..<%= if @in_umbrella, do: \"/apps/#{@app_name}\" %>/assets\", __DIR__),\n    env: %{\"NODE_PATH\" => [Path.expand(\"../deps\", __DIR__), Mix.Project.build_path()]}\n  ]<% end %><%= if @css do %>\n\n# Configure tailwind (the version is required)\nconfig :tailwind,\n  version: \"4.1.12\",\n  <%= @app_name %>: [\n    args: ~w(\n      --input=assets/css/app.css\n      --output=priv/static/assets/css/app.css\n    ),\n    cd: Path.expand(\"..<%= if @in_umbrella, do: \"/apps/#{@app_name}\" %>\", __DIR__),\n  ]<% end %>\n\n# Configure Elixir's Logger\nconfig :logger, :default_formatter,\n  format: \"$time $metadata[$level] $message\\n\",\n  metadata: [:request_id]\n\n# Use Jason for JSON parsing in Phoenix\nconfig :phoenix, :json_library, Jason\n\n# Import environment specific config. This must remain at the bottom\n# of this file so it overrides the configuration defined above.\nimport_config \"#{config_env()}.exs\"\n"
  },
  {
    "path": "installer/templates/phx_single/config/dev.exs.eex",
    "content": "import Config\n\n# For development, we disable any cache and enable\n# debugging and code reloading.\n#\n# The watchers configuration can be used to run external\n# watchers to your application. For example, we can use it\n# to bundle .js and .css sources.\nconfig :<%= @app_name %>, <%= @endpoint_module %>,<%= if @inside_docker_env? do %>\n  # Bind to 0.0.0.0 to expose the server to the docker host machine.\n  # This makes make the service accessible from any network interface.\n  # Change to `ip: {127, 0, 0, 1}` to allow access only from the server machine.\n  http: [ip: {0, 0, 0, 0}],<% else %>\n  # Binding to loopback ipv4 address prevents access from other machines.\n  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.\n  http: [ip: {127, 0, 0, 1}],<% end %>\n  check_origin: false,\n  code_reloader: true,\n  debug_errors: true,\n  secret_key_base: \"<%= @secret_key_base_dev %>\",\n  watchers: <%= if @javascript or @css do %>[<%= if @javascript do %>\n    esbuild: {Esbuild, :install_and_run, [:<%= @app_name %>, ~w(--sourcemap=inline --watch)]}<%= if @css, do: \",\" %><% end %><%= if @css do %>\n    tailwind: {Tailwind, :install_and_run, [:<%= @app_name %>, ~w(--watch)]}<% end %>\n  ]<% else %>[]<% end %>\n\n# ## SSL Support\n#\n# In order to use HTTPS in development, a self-signed\n# certificate can be generated by running the following\n# Mix task:\n#\n#     mix phx.gen.cert\n#\n# Run `mix help phx.gen.cert` for more information.\n#\n# The `http:` config above can be replaced with:\n#\n#     https: [\n#       port: 4001,\n#       cipher_suite: :strong,\n#       keyfile: \"priv/cert/selfsigned_key.pem\",\n#       certfile: \"priv/cert/selfsigned.pem\"\n#     ],\n#\n# If desired, both `http:` and `https:` keys can be\n# configured to run both http and https servers on\n# different ports.<%= if @html do %>\n\n# Reload browser tabs when matching files change.\nconfig :<%= @app_name %>, <%= @endpoint_module %>,\n  live_reload: [\n    web_console_logger: true,\n    patterns: [\n      # Static assets, except user uploads\n      ~r\"priv/static/(?!uploads/).*\\.(js|css|png|jpeg|jpg|gif|svg)$\"<%= @config_regex_E %>,<%= if @gettext do %>\n      # Gettext translations\n      ~r\"priv/gettext/.*\\.po$\"<%= @config_regex_E %>,<% end %>\n      # Router, Controllers, LiveViews and LiveComponents\n      ~r\"lib/<%= @lib_web_name %>/router\\.ex$\"<%= @config_regex_E %>,\n      ~r\"lib/<%= @lib_web_name %>/(controllers|live|components)/.*\\.(ex|heex)$\"<%= @config_regex_E %>\n    ]\n  ]<% end %>\n\n# Enable dev routes for dashboard and mailbox\nconfig :<%= @app_name %>, dev_routes: true\n\n# Do not include metadata nor timestamps in development logs\nconfig :logger, :default_formatter, format: \"[$level] $message\\n\"\n\n# Set a higher stacktrace during development. Avoid configuring such\n# in production as building large stacktraces may be expensive.\nconfig :phoenix, :stacktrace_depth, 20\n\n# Initialize plugs at runtime for faster development compilation\nconfig :phoenix, :plug_init_mode, :runtime<%= if @html do %>\n\nconfig :phoenix_live_view,\n  # Include debug annotations and locations in rendered markup.\n  # Changing this configuration will require mix clean and a full recompile.\n  debug_heex_annotations: true,\n  debug_attributes: true,\n  # Enable helpful, but potentially expensive runtime checks\n  enable_expensive_runtime_checks: true<% end %><%= if @mailer do %>\n\n# Disable swoosh api client as it is only required for production adapters.\nconfig :swoosh, :api_client, false<% end %>\n"
  },
  {
    "path": "installer/templates/phx_single/config/prod.exs.eex",
    "content": "import Config\n\n<%= if @javascript or @css do %>\n# Note we also include the path to a cache manifest\n# containing the digested version of static files. This\n# manifest is generated by the `mix assets.deploy` task,\n# which you should run after static files are built and\n# before starting your production server.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>, cache_static_manifest: \"priv/static/cache_manifest.json\"\n\n<% end %># Force using SSL in production. This also sets the \"strict-security-transport\" header,\n# known as HSTS. If you have a health check endpoint, you may want to exclude it below.\n# Note `:force_ssl` is required to be set at compile-time.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  force_ssl: [\n    rewrite_on: [:x_forwarded_proto],\n    exclude: [\n      # paths: [\"/health\"],\n      hosts: [\"localhost\", \"127.0.0.1\"]\n    ]\n  ]<%= if @mailer do %>\n\n# Configure Swoosh API Client\nconfig :swoosh, api_client: Swoosh.ApiClient.Req\n\n# Disable Swoosh Local Memory Storage\nconfig :swoosh, local: false<% end %>\n\n# Do not print debug messages in production\nconfig :logger, level: :info\n\n# Runtime production configuration, including reading\n# of environment variables, is done on config/runtime.exs.\n"
  },
  {
    "path": "installer/templates/phx_single/config/runtime.exs.eex",
    "content": "import Config\n\n# config/runtime.exs is executed for all environments, including\n# during releases. It is executed after compilation and before the\n# system starts, so it is typically used to load production configuration\n# and secrets from environment variables or elsewhere. Do not define\n# any compile-time configuration in here, as it won't be applied.\n# The block below contains prod specific runtime configuration.\n\n# ## Using releases\n#\n# If you use `mix release`, you need to explicitly enable the server\n# by passing the PHX_SERVER=true when you start it:\n#\n#     PHX_SERVER=true bin/<%= @app_name %> start\n#\n# Alternatively, you can use `mix phx.gen.release` to generate a `bin/server`\n# script that automatically sets the env var above.\nif System.get_env(\"PHX_SERVER\") do\n  config :<%= @app_name %>, <%= @endpoint_module %>, server: true\nend\n\nconfig :<%= @app_name %>, <%= @endpoint_module %>,\n  http: [port: String.to_integer(System.get_env(\"PORT\", \"4000\"))]\n\nif config_env() == :prod do\n  # The secret key base is used to sign/encrypt cookies and other secrets.\n  # A default value is used in config/dev.exs and config/test.exs but you\n  # want to use a different value for prod and you most likely don't want\n  # to check this value into version control, so we use an environment\n  # variable instead.\n  secret_key_base =\n    System.get_env(\"SECRET_KEY_BASE\") ||\n      raise \"\"\"\n      environment variable SECRET_KEY_BASE is missing.\n      You can generate one by calling: mix phx.gen.secret\n      \"\"\"\n\n  host = System.get_env(\"PHX_HOST\") || \"example.com\"\n\n  config :<%= @app_name %>, :dns_cluster_query, System.get_env(\"DNS_CLUSTER_QUERY\")\n\n  config :<%= @app_name %>, <%= @endpoint_module %>,\n    url: [host: host, port: 443, scheme: \"https\"],\n    http: [\n      # Enable IPv6 and bind on all interfaces.\n      # Set it to  {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.\n      # See the documentation on <%= @web_adapter_docs %>\n      # for details about using IPv6 vs IPv4 and loopback vs public addresses.\n      ip: {0, 0, 0, 0, 0, 0, 0, 0}\n    ],\n    secret_key_base: secret_key_base\n\n  # ## SSL Support\n  #\n  # To get SSL working, you will need to add the `https` key\n  # to your endpoint configuration:\n  #\n  #     config :<%= @web_app_name %>, <%= @endpoint_module %>,\n  #       https: [\n  #         ...,\n  #         port: 443,\n  #         cipher_suite: :strong,\n  #         keyfile: System.get_env(\"SOME_APP_SSL_KEY_PATH\"),\n  #         certfile: System.get_env(\"SOME_APP_SSL_CERT_PATH\")\n  #       ]\n  #\n  # The `cipher_suite` is set to `:strong` to support only the\n  # latest and more secure SSL ciphers. This means old browsers\n  # and clients may not be supported. You can set it to\n  # `:compatible` for wider support.\n  #\n  # `:keyfile` and `:certfile` expect an absolute path to the key\n  # and cert in disk or a relative path inside priv, for example\n  # \"priv/ssl/server.key\". For all supported SSL configuration\n  # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1\n  #\n  # We also recommend setting `force_ssl` in your config/prod.exs,\n  # ensuring no data is ever sent via http, always redirecting to https:\n  #\n  #     config :<%= @web_app_name %>, <%= @endpoint_module %>,\n  #       force_ssl: [hsts: true]\n  #\n  # Check `Plug.SSL` for all available options in `force_ssl`.<%= if @mailer do %>\n\n  # ## Configuring the mailer\n  #\n  # In production you need to configure the mailer to use a different adapter.\n  # Here is an example configuration for Mailgun:\n  #\n  #     config :<%= @app_name %>, <%= @app_module %>.Mailer,\n  #       adapter: Swoosh.Adapters.Mailgun,\n  #       api_key: System.get_env(\"MAILGUN_API_KEY\"),\n  #       domain: System.get_env(\"MAILGUN_DOMAIN\")\n  #\n  # Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney,\n  # and Finch out-of-the-box. This configuration is typically done at\n  # compile-time in your config/prod.exs:\n  #\n  #     config :swoosh, :api_client, Swoosh.ApiClient.Req\n  #\n  # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_single/config/test.exs.eex",
    "content": "import Config\n\n# We don't run a server during test. If one is required,\n# you can enable the server option below.\nconfig :<%= @app_name %>, <%= @endpoint_module %>,\n  http: [ip: {127, 0, 0, 1}, port: 4002],\n  secret_key_base: \"<%= @secret_key_base_test %>\",\n  server: false<%= if @mailer do %>\n\n# In test we don't send emails\nconfig :<%= @app_name %>, <%= @app_module %>.Mailer,\n  adapter: Swoosh.Adapters.Test\n\n# Disable swoosh api client as it is only required for production adapters\nconfig :swoosh, :api_client, false<% end %>\n\n# Print only warnings and errors during test\nconfig :logger, level: :warning\n\n# Initialize plugs at runtime for faster test compilation\nconfig :phoenix, :plug_init_mode, :runtime<%= if @html do %>\n\n# Enable helpful, but potentially expensive runtime checks\nconfig :phoenix_live_view,\n  enable_expensive_runtime_checks: true<% end %>\n\n# Sort query params output of verified routes for robust url comparisons\nconfig :phoenix,\n  sort_verified_routes_query_params: true\n"
  },
  {
    "path": "installer/templates/phx_single/formatter.exs.eex",
    "content": "[\n  import_deps: [<%= if @ecto do %>:ecto, :ecto_sql, <% end %>:phoenix],<%= if @ecto do %>\n  subdirectories: [\"priv/*/migrations\"],<% end %><%= if @html do %>\n  plugins: [Phoenix.LiveView.HTMLFormatter],<% end %>\n  inputs: [<%= if @html do %>\"*.{heex,ex,exs}\", \"{config,lib,test}/**/*.{heex,ex,exs}\"<% else %>\"*.{ex,exs}\", \"{config,lib,test}/**/*.{ex,exs}\"<% end %><%= if @ecto do %>, \"priv/*/seeds.exs\"<% end %>]\n]\n"
  },
  {
    "path": "installer/templates/phx_single/gitignore.eex",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Temporary files, for example, from tests.\n/tmp/\n\n# Ignore package tarball (built via \"mix hex.build\").\n<%= @app_name %>-*.tar\n<%= if @javascript or @css do %>\n# Ignore assets that are produced by build tools.\n/priv/static/assets/\n\n# Ignore digested assets cache.\n/priv/static/cache_manifest.json\n\n# In case you use Node.js/npm, you want to ignore these.\nnpm-debug.log\n/assets/node_modules/\n<% end %><%= if @adapter_app == :ecto_sqlite3 do %>\n# Database files\n*.db\n*.db-*\n<% end %>\n"
  },
  {
    "path": "installer/templates/phx_single/lib/app_name/application.ex.eex",
    "content": "defmodule <%= @app_module %>.Application do\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  @moduledoc false\n\n  use Application\n\n  @impl true\n  def start(_type, _args) do\n    children = [\n      <%= @web_namespace %>.Telemetry,<%= if @ecto do %>\n      <%= @app_module %>.Repo,<% end %><%= if @adapter_app == :ecto_sqlite3 do %>\n      {Ecto.Migrator,\n       repos: Application.fetch_env!(<%= inspect(String.to_atom(@app_name)) %>, :ecto_repos), skip: skip_migrations?()},<% end %>\n      {DNSCluster, query: Application.get_env(<%= inspect(String.to_atom(@app_name)) %>, :dns_cluster_query) || :ignore},\n      {Phoenix.PubSub, name: <%= @app_module %>.PubSub},\n      # Start a worker by calling: <%= @app_module %>.Worker.start_link(arg)\n      # {<%= @app_module %>.Worker, arg},\n      # Start to serve requests, typically the last entry\n      <%= @endpoint_module %>\n    ]\n\n    # See https://hexdocs.pm/elixir/Supervisor.html\n    # for other strategies and supported options\n    opts = [strategy: :one_for_one, name: <%= @app_module %>.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\n\n  # Tell Phoenix to update the endpoint configuration\n  # whenever the application is updated.\n  @impl true\n  def config_change(changed, _new, removed) do\n    <%= @endpoint_module %>.config_change(changed, removed)\n    :ok\n  end<%= if @adapter_app == :ecto_sqlite3 do %>\n\n  defp skip_migrations?() do\n    # By default, sqlite migrations are run when using a release\n    System.get_env(\"RELEASE_NAME\") == nil\n  end<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_single/lib/app_name.ex.eex",
    "content": "defmodule <%= @app_module %> do\n  @moduledoc \"\"\"\n  <%= @app_module %> keeps the contexts that define your domain\n  and business logic.\n\n  Contexts are also responsible for managing your data, regardless\n  if it comes from the database, an external API or others.\n  \"\"\"\nend\n"
  },
  {
    "path": "installer/templates/phx_single/lib/app_name_web.ex.eex",
    "content": "defmodule <%= @web_namespace %> do\n  @moduledoc \"\"\"\n  The entrypoint for defining your web interface, such\n  as controllers, components, channels, and so on.\n\n  This can be used in your application as:\n\n      use <%= @web_namespace %>, :controller\n      use <%= @web_namespace %>, :html\n\n  The definitions below will be executed for every controller,\n  component, etc, so keep them short and clean, focused\n  on imports, uses and aliases.\n\n  Do NOT define functions inside the quoted expressions\n  below. Instead, define additional modules and import\n  those modules here.\n  \"\"\"\n\n  def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)\n\n  def router do\n    quote do\n      use Phoenix.Router, helpers: false\n\n      # Import common connection and controller functions to use in pipelines\n      import Plug.Conn\n      import Phoenix.Controller<%= if @html do %>\n      import Phoenix.LiveView.Router<% end %>\n    end\n  end\n\n  def channel do\n    quote do\n      use Phoenix.Channel\n    end\n  end\n\n  def controller do\n    quote do\n      use Phoenix.Controller, formats: [:html, :json]<%= if @gettext do %>\n\n      use Gettext, backend: <%= @web_namespace %>.Gettext<% end %>\n\n      import Plug.Conn\n\n      unquote(verified_routes())\n    end\n  end<%= if @html do %>\n\n  def live_view do\n    quote do\n      use Phoenix.LiveView\n\n      unquote(html_helpers())\n    end\n  end\n\n  def live_component do\n    quote do\n      use Phoenix.LiveComponent\n\n      unquote(html_helpers())\n    end\n  end\n\n  def html do\n    quote do\n      use Phoenix.Component\n\n      # Import convenience functions from controllers\n      import Phoenix.Controller,\n        only: [get_csrf_token: 0, view_module: 1, view_template: 1]\n\n      # Include general helpers for rendering HTML\n      unquote(html_helpers())\n    end\n  end\n\n  defp html_helpers do\n    quote do<%= if @gettext do %>\n      # Translation\n      use Gettext, backend: <%= @web_namespace %>.Gettext\n<% end %>\n      # HTML escaping functionality\n      import Phoenix.HTML\n      # Core UI components\n      import <%= @web_namespace %>.CoreComponents\n\n      # Common modules used in templates\n      alias Phoenix.LiveView.JS\n      alias <%= @web_namespace %>.Layouts\n\n      # Routes generation with the ~p sigil\n      unquote(verified_routes())\n    end\n  end<% end %>\n\n  def verified_routes do\n    quote do\n      use Phoenix.VerifiedRoutes,\n        endpoint: <%= @endpoint_module %>,\n        router: <%= @web_namespace %>.Router,\n        statics: <%= @web_namespace %>.static_paths()\n    end\n  end\n\n  @doc \"\"\"\n  When used, dispatch to the appropriate controller/live_view/etc.\n  \"\"\"\n  defmacro __using__(which) when is_atom(which) do\n    apply(__MODULE__, which, [])\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_single/mix.exs.eex",
    "content": "defmodule <%= @app_module %>.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :<%= @app_name %>,\n      version: \"0.1.0\",<%= if @in_umbrella do %>\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",<% end %>\n      elixir: \"~> 1.15\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      aliases: aliases(),\n      deps: deps(),<%= if @html do %>\n      compilers: [:phoenix_live_view] ++ Mix.compilers(),<% end %>\n      listeners: [Phoenix.CodeReloader]\n    ]\n  end\n\n  # Configuration for the OTP application.\n  #\n  # Type `mix help compile.app` for more information.\n  def application do\n    [\n      mod: {<%= @app_module %>.Application, []},\n      extra_applications: [:logger, :runtime_tools]\n    ]\n  end\n\n  def cli do\n    [\n      preferred_envs: [precommit: :test]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      <%= @phoenix_dep %>,<%= if @ecto do %>\n      {:phoenix_ecto, \"~> 4.5\"},\n      {:ecto_sql, \"~> 3.13\"},\n      {<%= inspect @adapter_app %>, \">= 0.0.0\"},<% end %><%= if @html do %>\n      {:phoenix_html, \"~> 4.1\"},\n      {:phoenix_live_reload, \"~> 1.2\", only: :dev},\n      {:phoenix_live_view, \"~> 1.1.0\"},\n      {:lazy_html, \">= 0.1.0\", only: :test},<% end %><%= if @dashboard do %>\n      {:phoenix_live_dashboard, \"~> 0.8.3\"},<% end %><%= if @javascript do %>\n      {:esbuild, \"~> 0.10\", runtime: Mix.env() == :dev},<% end %><%= if @css do %>\n      {:tailwind, \"~> 0.3\", runtime: Mix.env() == :dev},\n      {:heroicons,\n       github: \"tailwindlabs/heroicons\",\n       tag: \"v2.2.0\",\n       sparse: \"optimized\",\n       app: false,\n       compile: false,\n       depth: 1},<% end %><%= if @mailer do %>\n      {:swoosh, \"~> 1.16\"},\n      {:req, \"~> 0.5\"},<% end %>\n      {:telemetry_metrics, \"~> 1.0\"},\n      {:telemetry_poller, \"~> 1.0\"},<%= if @gettext do %>\n      {:gettext, \"~> 1.0\"},<% end %>\n      {:jason, \"~> 1.2\"},\n      {:dns_cluster, \"~> 0.2.0\"},\n      {<%= inspect @web_adapter_app %>, \"<%= @web_adapter_vsn %>\"}\n    ]\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  # For example, to install project dependencies and perform other setup tasks, run:\n  #\n  #     $ mix setup\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  defp aliases do\n    [\n      setup: [\"deps.get\"<%= if @ecto do %>, \"ecto.setup\"<% end %><%= if @asset_builders != [] do %>, \"assets.setup\", \"assets.build\"<% end %>]<%= if @ecto do %>,\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/seeds.exs\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\"ecto.create --quiet\", \"ecto.migrate --quiet\", \"test\"]<% end %><%= if @asset_builders != [] do %>,\n      \"assets.setup\": <%= inspect Enum.map(@asset_builders, &\"#{&1}.install --if-missing\") %>,\n      \"assets.build\": <%= inspect [\"compile\" | Enum.map(@asset_builders, &\"#{&1} #{@app_name}\")] %>,\n      \"assets.deploy\": [\n<%= Enum.map(@asset_builders, &\"        \\\"#{&1} #{@app_name} --minify\\\",\\n\") ++ [\"        \\\"phx.digest\\\"\"] %>\n      ]<% end %>,\n      precommit: [\"compile --warnings-as-errors\", \"deps.unlock --unused\", \"format\", \"test\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_single/test/test_helper.exs.eex",
    "content": "ExUnit.start()<%= if @ecto do %>\n<%= @adapter_config[:test_setup_all] %><% end %>\n"
  },
  {
    "path": "installer/templates/phx_static/app.css",
    "content": "/* This file is for your main application CSS */\n"
  },
  {
    "path": "installer/templates/phx_static/app.js",
    "content": "// For Phoenix.HTML support, including form and button helpers\n// copy the following scripts into your javascript bundle:\n// * deps/phoenix_html/priv/static/phoenix_html.js\n\n// For Phoenix.Channels support, copy the following scripts\n// into your javascript bundle:\n// * deps/phoenix/priv/static/phoenix.js\n\n// For Phoenix.LiveView support, copy the following scripts\n// into your javascript bundle:\n// * deps/phoenix_live_view/priv/static/phoenix_live_view.js\n\n// Handle flash close\n// (you can safely remove this if you don't use the default flash component)\ndocument.querySelectorAll(\"[role=alert][data-flash]\").forEach((el) => {\n  el.addEventListener(\"click\", () => {\n    el.setAttribute(\"hidden\", \"\");\n  });\n});\n"
  },
  {
    "path": "installer/templates/phx_static/default.css",
    "content": "/* These are daisyUI styles for styling the default CoreComponents and generator files\n * included to prevent shipping a completely unstyled page, even as you selected --no-tailwind.\n * You can safely remove the whole file and all references to \"default.css\".\n */\n/*! tailwindcss v4.0.9 | MIT License | https://tailwindcss.com */\n@layer theme, base, components, utilities;\n@layer theme {\n  :root, :host {\n    --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n    'Noto Color Emoji';\n    --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',\n    monospace;\n    --color-black: #000;\n    --spacing: 0.25rem;\n    --container-sm: 24rem;\n    --container-xl: 36rem;\n    --container-2xl: 42rem;\n    --text-sm: 0.875rem;\n    --text-sm--line-height: calc(1.25 / 0.875);\n    --text-lg: 1.125rem;\n    --text-lg--line-height: calc(1.75 / 1.125);\n    --font-weight-semibold: 600;\n    --font-weight-bold: 700;\n    --tracking-tighter: -0.05em;\n    --radius-lg: 0.5rem;\n    --ease-in: cubic-bezier(0.4, 0, 1, 1);\n    --ease-out: cubic-bezier(0, 0, 0.2, 1);\n    --animate-spin: spin 1s linear infinite;\n    --default-transition-duration: 150ms;\n    --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n    --default-font-family: var(--font-sans);\n    --default-font-feature-settings: var(--font-sans--font-feature-settings);\n    --default-font-variation-settings: var(--font-sans--font-variation-settings);\n    --default-mono-font-family: var(--font-mono);\n    --default-mono-font-feature-settings: var(--font-mono--font-feature-settings);\n    --default-mono-font-variation-settings: var(--font-mono--font-variation-settings);\n  }\n}\n@layer base {\n  *, ::after, ::before, ::backdrop, ::file-selector-button {\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    border: 0 solid;\n  }\n  html, :host {\n    line-height: 1.5;\n    -webkit-text-size-adjust: 100%;\n    tab-size: 4;\n    font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' );\n    font-feature-settings: var(--default-font-feature-settings, normal);\n    font-variation-settings: var(--default-font-variation-settings, normal);\n    -webkit-tap-highlight-color: transparent;\n  }\n  body {\n    line-height: inherit;\n  }\n  hr {\n    height: 0;\n    color: inherit;\n    border-top-width: 1px;\n  }\n  abbr:where([title]) {\n    -webkit-text-decoration: underline dotted;\n    text-decoration: underline dotted;\n  }\n  h1, h2, h3, h4, h5, h6 {\n    font-size: inherit;\n    font-weight: inherit;\n  }\n  a {\n    color: inherit;\n    -webkit-text-decoration: inherit;\n    text-decoration: inherit;\n  }\n  b, strong {\n    font-weight: bolder;\n  }\n  code, kbd, samp, pre {\n    font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace );\n    font-feature-settings: var(--default-mono-font-feature-settings, normal);\n    font-variation-settings: var(--default-mono-font-variation-settings, normal);\n    font-size: 1em;\n  }\n  small {\n    font-size: 80%;\n  }\n  sub, sup {\n    font-size: 75%;\n    line-height: 0;\n    position: relative;\n    vertical-align: baseline;\n  }\n  sub {\n    bottom: -0.25em;\n  }\n  sup {\n    top: -0.5em;\n  }\n  table {\n    text-indent: 0;\n    border-color: inherit;\n    border-collapse: collapse;\n  }\n  :-moz-focusring {\n    outline: auto;\n  }\n  progress {\n    vertical-align: baseline;\n  }\n  summary {\n    display: list-item;\n  }\n  ol, ul, menu {\n    list-style: none;\n  }\n  img, svg, video, canvas, audio, iframe, embed, object {\n    display: block;\n    vertical-align: middle;\n  }\n  img, video {\n    max-width: 100%;\n    height: auto;\n  }\n  button, input, select, optgroup, textarea, ::file-selector-button {\n    font: inherit;\n    font-feature-settings: inherit;\n    font-variation-settings: inherit;\n    letter-spacing: inherit;\n    color: inherit;\n    border-radius: 0;\n    background-color: transparent;\n    opacity: 1;\n  }\n  :where(select:is([multiple], [size])) optgroup {\n    font-weight: bolder;\n  }\n  :where(select:is([multiple], [size])) optgroup option {\n    padding-inline-start: 20px;\n  }\n  ::file-selector-button {\n    margin-inline-end: 4px;\n  }\n  ::placeholder {\n    opacity: 1;\n    color: color-mix(in oklab, currentColor 50%, transparent);\n  }\n  textarea {\n    resize: vertical;\n  }\n  ::-webkit-search-decoration {\n    -webkit-appearance: none;\n  }\n  ::-webkit-date-and-time-value {\n    min-height: 1lh;\n    text-align: inherit;\n  }\n  ::-webkit-datetime-edit {\n    display: inline-flex;\n  }\n  ::-webkit-datetime-edit-fields-wrapper {\n    padding: 0;\n  }\n  ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n    padding-block: 0;\n  }\n  :-moz-ui-invalid {\n    box-shadow: none;\n  }\n  button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {\n    appearance: button;\n  }\n  ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n    height: auto;\n  }\n  [hidden]:where(:not([hidden='until-found'])) {\n    display: none !important;\n  }\n}\n@layer utilities {\n  .menu {\n    display: flex;\n    width: fit-content;\n    flex-direction: column;\n    flex-wrap: wrap;\n    padding: calc(0.25rem * 2);\n    --menu-active-fg: var(--color-neutral-content);\n    --menu-active-bg: var(--color-neutral);\n    font-size: 0.875rem;\n    :where(li ul) {\n      position: relative;\n      margin-inline-start: calc(0.25rem * 4);\n      padding-inline-start: calc(0.25rem * 2);\n      white-space: nowrap;\n      &:before {\n        position: absolute;\n        inset-inline-start: calc(0.25rem * 0);\n        top: calc(0.25rem * 3);\n        bottom: calc(0.25rem * 3);\n        background-color: var(--color-base-content);\n        opacity: 10%;\n        width: var(--border);\n        content: \"\";\n      }\n    }\n    :where(li > .menu-dropdown:not(.menu-dropdown-show)) {\n      display: none;\n    }\n    :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), :where(li:not(.menu-title) > details > summary:not(.menu-title)) {\n      display: grid;\n      grid-auto-flow: column;\n      align-content: flex-start;\n      align-items: center;\n      gap: calc(0.25rem * 2);\n      border-radius: var(--radius-field);\n      padding-inline: calc(0.25rem * 3);\n      padding-block: calc(0.25rem * 1.5);\n      text-align: start;\n      transition-property: color, background-color, box-shadow;\n      transition-duration: 0.2s;\n      transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\n      grid-auto-columns: minmax(auto, max-content) auto max-content;\n      text-wrap: balance;\n      user-select: none;\n    }\n    :where(li > details > summary) {\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n      &::-webkit-details-marker {\n        display: none;\n      }\n    }\n    :where(li > details > summary), :where(li > .menu-dropdown-toggle) {\n      &:after {\n        justify-self: flex-end;\n        display: block;\n        height: 0.375rem;\n        width: 0.375rem;\n        rotate: -135deg;\n        translate: 0 -1px;\n        transition-property: rotate, translate;\n        transition-duration: 0.2s;\n        content: \"\";\n        transform-origin: 50% 50%;\n        box-shadow: 2px 2px inset;\n        pointer-events: none;\n      }\n    }\n    :where(li > details[open] > summary):after, :where(li > .menu-dropdown-toggle.menu-dropdown-show):after {\n      rotate: 45deg;\n      translate: 0 1px;\n    }\n    :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title), li:not(.menu-title, .disabled) > details > summary:not(.menu-title) ):not(.menu-active, :active, .btn) {\n      &.menu-focus, &:focus-visible {\n        cursor: pointer;\n        background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);\n        color: var(--color-base-content);\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n      }\n    }\n    :where( li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title):not(.menu-active, :active, .btn):hover, li:not(.menu-title, .disabled) > details > summary:not(.menu-title):not(.menu-active, :active, .btn):hover ) {\n      cursor: pointer;\n      background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n      box-shadow: 0 1px oklch(0% 0 0 / 0.01) inset, 0 -1px oklch(100% 0 0 / 0.01) inset;\n    }\n    :where(li:empty) {\n      background-color: var(--color-base-content);\n      opacity: 10%;\n      margin: 0.5rem 1rem;\n      height: 1px;\n    }\n    :where(li) {\n      position: relative;\n      display: flex;\n      flex-shrink: 0;\n      flex-direction: column;\n      flex-wrap: wrap;\n      align-items: stretch;\n      .badge {\n        justify-self: flex-end;\n      }\n      & > *:not(ul, .menu-title, details, .btn):active, & > *:not(ul, .menu-title, details, .btn).menu-active, & > details > summary:active {\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n        color: var(--menu-active-fg);\n        background-color: var(--menu-active-bg);\n        background-size: auto, calc(var(--noise) * 100%);\n        background-image: none, var(--fx-noise);\n        &:not(&:active) {\n          box-shadow: 0 2px calc(var(--depth) * 3px) -2px var(--menu-active-bg);\n        }\n      }\n      &.menu-disabled {\n        pointer-events: none;\n        color: color-mix(in oklab, var(--color-base-content) 20%, transparent);\n      }\n    }\n    .dropdown:focus-within {\n      .menu-dropdown-toggle:after {\n        rotate: 45deg;\n        translate: 0 1px;\n      }\n    }\n    .dropdown-content {\n      margin-top: calc(0.25rem * 2);\n      padding: calc(0.25rem * 2);\n      &:before {\n        display: none;\n      }\n    }\n  }\n  .btn {\n    :where(&) {\n      width: unset;\n    }\n    display: inline-flex;\n    flex-shrink: 0;\n    cursor: pointer;\n    flex-wrap: nowrap;\n    align-items: center;\n    justify-content: center;\n    gap: calc(0.25rem * 1.5);\n    text-align: center;\n    vertical-align: middle;\n    outline-offset: 2px;\n    webkit-user-select: none;\n    user-select: none;\n    padding-inline: var(--btn-p);\n    color: var(--btn-fg);\n    --tw-prose-links: var(--btn-fg);\n    height: var(--size);\n    font-size: var(--fontsize, 0.875rem);\n    font-weight: 600;\n    outline-color: var(--btn-color, var(--color-base-content));\n    transition-property: color, background-color, border-color, box-shadow;\n    transition-timing-function: cubic-bezier(0, 0, 0.2, 1);\n    transition-duration: 0.2s;\n    border-start-start-radius: var(--join-ss, var(--radius-field));\n    border-start-end-radius: var(--join-se, var(--radius-field));\n    border-end-start-radius: var(--join-es, var(--radius-field));\n    border-end-end-radius: var(--join-ee, var(--radius-field));\n    background-color: var(--btn-bg);\n    background-size: auto, calc(var(--noise) * 100%);\n    background-image: none, var(--btn-noise);\n    border-width: var(--border);\n    border-style: solid;\n    border-color: var(--btn-border);\n    text-shadow: 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 0.15));\n    box-shadow: 0 0.5px 0 0.5px oklch(100% 0 0 / calc(var(--depth) * 6%)) inset, var(--btn-shadow);\n    --size: calc(var(--size-field, 0.25rem) * 10);\n    --btn-bg: var(--btn-color, var(--color-base-200));\n    --btn-fg: var(--color-base-content);\n    --btn-p: 1rem;\n    --btn-border: color-mix(in oklab, var(--btn-bg), #000 calc(var(--depth) * 5%));\n    --btn-shadow: 0 3px 2px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000),\n    0 4px 3px -2px color-mix(in oklab, var(--btn-bg) calc(var(--depth) * 30%), #0000);\n    --btn-noise: var(--fx-noise);\n    .prose & {\n      text-decoration-line: none;\n    }\n    @media (hover: hover) {\n      &:hover {\n        --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%);\n      }\n    }\n    &:focus-visible {\n      outline-width: 2px;\n      outline-style: solid;\n    }\n    &:active:not(.btn-active) {\n      translate: 0 0.5px;\n      --btn-bg: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 5%);\n      --btn-border: color-mix(in oklab, var(--btn-color, var(--color-base-200)), #000 7%);\n      --btn-shadow: 0 0 0 0 oklch(0% 0 0/0), 0 0 0 0 oklch(0% 0 0/0);\n    }\n    &:is(:disabled, [disabled], .btn-disabled) {\n      &:not(.btn-link, .btn-ghost) {\n        background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);\n        box-shadow: none;\n      }\n      pointer-events: none;\n      --btn-border: #0000;\n      --btn-noise: none;\n      --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000);\n      @media (hover: hover) {\n        &:hover {\n          pointer-events: none;\n          background-color: color-mix(in oklab, var(--color-neutral) 20%, transparent);\n          --btn-border: #0000;\n          --btn-fg: color-mix(in oklch, var(--color-base-content) 20%, #0000);\n        }\n      }\n    }\n    &:is(input[type=\"checkbox\"], input[type=\"radio\"]) {\n      appearance: none;\n      &::after {\n        content: attr(aria-label);\n      }\n    }\n    &:where(input:checked:not(.filter .btn)) {\n      --btn-color: var(--color-primary);\n      --btn-fg: var(--color-primary-content);\n      isolation: isolate;\n    }\n  }\n  .list {\n    display: flex;\n    flex-direction: column;\n    font-size: 0.875rem;\n    :where(.list-row) {\n      --list-grid-cols: minmax(0, auto) 1fr;\n      position: relative;\n      display: grid;\n      grid-auto-flow: column;\n      gap: calc(0.25rem * 4);\n      border-radius: var(--radius-box);\n      padding: calc(0.25rem * 4);\n      word-break: break-word;\n      grid-template-columns: var(--list-grid-cols);\n      &:has(.list-col-grow:nth-child(1)) {\n        --list-grid-cols: 1fr;\n      }\n      &:has(.list-col-grow:nth-child(2)) {\n        --list-grid-cols: minmax(0, auto) 1fr;\n      }\n      &:has(.list-col-grow:nth-child(3)) {\n        --list-grid-cols: minmax(0, auto) minmax(0, auto) 1fr;\n      }\n      &:has(.list-col-grow:nth-child(4)) {\n        --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr;\n      }\n      &:has(.list-col-grow:nth-child(5)) {\n        --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto) 1fr;\n      }\n      &:has(.list-col-grow:nth-child(6)) {\n        --list-grid-cols: minmax(0, auto) minmax(0, auto) minmax(0, auto) minmax(0, auto)\n        minmax(0, auto) 1fr;\n      }\n      :not(.list-col-wrap) {\n        grid-row-start: 1;\n      }\n    }\n    & > :not(:last-child) {\n      &.list-row, .list-row {\n        &:after {\n          content: \"\";\n          border-bottom: var(--border) solid;\n          inset-inline: var(--radius-box);\n          position: absolute;\n          bottom: calc(0.25rem * 0);\n          border-color: color-mix(in oklab, var(--color-base-content) 5%, transparent);\n        }\n      }\n    }\n  }\n  .toast {\n    position: fixed;\n    inset-inline-start: auto;\n    inset-inline-end: calc(0.25rem * 0);\n    top: auto;\n    bottom: calc(0.25rem * 0);\n    margin: calc(0.25rem * 4);\n    display: flex;\n    min-width: fit-content;\n    flex-direction: column;\n    gap: calc(0.25rem * 2);\n    background-color: transparent;\n    white-space: nowrap;\n    translate: var(--toast-x, 0) var(--toast-y, 0);\n    & > * {\n      animation: toast 0.25s ease-out;\n    }\n    &:where(.toast-start) {\n      inset-inline-start: calc(0.25rem * 0);\n      inset-inline-end: auto;\n      --toast-x: 0;\n    }\n    &:where(.toast-center) {\n      inset-inline-start: calc(1/2 * 100%);\n      inset-inline-end: calc(1/2 * 100%);\n      --toast-x: -50%;\n    }\n    &:where(.toast-end) {\n      inset-inline-start: auto;\n      inset-inline-end: calc(0.25rem * 0);\n      --toast-x: 0;\n    }\n    &:where(.toast-bottom) {\n      top: auto;\n      bottom: calc(0.25rem * 0);\n      --toast-y: 0;\n    }\n    &:where(.toast-middle) {\n      top: calc(1/2 * 100%);\n      bottom: auto;\n      --toast-y: -50%;\n    }\n    &:where(.toast-top) {\n      top: calc(0.25rem * 0);\n      bottom: auto;\n      --toast-y: 0;\n    }\n  }\n  .toggle {\n    border: var(--border) solid currentColor;\n    color: var(--input-color);\n    position: relative;\n    display: inline-grid;\n    flex-shrink: 0;\n    cursor: pointer;\n    appearance: none;\n    place-content: center;\n    vertical-align: middle;\n    webkit-user-select: none;\n    user-select: none;\n    grid-template-columns: 0fr 1fr 1fr;\n    --radius-selector-max: calc(\n    var(--radius-selector) + var(--radius-selector) + var(--radius-selector)\n  );\n    border-radius: calc( var(--radius-selector) + min(var(--toggle-p), var(--radius-selector-max)) + min(var(--border), var(--radius-selector-max)) );\n    padding: var(--toggle-p);\n    box-shadow: 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000) inset;\n    transition: color 0.3s, grid-template-columns 0.2s;\n    --input-color: color-mix(in oklab, var(--color-base-content) 50%, #0000);\n    --toggle-p: 0.1875rem;\n    --size: calc(var(--size-selector, 0.25rem) * 6);\n    width: calc((var(--size) * 2) - (var(--border) + var(--toggle-p)) * 2);\n    height: var(--size);\n    > * {\n      z-index: 1;\n      grid-column: span 1 / span 1;\n      grid-column-start: 2;\n      grid-row-start: 1;\n      height: 100%;\n      cursor: pointer;\n      appearance: none;\n      background-color: transparent;\n      padding: calc(0.25rem * 0.5);\n      transition: opacity 0.2s, rotate 0.4s;\n      border: none;\n      &:focus {\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n      }\n      &:nth-child(2) {\n        color: var(--color-base-100);\n        rotate: 0deg;\n      }\n      &:nth-child(3) {\n        color: var(--color-base-100);\n        opacity: 0%;\n        rotate: -15deg;\n      }\n    }\n    &:has(:checked) {\n      > :nth-child(2) {\n        opacity: 0%;\n        rotate: 15deg;\n      }\n      > :nth-child(3) {\n        opacity: 100%;\n        rotate: 0deg;\n      }\n    }\n    &:before {\n      position: relative;\n      inset-inline-start: calc(0.25rem * 0);\n      grid-column-start: 2;\n      grid-row-start: 1;\n      aspect-ratio: 1 / 1;\n      height: 100%;\n      border-radius: var(--radius-selector);\n      background-color: currentColor;\n      translate: 0;\n      --tw-content: \"\";\n      content: var(--tw-content);\n      transition: background-color 0.1s, translate 0.2s, inset-inline-start 0.2s;\n      box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000);\n      background-size: auto, calc(var(--noise) * 100%);\n      background-image: none, var(--fx-noise);\n    }\n    @media (forced-colors: active) {\n      &:before {\n        outline-style: var(--tw-outline-style);\n        outline-width: 1px;\n        outline-offset: calc(1px * -1);\n      }\n    }\n    @media print {\n      &:before {\n        outline: 0.25rem solid;\n        outline-offset: -1rem;\n      }\n    }\n    &:focus-visible, &:has(:focus-visible) {\n      outline: 2px solid currentColor;\n      outline-offset: 2px;\n    }\n    &:checked, &[aria-checked=\"true\"], &:has(> input:checked) {\n      grid-template-columns: 1fr 1fr 0fr;\n      background-color: var(--color-base-100);\n      --input-color: var(--color-base-content);\n      &:before {\n        background-color: currentColor;\n      }\n      @starting-style {\n        &:before {\n          opacity: 0;\n        }\n      }\n    }\n    &:indeterminate {\n      grid-template-columns: 0.5fr 1fr 0.5fr;\n    }\n    &:disabled {\n      cursor: not-allowed;\n      opacity: 30%;\n      &:before {\n        background-color: transparent;\n        border: var(--border) solid currentColor;\n      }\n    }\n  }\n  .input {\n    cursor: text;\n    border: var(--border) solid #0000;\n    position: relative;\n    display: inline-flex;\n    flex-shrink: 1;\n    appearance: none;\n    align-items: center;\n    gap: calc(0.25rem * 2);\n    background-color: var(--color-base-100);\n    padding-inline: calc(0.25rem * 3);\n    vertical-align: middle;\n    white-space: nowrap;\n    width: clamp(3rem, 20rem, 100%);\n    height: var(--size);\n    font-size: 0.875rem;\n    border-start-start-radius: var(--join-ss, var(--radius-field));\n    border-start-end-radius: var(--join-se, var(--radius-field));\n    border-end-start-radius: var(--join-es, var(--radius-field));\n    border-end-end-radius: var(--join-ee, var(--radius-field));\n    border-color: var(--input-color);\n    box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;\n    --size: calc(var(--size-field, 0.25rem) * 10);\n    --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);\n    &:where(input) {\n      display: inline-flex;\n    }\n    :where(input) {\n      display: inline-flex;\n      height: 100%;\n      width: 100%;\n      appearance: none;\n      background-color: transparent;\n      border: none;\n      &:focus, &:focus-within {\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n      }\n    }\n    &:focus, &:focus-within {\n      --input-color: var(--color-base-content);\n      box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000);\n      outline: 2px solid var(--input-color);\n      outline-offset: 2px;\n      isolation: isolate;\n    }\n    &:has(> input[disabled]), &:is(:disabled, [disabled]) {\n      cursor: not-allowed;\n      border-color: var(--color-base-200);\n      background-color: var(--color-base-200);\n      color: color-mix(in oklab, var(--color-base-content) 40%, transparent);\n      &::placeholder {\n        color: color-mix(in oklab, var(--color-base-content) 20%, transparent);\n      }\n      box-shadow: none;\n    }\n    &:has(> input[disabled]) > input[disabled] {\n      cursor: not-allowed;\n    }\n    &::-webkit-date-and-time-value {\n      text-align: inherit;\n    }\n    &[type=\"number\"] {\n      &::-webkit-inner-spin-button {\n        margin-block: calc(0.25rem * -3);\n        margin-inline-end: calc(0.25rem * -3);\n      }\n    }\n    &::-webkit-calendar-picker-indicator {\n      position: absolute;\n      inset-inline-end: 0.75em;\n    }\n  }\n  .table {\n    font-size: 0.875rem;\n    position: relative;\n    width: 100%;\n    border-radius: var(--radius-box);\n    text-align: left;\n    &:where(:dir(rtl), [dir=\"rtl\"], [dir=\"rtl\"] *) {\n      text-align: right;\n    }\n    tr.row-hover {\n      &, &:nth-child(even) {\n        &:hover {\n          @media (hover: hover) {\n            background-color: var(--color-base-200);\n          }\n        }\n      }\n    }\n    :where(th, td) {\n      padding-inline: calc(0.25rem * 4);\n      padding-block: calc(0.25rem * 3);\n      vertical-align: middle;\n    }\n    :where(thead, tfoot) {\n      white-space: nowrap;\n      color: color-mix(in oklab, var(--color-base-content) 60%, transparent);\n      font-size: 0.875rem;\n      font-weight: 600;\n    }\n    :where(tfoot) {\n      border-top: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);\n    }\n    :where(.table-pin-rows thead tr) {\n      position: sticky;\n      top: calc(0.25rem * 0);\n      z-index: 1;\n      background-color: var(--color-base-100);\n    }\n    :where(.table-pin-rows tfoot tr) {\n      position: sticky;\n      bottom: calc(0.25rem * 0);\n      z-index: 1;\n      background-color: var(--color-base-100);\n    }\n    :where(.table-pin-cols tr th) {\n      position: sticky;\n      right: calc(0.25rem * 0);\n      left: calc(0.25rem * 0);\n      background-color: var(--color-base-100);\n    }\n    :where(thead tr, tbody tr:not(:last-child)) {\n      border-bottom: var(--border) solid color-mix(in oklch, var(--color-base-content) 5%, #0000);\n    }\n  }\n  .range {\n    appearance: none;\n    webkit-appearance: none;\n    --range-thumb: var(--color-base-100);\n    --range-thumb-size: calc(var(--size-selector, 0.25rem) * 6);\n    --range-progress: currentColor;\n    --range-fill: 1;\n    --range-p: 0.25rem;\n    --range-bg: color-mix(in oklab, currentColor 10%, #0000);\n    cursor: pointer;\n    overflow: hidden;\n    background-color: transparent;\n    vertical-align: middle;\n    width: clamp(3rem, 20rem, 100%);\n    --radius-selector-max: calc(\n    var(--radius-selector) + var(--radius-selector) + var(--radius-selector)\n  );\n    border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));\n    border: none;\n    height: var(--range-thumb-size);\n    [dir=\"rtl\"] & {\n      --range-dir: -1;\n    }\n    &:focus {\n      outline: none;\n    }\n    &:focus-visible {\n      outline: 2px solid;\n      outline-offset: 2px;\n    }\n    &::-webkit-slider-runnable-track {\n      width: 100%;\n      background-color: var(--range-bg);\n      border-radius: var(--radius-selector);\n      height: calc(var(--range-thumb-size) * 0.5);\n    }\n    @media (forced-colors: active) {\n      &::-webkit-slider-runnable-track {\n        border: 1px solid;\n      }\n    }\n    @media (forced-colors: active) {\n      &::-moz-range-track {\n        border: 1px solid;\n      }\n    }\n    &::-webkit-slider-thumb {\n      position: relative;\n      box-sizing: border-box;\n      border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));\n      background-color: currentColor;\n      height: var(--range-thumb-size);\n      width: var(--range-thumb-size);\n      border: var(--range-p) solid;\n      appearance: none;\n      webkit-appearance: none;\n      top: 50%;\n      color: var(--range-progress);\n      transform: translateY(-50%);\n      box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill));\n    }\n    &::-moz-range-track {\n      width: 100%;\n      background-color: var(--range-bg);\n      border-radius: var(--radius-selector);\n      height: calc(var(--range-thumb-size) * 0.5);\n    }\n    &::-moz-range-thumb {\n      position: relative;\n      box-sizing: border-box;\n      border-radius: calc(var(--radius-selector) + min(var(--range-p), var(--radius-selector-max)));\n      background-color: currentColor;\n      height: var(--range-thumb-size);\n      width: var(--range-thumb-size);\n      border: var(--range-p) solid;\n      top: 50%;\n      color: var(--range-progress);\n      box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px color-mix(in oklab, currentColor calc(var(--depth) * 10%), #0000), 0 0 0 2rem var(--range-thumb) inset, calc((var(--range-dir, 1) * -100rem) - (var(--range-dir, 1) * var(--range-thumb-size) / 2)) 0 0 calc(100rem * var(--range-fill));\n    }\n    &:disabled {\n      cursor: not-allowed;\n      opacity: 30%;\n    }\n  }\n  .card {\n    position: relative;\n    display: flex;\n    flex-direction: column;\n    border-radius: var(--radius-box);\n    outline-width: 2px;\n    transition: outline 0.2s ease-in-out;\n    outline: 0 solid #0000;\n    outline-offset: 2px;\n    &:focus {\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n    }\n    &:focus-visible {\n      outline-color: currentColor;\n    }\n    :where(figure:first-child) {\n      overflow: hidden;\n      border-start-start-radius: inherit;\n      border-start-end-radius: inherit;\n      border-end-start-radius: unset;\n      border-end-end-radius: unset;\n    }\n    :where(figure:last-child) {\n      overflow: hidden;\n      border-start-start-radius: unset;\n      border-start-end-radius: unset;\n      border-end-start-radius: inherit;\n      border-end-end-radius: inherit;\n    }\n    &:where(.card-border) {\n      border: var(--border) solid var(--color-base-200);\n    }\n    &:where(.card-dash) {\n      border: var(--border) dashed var(--color-base-200);\n    }\n    &.image-full {\n      display: grid;\n      &:before {\n        position: relative;\n        grid-column-start: 1;\n        grid-row-start: 1;\n        border-radius: var(--radius-box);\n        background-color: var(--color-neutral);\n        opacity: 75%;\n        content: \"\";\n      }\n      > * {\n        grid-column-start: 1;\n        grid-row-start: 1;\n      }\n      > .card-body {\n        position: relative;\n        color: var(--color-neutral-content);\n      }\n      :where(figure) {\n        overflow: hidden;\n        border-radius: inherit;\n      }\n      > figure img {\n        height: 100%;\n        object-fit: cover;\n      }\n    }\n    figure {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n    &:has(> input:is(input[type=\"checkbox\"], input[type=\"radio\"])) {\n      cursor: pointer;\n      user-select: none;\n    }\n    &:has(> :checked) {\n      outline: 2px solid currentColor;\n    }\n  }\n  .sr-only {\n    position: absolute;\n    width: 1px;\n    height: 1px;\n    padding: 0;\n    margin: -1px;\n    overflow: hidden;\n    clip: rect(0, 0, 0, 0);\n    white-space: nowrap;\n    border-width: 0;\n  }\n  .select {\n    border: var(--border) solid #0000;\n    position: relative;\n    display: inline-flex;\n    flex-shrink: 1;\n    appearance: none;\n    align-items: center;\n    gap: calc(0.25rem * 1.5);\n    background-color: var(--color-base-100);\n    padding-inline-start: calc(0.25rem * 4);\n    padding-inline-end: calc(0.25rem * 7);\n    vertical-align: middle;\n    width: clamp(3rem, 20rem, 100%);\n    height: var(--size);\n    font-size: 0.875rem;\n    border-start-start-radius: var(--join-ss, var(--radius-field));\n    border-start-end-radius: var(--join-se, var(--radius-field));\n    border-end-start-radius: var(--join-es, var(--radius-field));\n    border-end-end-radius: var(--join-ee, var(--radius-field));\n    background-image: linear-gradient(45deg, #0000 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, #0000 50%);\n    background-position: calc(100% - 20px) calc(1px + 50%), calc(100% - 16.1px) calc(1px + 50%);\n    background-size: 4px 4px, 4px 4px;\n    background-repeat: no-repeat;\n    text-overflow: ellipsis;\n    box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;\n    border-color: var(--input-color);\n    --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);\n    --size: calc(var(--size-field, 0.25rem) * 10);\n    [dir=\"rtl\"] & {\n      background-position: calc(0% + 12px) calc(1px + 50%), calc(0% + 16px) calc(1px + 50%);\n    }\n    select {\n      margin-inline-start: calc(0.25rem * -4);\n      margin-inline-end: calc(0.25rem * -7);\n      width: calc(100% + 2.75rem);\n      appearance: none;\n      padding-inline-start: calc(0.25rem * 4);\n      padding-inline-end: calc(0.25rem * 7);\n      height: calc(100% - 2px);\n      background: inherit;\n      border-radius: inherit;\n      border-style: none;\n      &:focus, &:focus-within {\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n      }\n      &:not(:last-child) {\n        margin-inline-end: calc(0.25rem * -5.5);\n        background-image: none;\n      }\n    }\n    &:focus, &:focus-within {\n      --input-color: var(--color-base-content);\n      box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000);\n      outline: 2px solid var(--input-color);\n      outline-offset: 2px;\n    }\n    &:has(> select[disabled]), &:is(:disabled, [disabled]) {\n      cursor: not-allowed;\n      border-color: var(--color-base-200);\n      background-color: var(--color-base-200);\n      color: color-mix(in oklab, var(--color-base-content) 40%, transparent);\n      &::placeholder {\n        color: color-mix(in oklab, var(--color-base-content) 20%, transparent);\n      }\n    }\n    &:has(> select[disabled]) > select[disabled] {\n      cursor: not-allowed;\n    }\n  }\n  .menu-horizontal {\n    display: inline-flex;\n    flex-direction: row;\n    & > li:not(.menu-title) > details > ul {\n      position: absolute;\n      margin-inline-start: calc(0.25rem * 0);\n      margin-top: calc(0.25rem * 4);\n      padding-block: calc(0.25rem * 2);\n      padding-inline-end: calc(0.25rem * 2);\n    }\n    & > li > details > ul {\n      &:before {\n        content: none;\n      }\n    }\n    :where(& > li:not(.menu-title) > details > ul) {\n      border-radius: var(--radius-box);\n      background-color: var(--color-base-100);\n      box-shadow: 0 1px 3px 0 oklch(0% 0 0/0.1), 0 1px 2px -1px oklch(0% 0 0/0.1);\n    }\n  }\n  .checkbox {\n    border: var(--border) solid var(--input-color, color-mix(in oklab, var(--color-base-content) 20%, #0000));\n    position: relative;\n    flex-shrink: 0;\n    cursor: pointer;\n    appearance: none;\n    border-radius: var(--radius-selector);\n    padding: calc(0.25rem * 1);\n    vertical-align: middle;\n    color: var(--color-base-content);\n    box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 0 #0000 inset, 0 0 #0000;\n    transition: background-color 0.2s, box-shadow 0.2s;\n    --size: calc(var(--size-selector, 0.25rem) * 6);\n    width: var(--size);\n    height: var(--size);\n    background-size: auto, calc(var(--noise) * 100%);\n    background-image: none, var(--fx-noise);\n    &:before {\n      --tw-content: \"\";\n      content: var(--tw-content);\n      display: block;\n      width: 100%;\n      height: 100%;\n      rotate: 45deg;\n      background-color: currentColor;\n      opacity: 0%;\n      transition: clip-path 0.3s, opacity 0.1s, rotate 0.3s, translate 0.3s;\n      transition-delay: 0.1s;\n      clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 70% 80%, 70% 100%);\n      box-shadow: 0px 3px 0 0px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;\n      font-size: 1rem;\n      line-height: 0.75;\n    }\n    &:focus-visible {\n      outline: 2px solid var(--input-color, currentColor);\n      outline-offset: 2px;\n    }\n    &:checked, &[aria-checked=\"true\"] {\n      background-color: var(--input-color, #0000);\n      box-shadow: 0 0 #0000 inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1));\n      &:before {\n        clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 0%, 70% 0%, 70% 100%);\n        opacity: 100%;\n      }\n      @media (forced-colors: active) {\n        &:before {\n          rotate: 0deg;\n          background-color: transparent;\n          --tw-content: \"✔︎\";\n          clip-path: none;\n        }\n      }\n      @media print {\n        &:before {\n          rotate: 0deg;\n          background-color: transparent;\n          --tw-content: \"✔︎\";\n          clip-path: none;\n        }\n      }\n    }\n    &:indeterminate {\n      &:before {\n        rotate: 0deg;\n        opacity: 100%;\n        translate: 0 -35%;\n        clip-path: polygon(20% 100%, 20% 80%, 50% 80%, 50% 80%, 80% 80%, 80% 100%);\n      }\n    }\n    &:disabled {\n      cursor: not-allowed;\n      opacity: 20%;\n    }\n  }\n  .radio {\n    position: relative;\n    flex-shrink: 0;\n    cursor: pointer;\n    appearance: none;\n    border-radius: calc(infinity * 1px);\n    padding: calc(0.25rem * 1);\n    vertical-align: middle;\n    border: var(--border) solid var(--input-color, color-mix(in srgb, currentColor 20%, #0000));\n    box-shadow: 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset;\n    --size: calc(var(--size-selector, 0.25rem) * 6);\n    width: var(--size);\n    height: var(--size);\n    color: var(--input-color, currentColor);\n    &:before {\n      display: block;\n      width: 100%;\n      height: 100%;\n      border-radius: calc(infinity * 1px);\n      --tw-content: \"\";\n      content: var(--tw-content);\n      background-size: auto, calc(var(--noise) * 100%);\n      background-image: none, var(--fx-noise);\n    }\n    &:focus-visible {\n      outline: 2px solid currentColor;\n    }\n    &:checked, &[aria-checked=\"true\"] {\n      animation: radio 0.2s ease-out;\n      border-color: currentColor;\n      background-color: var(--color-base-100);\n      &:before {\n        background-color: currentColor;\n        box-shadow: 0 -1px oklch(0% 0 0 / calc(var(--depth) * 0.1)) inset, 0 8px 0 -4px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset, 0 1px oklch(0% 0 0 / calc(var(--depth) * 0.1));\n      }\n      @media (forced-colors: active) {\n        &:before {\n          outline-style: var(--tw-outline-style);\n          outline-width: 1px;\n          outline-offset: calc(1px * -1);\n        }\n      }\n      @media print {\n        &:before {\n          outline: 0.25rem solid;\n          outline-offset: -1rem;\n        }\n      }\n    }\n    &:disabled {\n      cursor: not-allowed;\n      opacity: 20%;\n    }\n  }\n  .progress {\n    position: relative;\n    height: calc(0.25rem * 2);\n    width: 100%;\n    appearance: none;\n    overflow: hidden;\n    border-radius: var(--radius-box);\n    background-color: color-mix(in oklab, currentColor 20%, transparent);\n    color: var(--color-base-content);\n    &:indeterminate {\n      background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );\n      background-size: 200%;\n      background-position-x: 15%;\n      animation: progress 5s ease-in-out infinite;\n      @supports (-moz-appearance: none) {\n        &::-moz-progress-bar {\n          background-color: transparent;\n          background-image: repeating-linear-gradient( 90deg, currentColor -1%, currentColor 10%, #0000 10%, #0000 90% );\n          background-size: 200%;\n          background-position-x: 15%;\n          animation: progress 5s ease-in-out infinite;\n        }\n      }\n    }\n    @supports (-moz-appearance: none) {\n      &::-moz-progress-bar {\n        border-radius: var(--radius-box);\n        background-color: currentColor;\n      }\n    }\n    @supports (-webkit-appearance: none) {\n      &::-webkit-progress-bar {\n        border-radius: var(--radius-box);\n        background-color: transparent;\n      }\n      &::-webkit-progress-value {\n        border-radius: var(--radius-box);\n        background-color: currentColor;\n      }\n    }\n  }\n  .absolute {\n    position: absolute;\n  }\n  .fixed {\n    position: fixed;\n  }\n  .relative {\n    position: relative;\n  }\n  .static {\n    position: static;\n  }\n  .inset-0 {\n    inset: calc(var(--spacing) * 0);\n  }\n  .inset-y-0 {\n    inset-block: calc(var(--spacing) * 0);\n  }\n  .right-0 {\n    right: calc(var(--spacing) * 0);\n  }\n  .left-0 {\n    left: calc(var(--spacing) * 0);\n  }\n  .left-\\[40rem\\] {\n    left: 40rem;\n  }\n  .textarea {\n    border: var(--border) solid #0000;\n    min-height: calc(0.25rem * 20);\n    flex-shrink: 1;\n    appearance: none;\n    border-radius: var(--radius-field);\n    background-color: var(--color-base-100);\n    padding-block: calc(0.25rem * 2);\n    vertical-align: middle;\n    width: clamp(3rem, 20rem, 100%);\n    padding-inline-start: 0.75rem;\n    padding-inline-end: 0.75rem;\n    font-size: 0.875rem;\n    border-color: var(--input-color);\n    box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000) inset, 0 -1px oklch(100% 0 0 / calc(var(--depth) * 0.1)) inset;\n    --input-color: color-mix(in oklab, var(--color-base-content) 20%, #0000);\n    textarea {\n      appearance: none;\n      background-color: transparent;\n      border: none;\n      &:focus, &:focus-within {\n        --tw-outline-style: none;\n        outline-style: none;\n        @media (forced-colors: active) {\n          outline: 2px solid transparent;\n          outline-offset: 2px;\n        }\n      }\n    }\n    &:focus, &:focus-within {\n      --input-color: var(--color-base-content);\n      box-shadow: 0 1px color-mix(in oklab, var(--input-color) calc(var(--depth) * 10%), #0000);\n      outline: 2px solid var(--input-color);\n      outline-offset: 2px;\n      isolation: isolate;\n    }\n    &:has(> textarea[disabled]), &:is(:disabled, [disabled]) {\n      cursor: not-allowed;\n      border-color: var(--color-base-200);\n      background-color: var(--color-base-200);\n      color: color-mix(in oklab, var(--color-base-content) 40%, transparent);\n      &::placeholder {\n        color: color-mix(in oklab, var(--color-base-content) 20%, transparent);\n      }\n      box-shadow: none;\n    }\n    &:has(> textarea[disabled]) > textarea[disabled] {\n      cursor: not-allowed;\n    }\n  }\n  .z-0 {\n    z-index: 0;\n  }\n  .z-10 {\n    z-index: 10;\n  }\n  .z-50 {\n    z-index: 50;\n  }\n  .container {\n    width: 100%;\n    @media (width >= 40rem) {\n      max-width: 40rem;\n    }\n    @media (width >= 48rem) {\n      max-width: 48rem;\n    }\n    @media (width >= 64rem) {\n      max-width: 64rem;\n    }\n    @media (width >= 80rem) {\n      max-width: 80rem;\n    }\n    @media (width >= 96rem) {\n      max-width: 96rem;\n    }\n  }\n  .divider {\n    display: flex;\n    height: calc(0.25rem * 4);\n    flex-direction: row;\n    align-items: center;\n    align-self: stretch;\n    white-space: nowrap;\n    margin: var(--divider-m, 1rem 0);\n    &:before, &:after {\n      content: \"\";\n      height: calc(0.25rem * 0.5);\n      width: 100%;\n      flex-grow: 1;\n      background-color: color-mix(in oklab, var(--color-base-content) 10%, transparent);\n    }\n    @media print {\n      &:before, &:after {\n        border: 0.5px solid;\n      }\n    }\n    &:not(:empty) {\n      gap: calc(0.25rem * 4);\n    }\n  }\n  .-mx-2 {\n    margin-inline: calc(var(--spacing) * -2);\n  }\n  .mx-auto {\n    margin-inline: auto;\n  }\n  .-my-0\\.5 {\n    margin-block: calc(var(--spacing) * -0.5);\n  }\n  .label {\n    display: inline-flex;\n    align-items: center;\n    gap: calc(0.25rem * 1.5);\n    white-space: nowrap;\n    color: color-mix(in oklab, currentColor 60%, transparent);\n    &:has(input) {\n      cursor: pointer;\n    }\n    &:is(.input > *, .select > *) {\n      display: flex;\n      height: calc(100% - 0.5rem);\n      align-items: center;\n      padding-inline: calc(0.25rem * 3);\n      white-space: nowrap;\n      font-size: inherit;\n      &:first-child {\n        margin-inline-start: calc(0.25rem * -3);\n        margin-inline-end: calc(0.25rem * 3);\n        border-inline-end: var(--border) solid color-mix(in oklab, currentColor 10%, #0000);\n      }\n      &:last-child {\n        margin-inline-start: calc(0.25rem * 3);\n        margin-inline-end: calc(0.25rem * -3);\n        border-inline-start: var(--border) solid color-mix(in oklab, currentColor 10%, #0000);\n      }\n    }\n  }\n  .mt-1\\.5 {\n    margin-top: calc(var(--spacing) * 1.5);\n  }\n  .mt-4 {\n    margin-top: calc(var(--spacing) * 4);\n  }\n  .mt-8 {\n    margin-top: calc(var(--spacing) * 8);\n  }\n  .mt-10 {\n    margin-top: calc(var(--spacing) * 10);\n  }\n  .mb-1 {\n    margin-bottom: calc(var(--spacing) * 1);\n  }\n  .mb-2 {\n    margin-bottom: calc(var(--spacing) * 2);\n  }\n  .ml-1 {\n    margin-left: calc(var(--spacing) * 1);\n  }\n  .ml-3 {\n    margin-left: calc(var(--spacing) * 3);\n  }\n  .status {\n    display: inline-block;\n    aspect-ratio: 1 / 1;\n    width: calc(0.25rem * 2);\n    height: calc(0.25rem * 2);\n    border-radius: var(--radius-selector);\n    background-color: color-mix(in oklab, var(--color-base-content) 20%, transparent);\n    background-position: center;\n    background-repeat: no-repeat;\n    vertical-align: middle;\n    color: color-mix(in oklab, var(--color-black) 30%, transparent);\n    background-image: radial-gradient( circle at 35% 30%, oklch(1 0 0 / calc(var(--depth) * 0.5)), #0000 );\n    box-shadow: 0 2px 3px -1px color-mix(in oklab, currentColor calc(var(--depth) * 100%), #0000);\n  }\n  .badge {\n    display: inline-flex;\n    align-items: center;\n    justify-content: center;\n    gap: calc(0.25rem * 2);\n    border-radius: var(--radius-selector);\n    vertical-align: middle;\n    color: var(--badge-fg);\n    border: var(--border) solid var(--badge-color, var(--color-base-200));\n    font-size: 0.875rem;\n    width: fit-content;\n    padding-inline: calc(0.25rem * 3 - var(--border));\n    background-size: auto, calc(var(--noise) * 100%);\n    background-image: none, var(--fx-noise);\n    background-color: var(--badge-bg);\n    --badge-bg: var(--badge-color, var(--color-base-100));\n    --badge-fg: var(--color-base-content);\n    --size: calc(var(--size-selector, 0.25rem) * 6);\n    height: var(--size);\n    &.badge-outline {\n      --badge-fg: var(--badge-color);\n      --badge-bg: #0000;\n      background-image: none;\n    }\n    &.badge-dash {\n      --badge-fg: var(--badge-color);\n      --badge-bg: #0000;\n      border-style: dashed;\n      background-image: none;\n    }\n    &.badge-soft {\n      color: var(--badge-color, var(--color-base-content));\n      background-color: color-mix( in oklab, var(--badge-color, var(--color-base-content)) 8%, var(--color-base-100) );\n      border-color: color-mix( in oklab, var(--badge-color, var(--color-base-content)) 10%, var(--color-base-100) );\n      background-image: none;\n    }\n  }\n  .hero-arrow-left {\n    --hero-arrow-left: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M10.5%2019.5%203%2012m0%200%207.5-7.5M3%2012h18%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-arrow-left);\n    mask: var(--hero-arrow-left);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.5rem;\n    height: 1.5rem;\n  }\n  .hero-arrow-path {\n    --hero-arrow-path: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M16.023%209.348h4.992v-.001M2.985%2019.644v-4.992m0%200h4.992m-4.993%200%203.181%203.183a8.25%208.25%200%200%200%2013.803-3.7M4.031%209.865a8.25%208.25%200%200%201%2013.803-3.7l3.181%203.182m0-4.991v4.99%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-arrow-path);\n    mask: var(--hero-arrow-path);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.5rem;\n    height: 1.5rem;\n  }\n  .hero-computer-desktop-micro {\n    --hero-computer-desktop-micro: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M2%204.25A2.25%202.25%200%200%201%204.25%202h7.5A2.25%202.25%200%200%201%2014%204.25v5.5A2.25%202.25%200%200%201%2011.75%2012h-1.312c.1.128.21.248.328.36a.75.75%200%200%201%20.234.545v.345a.75.75%200%200%201-.75.75h-4.5a.75.75%200%200%201-.75-.75v-.345a.75.75%200%200%201%20.234-.545c.118-.111.228-.232.328-.36H4.25A2.25%202.25%200%200%201%202%209.75v-5.5Zm2.25-.75a.75.75%200%200%200-.75.75v4.5c0%20.414.336.75.75.75h7.5a.75.75%200%200%200%20.75-.75v-4.5a.75.75%200%200%200-.75-.75h-7.5Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-computer-desktop-micro);\n    mask: var(--hero-computer-desktop-micro);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1rem;\n    height: 1rem;\n  }\n  .hero-exclamation-circle {\n    --hero-exclamation-circle: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-8-5a.75.75%200%200%201%20.75.75v4.5a.75.75%200%200%201-1.5%200v-4.5A.75.75%200%200%201%2010%205Zm0%2010a1%201%200%201%200%200-2%201%201%200%200%200%200%202Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-exclamation-circle);\n    mask: var(--hero-exclamation-circle);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.25rem;\n    height: 1.25rem;\n  }\n  .hero-information-circle {\n    --hero-information-circle: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M18%2010a8%208%200%201%201-16%200%208%208%200%200%201%2016%200Zm-7-4a1%201%200%201%201-2%200%201%201%200%200%201%202%200ZM9%209a.75.75%200%200%200%200%201.5h.253a.25.25%200%200%201%20.244.304l-.459%202.066A1.75%201.75%200%200%200%2010.747%2015H11a.75.75%200%200%200%200-1.5h-.253a.25.25%200%200%201-.244-.304l.459-2.066A1.75%201.75%200%200%200%209.253%209H9Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-information-circle);\n    mask: var(--hero-information-circle);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.25rem;\n    height: 1.25rem;\n  }\n  .hero-moon-micro {\n    --hero-moon-micro: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20d%3D%22M14.438%2010.148c.19-.425-.321-.787-.748-.601A5.5%205.5%200%200%201%206.453%202.31c.186-.427-.176-.938-.6-.748a6.501%206.501%200%201%200%208.585%208.586Z%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-moon-micro);\n    mask: var(--hero-moon-micro);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1rem;\n    height: 1rem;\n  }\n  .hero-pencil-square {\n    --hero-pencil-square: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22m16.862%204.487%201.687-1.688a1.875%201.875%200%201%201%202.652%202.652L10.582%2016.07a4.5%204.5%200%200%201-1.897%201.13L6%2018l.8-2.685a4.5%204.5%200%200%201%201.13-1.897l8.932-8.931Zm0%200L19.5%207.125M18%2014v4.75A2.25%202.25%200%200%201%2015.75%2021H5.25A2.25%202.25%200%200%201%203%2018.75V8.25A2.25%202.25%200%200%201%205.25%206H10%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-pencil-square);\n    mask: var(--hero-pencil-square);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.5rem;\n    height: 1.5rem;\n  }\n  .hero-plus {\n    --hero-plus: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20viewBox%3D%220%200%2024%2024%22%20stroke-width%3D%221.5%22%20stroke%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20d%3D%22M12%204.5v15m7.5-7.5h-15%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-plus);\n    mask: var(--hero-plus);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.5rem;\n    height: 1.5rem;\n  }\n  .hero-sun-micro {\n    --hero-sun-micro: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2016%2016%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20d%3D%22M8%201a.75.75%200%200%201%20.75.75v1.5a.75.75%200%200%201-1.5%200v-1.5A.75.75%200%200%201%208%201ZM10.5%208a2.5%202.5%200%201%201-5%200%202.5%202.5%200%200%201%205%200ZM12.95%204.11a.75.75%200%201%200-1.06-1.06l-1.062%201.06a.75.75%200%200%200%201.061%201.062l1.06-1.061ZM15%208a.75.75%200%200%201-.75.75h-1.5a.75.75%200%200%201%200-1.5h1.5A.75.75%200%200%201%2015%208ZM11.89%2012.95a.75.75%200%200%200%201.06-1.06l-1.06-1.062a.75.75%200%200%200-1.062%201.061l1.061%201.06ZM8%2012a.75.75%200%200%201%20.75.75v1.5a.75.75%200%200%201-1.5%200v-1.5A.75.75%200%200%201%208%2012ZM5.172%2011.89a.75.75%200%200%200-1.061-1.062L3.05%2011.89a.75.75%200%201%200%201.06%201.06l1.06-1.06ZM4%208a.75.75%200%200%201-.75.75h-1.5a.75.75%200%200%201%200-1.5h1.5A.75.75%200%200%201%204%208ZM4.11%205.172A.75.75%200%200%200%205.173%204.11L4.11%203.05a.75.75%200%201%200-1.06%201.06l1.06%201.06Z%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-sun-micro);\n    mask: var(--hero-sun-micro);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1rem;\n    height: 1rem;\n  }\n  .hero-x-mark {\n    --hero-x-mark: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22currentColor%22%20aria-hidden%3D%22true%22%20data-slot%3D%22icon%22%3E%20%20%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.47%205.47a.75.75%200%200%201%201.06%200L12%2010.94l5.47-5.47a.75.75%200%201%201%201.06%201.06L13.06%2012l5.47%205.47a.75.75%200%201%201-1.06%201.06L12%2013.06l-5.47%205.47a.75.75%200%200%201-1.06-1.06L10.94%2012%205.47%206.53a.75.75%200%200%201%200-1.06Z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');\n    -webkit-mask: var(--hero-x-mark);\n    mask: var(--hero-x-mark);\n    mask-repeat: no-repeat;\n    background-color: currentColor;\n    vertical-align: middle;\n    display: inline-block;\n    width: 1.5rem;\n    height: 1.5rem;\n  }\n  .navbar {\n    display: flex;\n    width: 100%;\n    align-items: center;\n    padding: 0.5rem;\n    min-height: 4rem;\n  }\n  .footer {\n    display: grid;\n    width: 100%;\n    grid-auto-flow: row;\n    place-items: start;\n    column-gap: calc(0.25rem * 4);\n    row-gap: calc(0.25rem * 10);\n    font-size: 0.875rem;\n    line-height: 1.25rem;\n    & > * {\n      display: grid;\n      place-items: start;\n      gap: calc(0.25rem * 2);\n    }\n    &.footer-center {\n      grid-auto-flow: column dense;\n      place-items: center;\n      text-align: center;\n      & > * {\n        place-items: center;\n      }\n    }\n  }\n  .fieldset-label {\n    display: flex;\n    align-items: center;\n    gap: calc(0.25rem * 1.5);\n    color: color-mix(in oklab, var(--color-base-content) 60%, transparent);\n    &:has(input) {\n      cursor: pointer;\n    }\n  }\n  .alert {\n    display: grid;\n    align-items: center;\n    gap: calc(0.25rem * 4);\n    border-radius: var(--radius-box);\n    padding-inline: calc(0.25rem * 4);\n    padding-block: calc(0.25rem * 3);\n    color: var(--color-base-content);\n    background-color: var(--alert-color, var(--color-base-200));\n    justify-content: start;\n    justify-items: start;\n    grid-auto-flow: column;\n    grid-template-columns: auto;\n    text-align: start;\n    border: var(--border) solid var(--color-base-200);\n    font-size: 0.875rem;\n    line-height: 1.25rem;\n    background-size: auto, calc(var(--noise) * 100%);\n    background-image: none, var(--fx-noise);\n    box-shadow: 0 3px 0 -2px oklch(100% 0 0 / calc(var(--depth) * 0.08)) inset, 0 1px color-mix( in oklab, color-mix(in oklab, #000 20%, var(--alert-color, var(--color-base-200))) calc(var(--depth) * 20%), #0000 ), 0 4px 3px -2px oklch(0% 0 0 / calc(var(--depth) * 0.08));\n    &:has(:nth-child(2)) {\n      grid-template-columns: auto minmax(auto, 1fr);\n    }\n    &.alert-outline {\n      background-color: transparent;\n      color: var(--alert-color);\n      box-shadow: none;\n      background-image: none;\n    }\n    &.alert-dash {\n      background-color: transparent;\n      color: var(--alert-color);\n      border-style: dashed;\n      box-shadow: none;\n      background-image: none;\n    }\n    &.alert-soft {\n      color: var(--alert-color, var(--color-base-content));\n      background: color-mix( in oklab, var(--alert-color, var(--color-base-content)) 8%, var(--color-base-100) );\n      border-color: color-mix( in oklab, var(--alert-color, var(--color-base-content)) 10%, var(--color-base-100) );\n      box-shadow: none;\n      background-image: none;\n    }\n  }\n  .fieldset {\n    display: grid;\n    gap: calc(0.25rem * 1.5);\n    padding-block: calc(0.25rem * 1);\n    font-size: 0.75rem;\n    grid-template-columns: 1fr;\n    grid-auto-rows: max-content;\n  }\n  .block {\n    display: block;\n  }\n  .contents {\n    display: contents;\n  }\n  .flex {\n    display: flex;\n  }\n  .grid {\n    display: grid;\n  }\n  .hidden {\n    display: none;\n  }\n  .inline-flex {\n    display: inline-flex;\n  }\n  .table {\n    display: table;\n  }\n  .size-3 {\n    width: calc(var(--spacing) * 3);\n    height: calc(var(--spacing) * 3);\n  }\n  .size-4 {\n    width: calc(var(--spacing) * 4);\n    height: calc(var(--spacing) * 4);\n  }\n  .size-5 {\n    width: calc(var(--spacing) * 5);\n    height: calc(var(--spacing) * 5);\n  }\n  .h-3 {\n    height: calc(var(--spacing) * 3);\n  }\n  .h-4 {\n    height: calc(var(--spacing) * 4);\n  }\n  .h-6 {\n    height: calc(var(--spacing) * 6);\n  }\n  .h-12 {\n    height: calc(var(--spacing) * 12);\n  }\n  .h-full {\n    height: 100%;\n  }\n  .w-0 {\n    width: calc(var(--spacing) * 0);\n  }\n  .w-3 {\n    width: calc(var(--spacing) * 3);\n  }\n  .w-4 {\n    width: calc(var(--spacing) * 4);\n  }\n  .w-6 {\n    width: calc(var(--spacing) * 6);\n  }\n  .w-80 {\n    width: calc(var(--spacing) * 80);\n  }\n  .w-\\[33\\%\\] {\n    width: 33%;\n  }\n  .w-full {\n    width: 100%;\n  }\n  .max-w-2xl {\n    max-width: var(--container-2xl);\n  }\n  .max-w-80 {\n    max-width: calc(var(--spacing) * 80);\n  }\n  .max-w-sm {\n    max-width: var(--container-sm);\n  }\n  .max-w-xl {\n    max-width: var(--container-xl);\n  }\n  .flex-1 {\n    flex: 1;\n  }\n  .flex-none {\n    flex: none;\n  }\n  .shrink-0 {\n    flex-shrink: 0;\n  }\n  .translate-y-0 {\n    --tw-translate-y: calc(var(--spacing) * 0);\n    translate: var(--tw-translate-x) var(--tw-translate-y);\n  }\n  .translate-y-4 {\n    --tw-translate-y: calc(var(--spacing) * 4);\n    translate: var(--tw-translate-x) var(--tw-translate-y);\n  }\n  .skeleton {\n    border-radius: var(--radius-box);\n    background-color: var(--color-base-300);\n    @media (prefers-reduced-motion: reduce) {\n      transition-duration: 15s;\n    }\n    will-change: background-position;\n    animation: skeleton 1.8s ease-in-out infinite;\n    background-image: linear-gradient( 105deg, #0000 0% 40%, var(--color-base-100) 50%, #0000 60% 100% );\n    background-size: 200% auto;\n    background-repeat: no-repeat;\n    background-position-x: -50%;\n  }\n  .link {\n    cursor: pointer;\n    text-decoration-line: underline;\n    &:focus {\n      --tw-outline-style: none;\n      outline-style: none;\n      @media (forced-colors: active) {\n        outline: 2px solid transparent;\n        outline-offset: 2px;\n      }\n    }\n    &:focus-visible {\n      outline: 2px solid currentColor;\n      outline-offset: 2px;\n    }\n  }\n  .cursor-pointer {\n    cursor: pointer;\n  }\n  .grid-cols-1 {\n    grid-template-columns: repeat(1, minmax(0, 1fr));\n  }\n  .flex-row {\n    flex-direction: row;\n  }\n  .items-center {\n    align-items: center;\n  }\n  .justify-between {\n    justify-content: space-between;\n  }\n  .justify-end {\n    justify-content: flex-end;\n  }\n  .gap-2 {\n    gap: calc(var(--spacing) * 2);\n  }\n  .gap-3 {\n    gap: calc(var(--spacing) * 3);\n  }\n  .gap-4 {\n    gap: calc(var(--spacing) * 4);\n  }\n  .gap-6 {\n    gap: calc(var(--spacing) * 6);\n  }\n  .space-y-4 {\n    :where(& > :not(:last-child)) {\n      --tw-space-y-reverse: 0;\n      margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));\n      margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)));\n    }\n  }\n  .gap-x-6 {\n    column-gap: calc(var(--spacing) * 6);\n  }\n  .space-x-4 {\n    :where(& > :not(:last-child)) {\n      --tw-space-x-reverse: 0;\n      margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));\n      margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));\n    }\n  }\n  .gap-y-4 {\n    row-gap: calc(var(--spacing) * 4);\n  }\n  .self-start {\n    align-self: flex-start;\n  }\n  .rounded-box {\n    border-radius: var(--radius-box);\n  }\n  .rounded-box {\n    border-radius: var(--radius-box);\n  }\n  .rounded-full {\n    border-radius: calc(infinity * 1px);\n  }\n  .rounded-lg {\n    border-radius: var(--radius-lg);\n  }\n  .border-1 {\n    border-style: var(--tw-border-style);\n    border-width: 1px;\n  }\n  .border-2 {\n    border-style: var(--tw-border-style);\n    border-width: 2px;\n  }\n  .alert-error {\n    border-color: var(--color-error);\n    color: var(--color-error-content);\n    --alert-color: var(--color-error);\n  }\n  .alert-info {\n    border-color: var(--color-info);\n    color: var(--color-info-content);\n    --alert-color: var(--color-info);\n  }\n  .border-base-200 {\n    border-color: var(--color-base-200);\n  }\n  .border-base-300 {\n    border-color: var(--color-base-300);\n  }\n  .table-zebra {\n    tbody {\n      tr {\n        &:nth-child(even) {\n          background-color: var(--color-base-200);\n          :where(.table-pin-cols tr th) {\n            background-color: var(--color-base-200);\n          }\n        }\n        &.row-hover {\n          &, &:nth-child(even) {\n            &:hover {\n              @media (hover: hover) {\n                background-color: var(--color-base-300);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n  .bg-base-100 {\n    background-color: var(--color-base-100);\n  }\n  .bg-base-200 {\n    background-color: var(--color-base-200);\n  }\n  .bg-base-300 {\n    background-color: var(--color-base-300);\n  }\n  .fill-base-content\\/40 {\n    fill: color-mix(in oklab, var(--color-base-content) 40%, transparent);\n  }\n  .checkbox-sm {\n    padding: 0.1875rem;\n    --size: calc(var(--size-selector, 0.25rem) * 5);\n  }\n  .p-2 {\n    padding: calc(var(--spacing) * 2);\n  }\n  .badge-sm {\n    --size: calc(var(--size-selector, 0.25rem) * 5);\n    font-size: 0.75rem;\n    padding-inline: calc(0.25rem * 2.5 - var(--border));\n  }\n  .px-1 {\n    padding-inline: calc(var(--spacing) * 1);\n  }\n  .px-2 {\n    padding-inline: calc(var(--spacing) * 2);\n  }\n  .px-4 {\n    padding-inline: calc(var(--spacing) * 4);\n  }\n  .px-6 {\n    padding-inline: calc(var(--spacing) * 6);\n  }\n  .py-0\\.5 {\n    padding-block: calc(var(--spacing) * 0.5);\n  }\n  .py-4 {\n    padding-block: calc(var(--spacing) * 4);\n  }\n  .py-10 {\n    padding-block: calc(var(--spacing) * 10);\n  }\n  .py-20 {\n    padding-block: calc(var(--spacing) * 20);\n  }\n  .pb-4 {\n    padding-bottom: calc(var(--spacing) * 4);\n  }\n  .text-center {\n    text-align: center;\n  }\n  .text-lg {\n    font-size: var(--text-lg);\n    line-height: var(--tw-leading, var(--text-lg--line-height));\n  }\n  .text-sm {\n    font-size: var(--text-sm);\n    line-height: var(--tw-leading, var(--text-sm--line-height));\n  }\n  .text-\\[2rem\\] {\n    font-size: 2rem;\n  }\n  .leading-6 {\n    --tw-leading: calc(var(--spacing) * 6);\n    line-height: calc(var(--spacing) * 6);\n  }\n  .leading-7 {\n    --tw-leading: calc(var(--spacing) * 7);\n    line-height: calc(var(--spacing) * 7);\n  }\n  .leading-8 {\n    --tw-leading: calc(var(--spacing) * 8);\n    line-height: calc(var(--spacing) * 8);\n  }\n  .leading-10 {\n    --tw-leading: calc(var(--spacing) * 10);\n    line-height: calc(var(--spacing) * 10);\n  }\n  .font-bold {\n    --tw-font-weight: var(--font-weight-bold);\n    font-weight: var(--font-weight-bold);\n  }\n  .font-semibold {\n    --tw-font-weight: var(--font-weight-semibold);\n    font-weight: var(--font-weight-semibold);\n  }\n  .tracking-tighter {\n    --tw-tracking: var(--tracking-tighter);\n    letter-spacing: var(--tracking-tighter);\n  }\n  .text-balance {\n    text-wrap: balance;\n  }\n  .text-wrap {\n    text-wrap: wrap;\n  }\n  .text-base-content\\/70 {\n    color: color-mix(in oklab, var(--color-base-content) 70%, transparent);\n  }\n  .text-base-content\\/80 {\n    color: color-mix(in oklab, var(--color-base-content) 80%, transparent);\n  }\n  .text-error {\n    color: var(--color-error);\n  }\n  .underline {\n    text-decoration-line: underline;\n  }\n  .opacity-0 {\n    opacity: 0%;\n  }\n  .opacity-40 {\n    opacity: 40%;\n  }\n  .opacity-75 {\n    opacity: 75%;\n  }\n  .opacity-100 {\n    opacity: 100%;\n  }\n  .outline {\n    outline-style: var(--tw-outline-style);\n    outline-width: 1px;\n  }\n  .btn-ghost {\n    &:not(.btn-active, :hover, :active:focus, :focus-visible) {\n      --btn-shadow: \"\";\n      --btn-bg: #0000;\n      --btn-border: #0000;\n      --btn-noise: none;\n      &:not(:disabled, [disabled], .btn-disabled) {\n        outline-color: currentColor;\n        --btn-fg: currentColor;\n      }\n    }\n  }\n  .brightness-200 {\n    --tw-brightness: brightness(200%);\n    filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,);\n  }\n  .transition {\n    transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .transition-\\[left\\] {\n    transition-property: left;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .transition-all {\n    transition-property: all;\n    transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n    transition-duration: var(--tw-duration, var(--default-transition-duration));\n  }\n  .duration-200 {\n    --tw-duration: 200ms;\n    transition-duration: 200ms;\n  }\n  .duration-300 {\n    --tw-duration: 300ms;\n    transition-duration: 300ms;\n  }\n  .ease-in {\n    --tw-ease: var(--ease-in);\n    transition-timing-function: var(--ease-in);\n  }\n  .ease-out {\n    --tw-ease: var(--ease-out);\n    transition-timing-function: var(--ease-out);\n  }\n  .btn-soft {\n    &:not(.btn-active, :hover, :active:focus, :focus-visible, :disabled, [disabled], .btn-disabled) {\n      --btn-shadow: \"\";\n      --btn-fg: var(--btn-color, var(--color-base-content));\n      --btn-bg: color-mix(\n      in oklab,\n      var(--btn-color, var(--color-base-content)) 8%,\n      var(--color-base-100)\n    );\n      --btn-border: color-mix(\n      in oklab,\n      var(--btn-color, var(--color-base-content)) 10%,\n      var(--color-base-100)\n    );\n      --btn-noise: none;\n    }\n  }\n  .badge-warning {\n    --badge-color: var(--color-warning);\n    --badge-fg: var(--color-warning-content);\n  }\n  .btn-primary {\n    --btn-color: var(--color-primary);\n    --btn-fg: var(--color-primary-content);\n  }\n  .input-error {\n    &, &:focus, &:focus-within {\n      --input-color: var(--color-error);\n    }\n  }\n  .select-error {\n    &, &:focus, &:focus-within {\n      --input-color: var(--color-error);\n    }\n  }\n  .textarea-error {\n    &, &:focus, &:focus-within {\n      --input-color: var(--color-error);\n    }\n  }\n  .group-hover\\:bg-base-300 {\n    &:is(:where(.group):hover *) {\n      @media (hover: hover) {\n        background-color: var(--color-base-300);\n      }\n    }\n  }\n  .group-hover\\:fill-base-content {\n    &:is(:where(.group):hover *) {\n      @media (hover: hover) {\n        fill: var(--color-base-content);\n      }\n    }\n  }\n  .group-hover\\:opacity-70 {\n    &:is(:where(.group):hover *) {\n      @media (hover: hover) {\n        opacity: 70%;\n      }\n    }\n  }\n  .hover\\:cursor-pointer {\n    &:hover {\n      @media (hover: hover) {\n        cursor: pointer;\n      }\n    }\n  }\n  .hover\\:bg-base-200 {\n    &:hover {\n      @media (hover: hover) {\n        background-color: var(--color-base-200);\n      }\n    }\n  }\n  .hover\\:text-base-content {\n    &:hover {\n      @media (hover: hover) {\n        color: var(--color-base-content);\n      }\n    }\n  }\n  .hover\\:underline {\n    &:hover {\n      @media (hover: hover) {\n        text-decoration-line: underline;\n      }\n    }\n  }\n  .hover\\:opacity-100 {\n    &:hover {\n      @media (hover: hover) {\n        opacity: 100%;\n      }\n    }\n  }\n  .motion-safe\\:animate-spin {\n    @media (prefers-reduced-motion: no-preference) {\n      animation: var(--animate-spin);\n    }\n  }\n  .sm\\:w-96 {\n    @media (width >= 40rem) {\n      width: calc(var(--spacing) * 96);\n    }\n  }\n  .sm\\:w-auto {\n    @media (width >= 40rem) {\n      width: auto;\n    }\n  }\n  .sm\\:max-w-96 {\n    @media (width >= 40rem) {\n      max-width: calc(var(--spacing) * 96);\n    }\n  }\n  .sm\\:translate-y-0 {\n    @media (width >= 40rem) {\n      --tw-translate-y: calc(var(--spacing) * 0);\n      translate: var(--tw-translate-x) var(--tw-translate-y);\n    }\n  }\n  .sm\\:scale-95 {\n    @media (width >= 40rem) {\n      --tw-scale-x: 95%;\n      --tw-scale-y: 95%;\n      --tw-scale-z: 95%;\n      scale: var(--tw-scale-x) var(--tw-scale-y);\n    }\n  }\n  .sm\\:scale-100 {\n    @media (width >= 40rem) {\n      --tw-scale-x: 100%;\n      --tw-scale-y: 100%;\n      --tw-scale-z: 100%;\n      scale: var(--tw-scale-x) var(--tw-scale-y);\n    }\n  }\n  .sm\\:grid-cols-2 {\n    @media (width >= 40rem) {\n      grid-template-columns: repeat(2, minmax(0, 1fr));\n    }\n  }\n  .sm\\:grid-cols-3 {\n    @media (width >= 40rem) {\n      grid-template-columns: repeat(3, minmax(0, 1fr));\n    }\n  }\n  .sm\\:flex-col {\n    @media (width >= 40rem) {\n      flex-direction: column;\n    }\n  }\n  .sm\\:px-6 {\n    @media (width >= 40rem) {\n      padding-inline: calc(var(--spacing) * 6);\n    }\n  }\n  .sm\\:py-6 {\n    @media (width >= 40rem) {\n      padding-block: calc(var(--spacing) * 6);\n    }\n  }\n  .sm\\:py-28 {\n    @media (width >= 40rem) {\n      padding-block: calc(var(--spacing) * 28);\n    }\n  }\n  .sm\\:group-hover\\:scale-105 {\n    @media (width >= 40rem) {\n      &:is(:where(.group):hover *) {\n        @media (hover: hover) {\n          --tw-scale-x: 105%;\n          --tw-scale-y: 105%;\n          --tw-scale-z: 105%;\n          scale: var(--tw-scale-x) var(--tw-scale-y);\n        }\n      }\n    }\n  }\n  .lg\\:mx-0 {\n    @media (width >= 64rem) {\n      margin-inline: calc(var(--spacing) * 0);\n    }\n  }\n  .lg\\:block {\n    @media (width >= 64rem) {\n      display: block;\n    }\n  }\n  .lg\\:px-8 {\n    @media (width >= 64rem) {\n      padding-inline: calc(var(--spacing) * 8);\n    }\n  }\n  .xl\\:left-\\[50rem\\] {\n    @media (width >= 80rem) {\n      left: 50rem;\n    }\n  }\n  .xl\\:px-28 {\n    @media (width >= 80rem) {\n      padding-inline: calc(var(--spacing) * 28);\n    }\n  }\n  .xl\\:py-32 {\n    @media (width >= 80rem) {\n      padding-block: calc(var(--spacing) * 32);\n    }\n  }\n  .\\[\\[data-theme\\=dark\\]_\\&\\]\\:left-\\[66\\%\\] {\n    [data-theme=dark] & {\n      left: 66%;\n    }\n  }\n  .\\[\\[data-theme\\=light\\]_\\&\\]\\:left-\\[33\\%\\] {\n    [data-theme=light] & {\n      left: 33%;\n    }\n  }\n}\n[data-phx-session] {\n  display: contents;\n}\n@layer base {\n  @property --radialprogress {\n    syntax: \"<percentage>\";\n    inherits: true;\n    initial-value: 0%;\n  }\n}\n@layer base {\n  :root {\n    scrollbar-color: color-mix(in oklch, currentColor 35%, #0000) #0000;\n  }\n}\n@layer base {\n  :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not([class*=\"drawer-open\"]) > .drawer-toggle:checked ) {\n    overflow: hidden;\n  }\n}\n@layer base {\n  :root, [data-theme] {\n    background-color: var(--root-bg, var(--color-base-100));\n    color: var(--color-base-content);\n  }\n}\n@layer base {\n  :root {\n    --fx-noise: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.34' numOctaves='4' stitchTiles='stitch'%3E%3C/feTurbulence%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)' opacity='0.2'%3E%3C/rect%3E%3C/svg%3E\");\n  }\n  .chat {\n    --mask-chat: url(\"data:image/svg+xml,%3csvg width='13' height='13' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill='black' d='M0 11.5004C0 13.0004 2 13.0004 2 13.0004H12H13V0.00036329L12.5 0C12.5 0 11.977 2.09572 11.8581 2.50033C11.6075 3.35237 10.9149 4.22374 9 5.50036C6 7.50036 0 10.0004 0 11.5004Z'/%3e%3c/svg%3e\");\n  }\n}\n@layer base {\n  :where( :root:has( .modal-open, .modal[open], .modal:target, .modal-toggle:checked, .drawer:not(.drawer-open) > .drawer-toggle:checked ) ) {\n    scrollbar-gutter: stable;\n    background-image: linear-gradient(var(--color-base-100), var(--color-base-100));\n    --root-bg: color-mix(in srgb, var(--color-base-100), oklch(0% 0 0) 40%);\n  }\n}\n@keyframes skeleton {\n  0% {\n    background-position: 150%;\n  }\n  100% {\n    background-position: -50%;\n  }\n}\n@keyframes progress {\n  50% {\n    background-position-x: -115%;\n  }\n}\n@keyframes radio {\n  0% {\n    padding: 5px;\n  }\n  50% {\n    padding: 3px;\n  }\n}\n@keyframes dropdown {\n  0% {\n    opacity: 0;\n  }\n}\n@keyframes rating {\n  0%, 40% {\n    scale: 1.1;\n    filter: brightness(1.05) contrast(1.05);\n  }\n}\n@keyframes toast {\n  0% {\n    scale: 0.9;\n    opacity: 0;\n  }\n  100% {\n    scale: 1;\n    opacity: 1;\n  }\n}\n@layer base {\n  @media (prefers-color-scheme: dark) {\n    :root {\n      color-scheme: dark;\n      --color-base-100: oklch(30.33% 0.016 252.42);\n      --color-base-200: oklch(25.26% 0.014 253.1);\n      --color-base-300: oklch(20.15% 0.012 254.09);\n      --color-base-content: oklch(97.807% 0.029 256.847);\n      --color-primary: oklch(58% 0.233 277.117);\n      --color-primary-content: oklch(96% 0.018 272.314);\n      --color-secondary: oklch(58% 0.233 277.117);\n      --color-secondary-content: oklch(96% 0.018 272.314);\n      --color-accent: oklch(60% 0.25 292.717);\n      --color-accent-content: oklch(96% 0.016 293.756);\n      --color-neutral: oklch(37% 0.044 257.287);\n      --color-neutral-content: oklch(98% 0.003 247.858);\n      --color-info: oklch(58% 0.158 241.966);\n      --color-info-content: oklch(97% 0.013 236.62);\n      --color-success: oklch(60% 0.118 184.704);\n      --color-success-content: oklch(98% 0.014 180.72);\n      --color-warning: oklch(66% 0.179 58.318);\n      --color-warning-content: oklch(98% 0.022 95.277);\n      --color-error: oklch(58% 0.253 17.585);\n      --color-error-content: oklch(96% 0.015 12.422);\n      --radius-selector: 0.25rem;\n      --radius-field: 0.25rem;\n      --radius-box: 0.5rem;\n      --size-selector: 0.21875rem;\n      --size-field: 0.21875rem;\n      --border: 1.5px;\n      --depth: 1;\n      --noise: 0;\n    }\n  }\n}\n@layer base {\n  :root:has(input.theme-controller[value=dark]:checked),[data-theme=\"dark\"] {\n    color-scheme: dark;\n    --color-base-100: oklch(30.33% 0.016 252.42);\n    --color-base-200: oklch(25.26% 0.014 253.1);\n    --color-base-300: oklch(20.15% 0.012 254.09);\n    --color-base-content: oklch(97.807% 0.029 256.847);\n    --color-primary: oklch(58% 0.233 277.117);\n    --color-primary-content: oklch(96% 0.018 272.314);\n    --color-secondary: oklch(58% 0.233 277.117);\n    --color-secondary-content: oklch(96% 0.018 272.314);\n    --color-accent: oklch(60% 0.25 292.717);\n    --color-accent-content: oklch(96% 0.016 293.756);\n    --color-neutral: oklch(37% 0.044 257.287);\n    --color-neutral-content: oklch(98% 0.003 247.858);\n    --color-info: oklch(58% 0.158 241.966);\n    --color-info-content: oklch(97% 0.013 236.62);\n    --color-success: oklch(60% 0.118 184.704);\n    --color-success-content: oklch(98% 0.014 180.72);\n    --color-warning: oklch(66% 0.179 58.318);\n    --color-warning-content: oklch(98% 0.022 95.277);\n    --color-error: oklch(58% 0.253 17.585);\n    --color-error-content: oklch(96% 0.015 12.422);\n    --radius-selector: 0.25rem;\n    --radius-field: 0.25rem;\n    --radius-box: 0.5rem;\n    --size-selector: 0.21875rem;\n    --size-field: 0.21875rem;\n    --border: 1.5px;\n    --depth: 1;\n    --noise: 0;\n  }\n}\n@layer base {\n  :where(:root),:root:has(input.theme-controller[value=light]:checked),[data-theme=\"light\"] {\n    color-scheme: light;\n    --color-base-100: oklch(98% 0 0);\n    --color-base-200: oklch(96% 0.001 286.375);\n    --color-base-300: oklch(92% 0.004 286.32);\n    --color-base-content: oklch(21% 0.006 285.885);\n    --color-primary: oklch(70% 0.213 47.604);\n    --color-primary-content: oklch(98% 0.016 73.684);\n    --color-secondary: oklch(55% 0.027 264.364);\n    --color-secondary-content: oklch(98% 0.002 247.839);\n    --color-accent: oklch(0% 0 0);\n    --color-accent-content: oklch(100% 0 0);\n    --color-neutral: oklch(44% 0.017 285.786);\n    --color-neutral-content: oklch(98% 0 0);\n    --color-info: oklch(62% 0.214 259.815);\n    --color-info-content: oklch(97% 0.014 254.604);\n    --color-success: oklch(70% 0.14 182.503);\n    --color-success-content: oklch(98% 0.014 180.72);\n    --color-warning: oklch(66% 0.179 58.318);\n    --color-warning-content: oklch(98% 0.022 95.277);\n    --color-error: oklch(58% 0.253 17.585);\n    --color-error-content: oklch(96% 0.015 12.422);\n    --radius-selector: 0.25rem;\n    --radius-field: 0.25rem;\n    --radius-box: 0.5rem;\n    --size-selector: 0.21875rem;\n    --size-field: 0.21875rem;\n    --border: 1.5px;\n    --depth: 1;\n    --noise: 0;\n  }\n}\n@property --tw-translate-x {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-translate-y {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-translate-z {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-space-y-reverse {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-space-x-reverse {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 0;\n}\n@property --tw-border-style {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: solid;\n}\n@property --tw-leading {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-font-weight {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-tracking {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-outline-style {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: solid;\n}\n@property --tw-blur {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-brightness {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-contrast {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-grayscale {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-hue-rotate {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-invert {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-opacity {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-saturate {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-sepia {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-drop-shadow {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-duration {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-ease {\n  syntax: \"*\";\n  inherits: false;\n}\n@property --tw-scale-x {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 1;\n}\n@property --tw-scale-y {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 1;\n}\n@property --tw-scale-z {\n  syntax: \"*\";\n  inherits: false;\n  initial-value: 1;\n}\n@keyframes spin {\n  to {\n    transform: rotate(360deg);\n  }\n}\n\n"
  },
  {
    "path": "installer/templates/phx_static/robots.txt",
    "content": "# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file\n#\n# To ban all spiders from the entire site uncomment the next two lines:\n# User-agent: *\n# Disallow: /\n"
  },
  {
    "path": "installer/templates/phx_test/controllers/error_html_test.exs.eex",
    "content": "defmodule <%= @web_namespace %>.ErrorHTMLTest do\n  use <%= @web_namespace %>.ConnCase, async: true\n\n  # Bring render_to_string/4 for testing custom views\n  import Phoenix.Template, only: [render_to_string: 4]\n\n  test \"renders 404.html\" do\n    assert render_to_string(<%= @web_namespace %>.ErrorHTML, \"404\", \"html\", []) == \"Not Found\"\n  end\n\n  test \"renders 500.html\" do\n    assert render_to_string(<%= @web_namespace %>.ErrorHTML, \"500\", \"html\", []) == \"Internal Server Error\"\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_test/controllers/error_json_test.exs.eex",
    "content": "defmodule <%= @web_namespace %>.ErrorJSONTest do\n  use <%= @web_namespace %>.ConnCase, async: true\n\n  test \"renders 404\" do\n    assert <%= @web_namespace %>.ErrorJSON.render(\"404.json\", %{}) == %{errors: %{detail: \"Not Found\"}}\n  end\n\n  test \"renders 500\" do\n    assert <%= @web_namespace %>.ErrorJSON.render(\"500.json\", %{}) ==\n             %{errors: %{detail: \"Internal Server Error\"}}\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_test/controllers/page_controller_test.exs.eex",
    "content": "defmodule <%= @web_namespace %>.PageControllerTest do\n  use <%= @web_namespace %>.ConnCase\n\n  test \"GET /\", %{conn: conn} do\n    conn = get(conn, ~p\"/\")\n    assert html_response(conn, 200) =~ \"Peace of mind from prototype to production\"\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_test/support/conn_case.ex.eex",
    "content": "defmodule <%= @web_namespace %>.ConnCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  tests that require setting up a connection.\n\n  Such tests rely on `Phoenix.ConnTest` and also\n  import other functionality to make it easier\n  to build common data structures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use <%= @web_namespace %>.ConnCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # The default endpoint for testing\n      @endpoint <%= @endpoint_module %>\n\n      use <%= @web_namespace %>, :verified_routes\n\n      # Import conveniences for testing with connections\n      import Plug.Conn\n      import Phoenix.ConnTest\n      import <%= @web_namespace %>.ConnCase\n    end\n  end<%= if @ecto do %>\n\n  setup tags do\n    <%= @app_module %>.DataCase.setup_sandbox(tags)\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end<% else %>\n\n  setup _tags do\n    {:ok, conn: Phoenix.ConnTest.build_conn()}\n  end<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/README.md.eex",
    "content": "# <%= @root_app_module %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/README.md.eex",
    "content": "# <%= @app_module %>\n\n**TODO: Add description**\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/config/config.exs.eex",
    "content": "import Config\n\n<%= if @namespaced? || @ecto do %>\n# Configure Mix tasks and generators\nconfig :<%= @app_name %><%= if @namespaced? do %>,\n  namespace: <%= @app_module %><% end %><%= if @ecto do %>,\n  ecto_repos: [<%= @app_module %>.Repo]<% end %><% end %><%= if @mailer do %>\n\n# Configure the mailer\n#\n# By default it uses the \"Local\" adapter which stores the emails\n# locally. You can see the emails in your browser, at \"/dev/mailbox\".\n#\n# For production it's recommended to configure a different adapter\n# at the `config/runtime.exs`.\nconfig :<%= @app_name %>, <%= @app_module %>.Mailer, adapter: Swoosh.Adapters.Local<% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/formatter.exs.eex",
    "content": "[<%= if @ecto do %>\n  import_deps: [:ecto, :ecto_sql],\n  subdirectories: [\"priv/*/migrations\"],<% end %><%= if @html do %>\n  plugins: [Phoenix.LiveView.HTMLFormatter],<% end %>\n  inputs: [<%= if @html do %>\"*.{heex,ex,exs}\", \"{config,lib,test}/**/*.{heex,ex,exs}\"<% else %>\"*.{ex,exs}\", \"{config,lib,test}/**/*.{ex,exs}\"<% end %><%= if @ecto do %>, \"priv/*/seeds.exs\"<% end %>]\n]\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/gitignore.eex",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Temporary files, for example, from tests.\n/tmp/\n\n# Ignore package tarball (built via \"mix hex.build\").\n<%= @app_name %>-*.tar\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/lib/app_name/application.ex.eex",
    "content": "defmodule <%= @app_module %>.Application do\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  @moduledoc false\n\n  use Application\n\n  @impl true\n  def start(_type, _args) do\n    children = [<%= if @ecto do %>\n      <%= @app_module %>.Repo,<% end %><%= if @adapter_app == :ecto_sqlite3 do %>\n      {Ecto.Migrator,\n       repos: Application.fetch_env!(<%= inspect(String.to_atom(@app_name)) %>, :ecto_repos), skip: skip_migrations?()},<% end %>\n      {DNSCluster, query: Application.get_env(<%= inspect(String.to_atom(@app_name)) %>, :dns_cluster_query) || :ignore},\n      {Phoenix.PubSub, name: <%= @app_module %>.PubSub}\n      # Start a worker by calling: <%= @app_module %>.Worker.start_link(arg)\n      # {<%= @app_module %>.Worker, arg}\n    ]\n\n    Supervisor.start_link(children, strategy: :one_for_one, name: <%= @app_module %>.Supervisor)\n  end<%= if @adapter_app == :ecto_sqlite3 do %>\n\n  defp skip_migrations?() do\n    # By default, sqlite migrations are run when using a release\n    System.get_env(\"RELEASE_NAME\") == nil\n  end<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/lib/app_name.ex.eex",
    "content": "defmodule <%= @app_module %> do\n  @moduledoc \"\"\"\n  <%= @app_module %> keeps the contexts that define your domain\n  and business logic.\n\n  Contexts are also responsible for managing your data, regardless\n  if it comes from the database, an external API or others.\n  \"\"\"\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/mix.exs.eex",
    "content": "defmodule <%= @app_module %>.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :<%= @app_name %>,\n      version: \"0.1.0\",\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.15\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      aliases: aliases(),\n      deps: deps()\n    ]\n  end\n\n  # Configuration for the OTP application.\n  #\n  # Type `mix help compile.app` for more information.\n  def application do\n    [\n      mod: {<%= @app_module %>.Application, []},\n      extra_applications: [:logger, :runtime_tools]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      {:dns_cluster, \"~> 0.2.0\"},\n      {:phoenix_pubsub, \"~> 2.1\"}<%= if @ecto do %>,\n      {:ecto_sql, \"~> 3.13\"},\n      {:<%= @adapter_app %>, \">= 0.0.0\"},\n      {:jason, \"~> 1.2\"}<% end %><%= if @mailer do %>,\n      {:swoosh, \"~> 1.16\"},\n      {:req, \"~> 0.5\"}<% end %>\n    ]\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  defp aliases do\n    [\n      setup: [\"deps.get\"<%= if @ecto do %>, \"ecto.setup\"<% end %>]<%= if @ecto do %>,\n      \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run #{__DIR__}/priv/repo/seeds.exs\"],\n      \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n      test: [\"ecto.create --quiet\", \"ecto.migrate --quiet\", \"test\"]<% end %>\n    ]\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name/test/test_helper.exs.eex",
    "content": "ExUnit.start()<%= if @ecto do %>\n<%= @adapter_config[:test_setup_all] %><% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/README.md.eex",
    "content": "# <%= @web_namespace %>\n\nTo start your Phoenix server:\n\n* Run `mix setup` to install and setup dependencies\n* Start Phoenix endpoint with `mix phx.server`\n\nNow you can visit [`localhost:4000`](http://localhost:4000) from your browser.\n\nReady to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).\n\n## Learn more\n\n* Official website: https://www.phoenixframework.org/\n* Guides: https://hexdocs.pm/phoenix/overview.html\n* Docs: https://hexdocs.pm/phoenix\n* Forum: https://elixirforum.com/c/phoenix-forum\n* Source: https://github.com/phoenixframework/phoenix\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/config/config.exs.eex",
    "content": "import Config\n\n<%= if @namespaced? || @ecto || @generators do %>\nconfig :<%= @web_app_name %><%= if @namespaced? do %>,\n  namespace: <%= @web_namespace %><% end %><%= if @ecto do %>,\n  ecto_repos: [<%= @app_module %>.Repo]<% end %><%= if @generators do %>,\n  generators: <%= inspect @generators %><% end %>\n\n<% end %># Configures the endpoint\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  url: [host: \"localhost\"],\n  adapter: <%= inspect @web_adapter_module %>,\n  render_errors: [\n    formats: [<%= if @html do%>html: <%= @web_namespace %>.ErrorHTML, <% end %>json: <%= @web_namespace %>.ErrorJSON],\n    layout: false\n  ],\n  pubsub_server: <%= @app_module %>.PubSub,\n  live_view: [signing_salt: \"<%= @lv_signing_salt %>\"]<%= if @javascript do %>\n\n# Configure esbuild (the version is required)\nconfig :esbuild,\n  version: \"0.25.4\",\n  <%= @web_app_name %>: [\n    args:\n      ~w(js/app.js --bundle --target=es2022 --outdir=../priv/static/assets/js --external:/fonts/* --external:/images/* --alias:@=.),\n    cd: Path.expand(\"../apps/<%= @web_app_name %>/assets\", __DIR__),\n    env: %{\"NODE_PATH\" => [Path.expand(\"../deps\", __DIR__), Mix.Project.build_path()]}\n  ]<% end %><%= if @css do %>\n\n# Configure tailwind (the version is required)\nconfig :tailwind,\n  version: \"4.1.12\",\n  <%= @web_app_name %>: [\n    args: ~w(\n      --input=assets/css/app.css\n      --output=priv/static/assets/css/app.css\n    ),\n    cd: Path.expand(\"../apps/<%= @web_app_name %>\", __DIR__)\n  ]<% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/config/dev.exs.eex",
    "content": "import Config\n\n# For development, we disable any cache and enable\n# debugging and code reloading.\n#\n# The watchers configuration can be used to run external\n# watchers to your application. For example, we can use it\n# to bundle .js and .css sources.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,<%= if @inside_docker_env? do %>\n  # Bind to 0.0.0.0 to expose the server to the docker host machine.\n  # This makes make the service accessible from any network interface.\n  # Change to `ip: {127, 0, 0, 1}` to allow access only from the server machine.\n  http: [ip: {0, 0, 0, 0}],<% else %>\n  # Binding to loopback ipv4 address prevents access from other machines.\n  # Change to `ip: {0, 0, 0, 0}` to allow access from other machines.\n  http: [ip: {127, 0, 0, 1}],<% end %>\n  check_origin: false,\n  code_reloader: true,\n  debug_errors: true,\n  secret_key_base: \"<%= @secret_key_base_dev %>\",\n  watchers: <%= if @javascript or @css do %>[<%= if @javascript do %>\n    esbuild: {Esbuild, :install_and_run, [:<%= @web_app_name %>, ~w(--sourcemap=inline --watch)]}<%= if @css, do: \",\" %><% end %><%= if @css do %>\n    tailwind: {Tailwind, :install_and_run, [:<%= @web_app_name %>, ~w(--watch)]}<% end %>\n  ]<% else %>[]<% end %>\n\n# ## SSL Support\n#\n# In order to use HTTPS in development, a self-signed\n# certificate can be generated by running the following\n# Mix task:\n#\n#     mix phx.gen.cert\n#\n# Run `mix help phx.gen.cert` for more information.\n#\n# The `http:` config above can be replaced with:\n#\n#     https: [\n#       port: 4001,\n#       cipher_suite: :strong,\n#       keyfile: \"priv/cert/selfsigned_key.pem\",\n#       certfile: \"priv/cert/selfsigned.pem\"\n#     ],\n#\n# If desired, both `http:` and `https:` keys can be\n# configured to run both http and https servers on\n# different ports.<%= if @html do %>\n\n# Reload browser tabs when matching files change.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  live_reload: [\n    web_console_logger: true,\n    patterns: [\n      # Static assets, except user uploads\n      ~r\"priv/static/(?!uploads/).*\\.(js|css|png|jpeg|jpg|gif|svg)$\"<%= @config_regex_E %>,<%= if @gettext do %>\n      # Gettext translations\n      ~r\"priv/gettext/.*\\.po$\"<%= @config_regex_E %>,<% end %>\n      # Router, Controllers, LiveViews and LiveComponents\n      ~r\"lib/<%= @web_app_name %>/router\\.ex$\"<%= @config_regex_E %>,\n      ~r\"lib/<%= @web_app_name %>/(controllers|live|components)/.*\\.(ex|heex)$\"<%= @config_regex_E %>\n    ]\n  ]<% end %>\n\n# Enable dev routes for dashboard and mailbox\nconfig :<%= @web_app_name %>, dev_routes: true\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/config/prod.exs.eex",
    "content": "import Config\n\n# Note we also include the path to a cache manifest\n# containing the digested version of static files. This\n# manifest is generated by the `mix phx.digest` task,\n# which you should run after static files are built and\n# before starting your production server.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  url: [host: \"example.com\", port: 80],\n  cache_static_manifest: \"priv/static/cache_manifest.json\"\n\n# Force using SSL in production. This also sets the \"strict-security-transport\" header,\n# known as HSTS. If you have a health check endpoint, you may want to exclude it below.\n# Note `:force_ssl` is required to be set at compile-time.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  force_ssl: [\n    rewrite_on: [:x_forwarded_proto],\n    exclude: [\n      # paths: [\"/health\"],\n      hosts: [\"localhost\", \"127.0.0.1\"]\n    ]\n  ]\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/config/runtime.exs.eex",
    "content": "# The secret key base is used to sign/encrypt cookies and other secrets.\n# A default value is used in config/dev.exs and config/test.exs but you\n# want to use a different value for prod and you most likely don't want\n# to check this value into version control, so we use an environment\n# variable instead.\nsecret_key_base =\n  System.get_env(\"SECRET_KEY_BASE\") ||\n    raise \"\"\"\n    environment variable SECRET_KEY_BASE is missing.\n    You can generate one by calling: mix phx.gen.secret\n    \"\"\"\n\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  http: [\n    # Enable IPv6 and bind on all interfaces.\n    # Set it to  {0, 0, 0, 0, 0, 0, 0, 1} for local network only access.\n    ip: {0, 0, 0, 0, 0, 0, 0, 0}\n  ],\n  secret_key_base: secret_key_base\n\n# ## Using releases\n#\n# If you are doing OTP releases, you need to instruct Phoenix\n# to start each relevant endpoint:\n#\n#     config :<%= @web_app_name %>, <%= @endpoint_module %>, server: true\n#\n# Then you can assemble a release by calling `mix release`.\n# See `mix help release` for more information.\n\n# ## SSL Support\n#\n# To get SSL working, you will need to add the `https` key\n# to your endpoint configuration:\n#\n#     config :<%= @web_app_name %>, <%= @endpoint_module %>,\n#       https: [\n#         ...,\n#         port: 443,\n#         cipher_suite: :strong,\n#         keyfile: System.get_env(\"SOME_APP_SSL_KEY_PATH\"),\n#         certfile: System.get_env(\"SOME_APP_SSL_CERT_PATH\")\n#       ]\n#\n# The `cipher_suite` is set to `:strong` to support only the\n# latest and more secure SSL ciphers. This means old browsers\n# and clients may not be supported. You can set it to\n# `:compatible` for wider support.\n#\n# `:keyfile` and `:certfile` expect an absolute path to the key\n# and cert in disk or a relative path inside priv, for example\n# \"priv/ssl/server.key\". For all supported SSL configuration\n# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1\n#\n# We also recommend setting `force_ssl` in your config/prod.exs,\n# ensuring no data is ever sent via http, always redirecting to https:\n#\n#     config :<%= @web_app_name %>, <%= @endpoint_module %>,\n#       force_ssl: [hsts: true]\n#\n# Check `Plug.SSL` for all available options in `force_ssl`.<%= if @mailer do %>\n\n# ## Configuring the mailer\n#\n# In production you need to configure the mailer to use a different adapter.\n# Here is an example configuration for Mailgun:\n#\n#     config :<%= @app_name %>, <%= @app_module %>.Mailer,\n#       adapter: Swoosh.Adapters.Mailgun,\n#       api_key: System.get_env(\"MAILGUN_API_KEY\"),\n#       domain: System.get_env(\"MAILGUN_DOMAIN\")\n#\n# Most non-SMTP adapters require an API client. Swoosh supports Req, Hackney,\n# and Finch out-of-the-box. This configuration is typically done at\n# compile-time in your config/prod.exs:\n#\n#     config :swoosh, :api_client, Swoosh.ApiClient.Req\n#\n# See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details.<% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/config/test.exs.eex",
    "content": "import Config\n\n# We don't run a server during test. If one is required,\n# you can enable the server option below.\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  http: [ip: {127, 0, 0, 1}, port: 4002],\n  secret_key_base: \"<%= @secret_key_base_test %>\",\n  server: false\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/formatter.exs.eex",
    "content": "[\n  import_deps: [:phoenix],<%= if @html do %>\n  plugins: [Phoenix.LiveView.HTMLFormatter],<% end %>\n  inputs: [<%= if @html do %>\"*.{heex,ex,exs}\", \"{config,lib,test}/**/*.{heex,ex,exs}\"<% else %>\"*.{ex,exs}\", \"{config,lib,test}/**/*.{ex,exs}\"<% end %>]\n]\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/gitignore.eex",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Temporary files, for example, from tests.\n/tmp/\n\n# Ignore package tarball (built via \"mix hex.build\").\n<%= @web_app_name %>-*.tar\n<%= if @javascript or @css do %>\n# Ignore assets that are produced by build tools.\n/priv/static/assets/\n\n# Ignore digested assets cache.\n/priv/static/cache_manifest.json\n\n# In case you use Node.js/npm, you want to ignore these.\nnpm-debug.log\n/assets/node_modules/\n<% end %><%= if @adapter_app == :ecto_sqlite3 do %>\n# Database files\n*.db\n*.db-*\n<% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/lib/app_name/application.ex.eex",
    "content": "defmodule <%= @web_namespace %>.Application do\n  # See https://hexdocs.pm/elixir/Application.html\n  # for more information on OTP Applications\n  @moduledoc false\n\n  use Application\n\n  @impl true\n  def start(_type, _args) do\n    children = [\n      <%= @web_namespace %>.Telemetry,\n      # Start a worker by calling: <%= @web_namespace %>.Worker.start_link(arg)\n      # {<%= @web_namespace %>.Worker, arg},\n      # Start to serve requests, typically the last entry\n      <%= @endpoint_module %>\n    ]\n\n    # See https://hexdocs.pm/elixir/Supervisor.html\n    # for other strategies and supported options\n    opts = [strategy: :one_for_one, name: <%= @web_namespace %>.Supervisor]\n    Supervisor.start_link(children, opts)\n  end\n\n  # Tell Phoenix to update the endpoint configuration\n  # whenever the application is updated.\n  @impl true\n  def config_change(changed, _new, removed) do\n    <%= @endpoint_module %>.config_change(changed, removed)\n    :ok\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/lib/app_name.ex.eex",
    "content": "defmodule <%= @web_namespace %> do\n  @moduledoc \"\"\"\n  The entrypoint for defining your web interface, such\n  as controllers, components, channels, and so on.\n\n  This can be used in your application as:\n\n      use <%= @web_namespace %>, :controller\n      use <%= @web_namespace %>, :html\n\n  The definitions below will be executed for every controller,\n  component, etc, so keep them short and clean, focused\n  on imports, uses and aliases.\n\n  Do NOT define functions inside the quoted expressions\n  below. Instead, define additional modules and import\n  those modules here.\n  \"\"\"\n\n  def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)\n\n  def router do\n    quote do\n      use Phoenix.Router, helpers: false\n\n      # Import common connection and controller functions to use in pipelines\n      import Plug.Conn\n      import Phoenix.Controller<%= if @html do %>\n      import Phoenix.LiveView.Router<% end %>\n    end\n  end\n\n  def channel do\n    quote do\n      use Phoenix.Channel\n    end\n  end\n\n  def controller do\n    quote do\n      use Phoenix.Controller, formats: [:html, :json]<%= if @gettext do %>\n\n      use Gettext, backend: <%= @web_namespace %>.Gettext<% end %>\n\n      import Plug.Conn\n\n      unquote(verified_routes())\n    end\n  end<%= if @html do %>\n\n  def live_view do\n    quote do\n      use Phoenix.LiveView\n\n      unquote(html_helpers())\n    end\n  end\n\n  def live_component do\n    quote do\n      use Phoenix.LiveComponent\n\n      unquote(html_helpers())\n    end\n  end\n\n  def html do\n    quote do\n      use Phoenix.Component\n\n      # Import convenience functions from controllers\n      import Phoenix.Controller,\n        only: [get_csrf_token: 0, view_module: 1, view_template: 1]\n\n      # Include general helpers for rendering HTML\n      unquote(html_helpers())\n    end\n  end\n\n  defp html_helpers do\n    quote do<%= if @gettext do %>\n      # Translation\n      use Gettext, backend: <%= @web_namespace %>.Gettext\n<% end %>\n      # HTML escaping functionality\n      import Phoenix.HTML\n      # Core UI components\n      import <%= @web_namespace %>.CoreComponents\n\n      # Common modules used in templates\n      alias Phoenix.LiveView.JS\n      alias <%= @web_namespace %>.Layouts\n\n      # Routes generation with the ~p sigil\n      unquote(verified_routes())\n    end\n  end<% end %>\n\n  def verified_routes do\n    quote do\n      use Phoenix.VerifiedRoutes,\n        endpoint: <%= @endpoint_module %>,\n        router: <%= @web_namespace %>.Router,\n        statics: <%= @web_namespace %>.static_paths()\n    end\n  end\n\n  @doc \"\"\"\n  When used, dispatch to the appropriate controller/view/etc.\n  \"\"\"\n  defmacro __using__(which) when is_atom(which) do\n    apply(__MODULE__, which, [])\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/mix.exs.eex",
    "content": "defmodule <%= @web_namespace %>.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :<%= @web_app_name %>,\n      version: \"0.1.0\",\n      build_path: \"../../_build\",\n      config_path: \"../../config/config.exs\",\n      deps_path: \"../../deps\",\n      lockfile: \"../../mix.lock\",\n      elixir: \"~> 1.15\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      aliases: aliases(),\n      deps: deps(),<%= if @html do %>\n      compilers: [:phoenix_live_view] ++ Mix.compilers(),<% end %>\n      listeners: [Phoenix.CodeReloader]\n    ]\n  end\n\n  # Configuration for the OTP application.\n  #\n  # Type `mix help compile.app` for more information.\n  def application do\n    [\n      mod: {<%= @web_namespace %>.Application, []},\n      extra_applications: [:logger, :runtime_tools]\n    ]\n  end\n\n  # Specifies which paths to compile per environment.\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  # Specifies your project dependencies.\n  #\n  # Type `mix help deps` for examples and options.\n  defp deps do\n    [\n      <%= @phoenix_dep %>,<%= if @ecto do %>\n      {:phoenix_ecto, \"~> 4.5\"},<% end %><%= if @html do %>\n      {:phoenix_html, \"~> 4.1\"},\n      {:phoenix_live_reload, \"~> 1.2\", only: :dev},\n      {:phoenix_live_view, \"~> 1.1.0\"},\n      {:lazy_html, \">= 0.1.0\", only: :test},<% end %><%= if @dashboard do %>\n      {:phoenix_live_dashboard, \"~> 0.8.3\"},<% end %><%= if @javascript do %>\n      {:esbuild, \"~> 0.10\", runtime: Mix.env() == :dev},<% end %><%= if @css do %>\n      {:tailwind, \"~> 0.3\", runtime: Mix.env() == :dev},\n      {:heroicons,\n       github: \"tailwindlabs/heroicons\",\n       tag: \"v2.2.0\",\n       sparse: \"optimized\",\n       app: false,\n       compile: false,\n       depth: 1},<% end %>\n      {:telemetry_metrics, \"~> 1.0\"},\n      {:telemetry_poller, \"~> 1.0\"},<%= if @gettext do %>\n      {:gettext, \"~> 1.0\"},<% end %><%= if @app_name != @web_app_name do %>\n      {:<%= @app_name %>, in_umbrella: true},<% end %>\n      {:jason, \"~> 1.2\"},\n      {<%= inspect @web_adapter_app %>, \"<%= @web_adapter_vsn %>\"}\n    ]\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  defp aliases do\n    [\n      setup: [\"deps.get\"<%= if @asset_builders != [] do %>, \"assets.setup\", \"assets.build\"<% end %>]<%= if @ecto do %>,\n      test: [\"ecto.create --quiet\", \"ecto.migrate --quiet\", \"test\"]<% end %><%= if @asset_builders != [] do %>,\n      \"assets.setup\": <%= inspect Enum.map(@asset_builders, &\"#{&1}.install --if-missing\") %>,\n      \"assets.build\": <%= inspect [\"compile\" | Enum.map(@asset_builders, &\"#{&1} #{@web_app_name}\")] %>,\n      \"assets.deploy\": [\n<%= Enum.map(@asset_builders, &\"        \\\"#{&1} #{@web_app_name} --minify\\\",\\n\") ++ [\"        \\\"phx.digest\\\"\"] %>\n      ]<% end %>\n    ]\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/apps/app_name_web/test/test_helper.exs.eex",
    "content": "ExUnit.start()<%= if @ecto do %>\n<%= @adapter_config[:test_setup_all] %><% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/config.exs.eex",
    "content": "# This file is responsible for configuring your umbrella\n# and **all applications** and their dependencies with the\n# help of the Config module.\n#\n# Note that all applications in your umbrella share the\n# same configuration and dependencies, which is why they\n# all use the same configuration file. If you want different\n# configurations or dependencies per app, it is best to\n# move said applications out of the umbrella.\nimport Config\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/dev.exs.eex",
    "content": "import Config\n\n# Do not include metadata nor timestamps in development logs\nconfig :logger, :default_formatter, format: \"[$level] $message\\n\"\n\n# Initialize plugs at runtime for faster development compilation\nconfig :phoenix, :plug_init_mode, :runtime<%= if @html do %>\n\nconfig :phoenix_live_view,\n  # Include debug annotations and locations in rendered markup.\n  # Changing this configuration will require mix clean and a full recompile.\n  debug_heex_annotations: true,\n  debug_attributes: true,\n  # Enable helpful, but potentially expensive runtime checks\n  enable_expensive_runtime_checks: true<% end %><%= if @mailer do %>\n\n# Disable swoosh api client as it is only required for production adapters.\nconfig :swoosh, :api_client, false<% end %>\n\n# Set a higher stacktrace during development. Avoid configuring such\n# in production as building large stacktraces may be expensive.\nconfig :phoenix, :stacktrace_depth, 20\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/extra_config.exs.eex",
    "content": "import Config\n\n# Configure Elixir's Logger\nconfig :logger, :default_formatter,\n  format: \"$time $metadata[$level] $message\\n\",\n  metadata: [:request_id]\n\n# Use Jason for JSON parsing in Phoenix\nconfig :phoenix, :json_library, Jason\n\n# Import environment specific config. This must remain at the bottom\n# of this file so it overrides the configuration defined above.\nimport_config \"#{config_env()}.exs\"\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/prod.exs.eex",
    "content": "import Config\n\n<%= if @mailer do %>\n# Configure Swoosh API Client\nconfig :swoosh, :api_client, Swoosh.ApiClient.Req\n\n# Disable Swoosh Local Memory Storage\nconfig :swoosh, local: false<% end %>\n\n# Do not print debug messages in production\nconfig :logger, level: :info\n\n# Runtime production configuration, including reading\n# of environment variables, is done on config/runtime.exs.\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/runtime.exs.eex",
    "content": "import Config\n\n# config/runtime.exs is executed for all environments, including\n# during releases. It is executed after compilation and before the\n# system starts, so it is typically used to load production configuration\n# and secrets from environment variables or elsewhere. Do not define\n# any compile-time configuration in here, as it won't be applied.\n# The block below contains prod specific runtime configuration.\n\nconfig :<%= @web_app_name %>, <%= @endpoint_module %>,\n  http: [port: String.to_integer(System.get_env(\"PORT\", \"4000\"))]\n\nif config_env() == :prod do\n  config :<%= @app_name %>, :dns_cluster_query, System.get_env(\"DNS_CLUSTER_QUERY\")\nend\n"
  },
  {
    "path": "installer/templates/phx_umbrella/config/test.exs.eex",
    "content": "import Config\n\n# Print only warnings and errors during test\nconfig :logger, level: :warning<%= if @mailer do %>\n\n# In test we don't send emails\nconfig :<%= @app_name %>, <%= @app_module %>.Mailer,\n  adapter: Swoosh.Adapters.Test\n\n# Disable swoosh api client as it is only required for production adapters\nconfig :swoosh, :api_client, false<% end %>\n\n# Initialize plugs at runtime for faster test compilation\nconfig :phoenix, :plug_init_mode, :runtime<%= if @html do %>\n\n# Enable helpful, but potentially expensive runtime checks\nconfig :phoenix_live_view,\n  enable_expensive_runtime_checks: true<% end %>\n  \n# Sort query params output of verified routes for robust url comparisons\nconfig :phoenix,\n  sort_verified_routes_query_params: true"
  },
  {
    "path": "installer/templates/phx_umbrella/formatter.exs.eex",
    "content": "[<%= if @html do %>\n  plugins: [Phoenix.LiveView.HTMLFormatter],<% end %>\n  inputs: [\"mix.exs\", \"config/*.exs\"],\n  subdirectories: [\"apps/*\"]\n]\n"
  },
  {
    "path": "installer/templates/phx_umbrella/gitignore.eex",
    "content": "# The directory Mix will write compiled artifacts to.\n/_build/\n\n# If you run \"mix test --cover\", coverage assets end up here.\n/cover/\n\n# The directory Mix downloads your dependencies sources to.\n/deps/\n\n# Where 3rd-party dependencies like ExDoc output generated docs.\n/doc/\n\n# Ignore .fetch files in case you like to edit your project deps locally.\n/.fetch\n\n# If the VM crashes, it generates a dump, let's ignore it too.\nerl_crash.dump\n\n# Also ignore archive artifacts (built via \"mix archive.build\").\n*.ez\n\n# Temporary files, for example, from tests.\n/tmp/\n\n<%= if @adapter_app == :ecto_sqlite3 do %>\n# Database files\n*.db\n*.db-*\n<% end %>\n"
  },
  {
    "path": "installer/templates/phx_umbrella/mix.exs.eex",
    "content": "defmodule <%= @root_app_module %>.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      apps_path: \"apps\",\n      version: \"0.1.0\",\n      start_permanent: Mix.env() == :prod,\n      deps: deps(),\n      aliases: aliases(),\n      listeners: [Phoenix.CodeReloader]\n    ]\n  end\n\n  def cli do\n    [\n      preferred_envs: [precommit: :test]\n    ]\n  end\n\n  # Dependencies can be Hex packages:\n  #\n  #   {:mydep, \"~> 0.3.0\"}\n  #\n  # Or git/path repositories:\n  #\n  #   {:mydep, git: \"https://github.com/elixir-lang/mydep.git\", tag: \"0.1.0\"}\n  #\n  # Type \"mix help deps\" for more examples and options.\n  #\n  # Dependencies listed here are available only for this project\n  # and cannot be accessed from applications inside the apps/ folder.\n  defp deps do<%= if @html do %>\n    [\n      <%= if @dev or @phoenix_version.pre != [] do %><%= @phoenix_dep_umbrella_root %>,\n      <% end %># Required to run \"mix format\" on ~H/.heex files from the umbrella root\n      {:phoenix_live_view, \">= 0.0.0\"}\n    ]<% else %>\n    []<% end %>\n  end\n\n  # Aliases are shortcuts or tasks specific to the current project.\n  # For example, to install project dependencies and perform other setup tasks, run:\n  #\n  #     $ mix setup\n  #\n  # See the documentation for `Mix` for more info on aliases.\n  #\n  # Aliases listed here are available only for this project\n  # and cannot be accessed from applications inside the apps/ folder.\n  defp aliases do\n    [\n      # run `mix setup` in all child apps\n      setup: [\"cmd mix setup\"],\n      precommit: [\"compile --warnings-as-errors\", \"deps.unlock --unused\", \"format\", \"test\"]\n    ]\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_web/components/core_components.ex.eex",
    "content": "defmodule <%= @web_namespace %>.CoreComponents do\n  @moduledoc \"\"\"\n  Provides core UI components.\n\n  At first glance, this module may seem daunting, but its goal is to provide\n  core building blocks for your application, such as tables, forms, and\n  inputs. The components consist mostly of markup and are well-documented\n  with doc strings and declarative assigns. You may customize and style\n  them in any way you want, based on your application growth and needs.\n\n  The foundation for styling is Tailwind CSS, a utility-first CSS framework,\n  augmented with daisyUI, a Tailwind CSS plugin that provides UI components\n  and themes. Here are useful references:\n\n    * [daisyUI](https://daisyui.com/docs/intro/) - a good place to get\n      started and see the available components.\n\n    * [Tailwind CSS](https://tailwindcss.com) - the foundational framework\n      we build on. You will use it for layout, sizing, flexbox, grid, and\n      spacing.\n\n    * [Heroicons](https://heroicons.com) - see `icon/1` for usage.\n\n    * [Phoenix.Component](https://hexdocs.pm/phoenix_live_view/Phoenix.Component.html) -\n      the component system used by Phoenix. Some components, such as `<.link>`\n      and `<.form>`, are defined there.\n\n  \"\"\"\n  use Phoenix.Component<%= if @gettext do %>\n  use Gettext, backend: <%= @web_namespace %>.Gettext<% end %><%= if @live do %>\n\n  alias Phoenix.LiveView.JS<% end %>\n\n  @doc \"\"\"\n  Renders flash notices.\n\n  ## Examples\n\n      <.flash kind={:info} flash={@flash} />\n      <.flash kind={:info} phx-mounted={show(\"#flash\")}>Welcome Back!</.flash>\n  \"\"\"\n  attr :id, :string, doc: \"the optional id of flash container\"\n  attr :flash, :map, default: %{}, doc: \"the map of flash messages to display\"\n  attr :title, :string, default: nil\n  attr :kind, :atom, values: [:info, :error], doc: \"used for styling and flash lookup\"\n  attr :rest, :global, doc: \"the arbitrary HTML attributes to add to the flash container\"\n\n  slot :inner_block, doc: \"the optional inner block that renders the flash message\"\n\n  def flash(assigns) do\n    assigns = assign_new(assigns, :id, fn -> \"flash-#{assigns.kind}\" end)\n\n    ~H\"\"\"\n    <div\n      :if={msg = render_slot(@inner_block) || Phoenix.Flash.get(@flash, @kind)}\n      id={@id}<%= if @live and @javascript do %>\n      phx-click={JS.push(\"lv:clear-flash\", value: %{key: @kind}) |> hide(\"##{@id}\")}<% else %>\n      data-flash<% end %>\n      role=\"alert\"\n      class=\"toast toast-top toast-end z-50\"\n      {@rest}\n    >\n      <div class={[\n        \"alert w-80 sm:w-96 max-w-80 sm:max-w-96 text-wrap\",\n        @kind == :info && \"alert-info\",\n        @kind == :error && \"alert-error\"\n      ]}>\n        <.icon :if={@kind == :info} name=\"hero-information-circle\" class=\"size-5 shrink-0\" />\n        <.icon :if={@kind == :error} name=\"hero-exclamation-circle\" class=\"size-5 shrink-0\" />\n        <div>\n          <p :if={@title} class=\"font-semibold\">{@title}</p>\n          <p>{msg}</p>\n        </div>\n        <div class=\"flex-1\" />\n        <button type=\"button\" class=\"group self-start cursor-pointer\" aria-label=<%= maybe_heex_attr_gettext.(\"close\", @gettext) %>>\n          <.icon name=\"hero-x-mark\" class=\"size-5 opacity-40 group-hover:opacity-70\" />\n        </button>\n      </div>\n    </div>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a button with navigation support.\n\n  ## Examples\n\n      <.button>Send!</.button>\n      <.button phx-click=\"go\" variant=\"primary\">Send!</.button>\n      <.button navigate={~p\"/\"}>Home</.button>\n  \"\"\"\n  attr :rest, :global, include: ~w(href navigate patch method download name value disabled)\n  attr :class, :any\n  attr :variant, :string, values: ~w(primary)\n  slot :inner_block, required: true\n\n  def button(%{rest: rest} = assigns) do\n    variants = %{\"primary\" => \"btn-primary\", nil => \"btn-primary btn-soft\"}\n\n    assigns =\n      assign_new(assigns, :class, fn ->\n        [\"btn\", Map.fetch!(variants, assigns[:variant])]\n      end)\n\n    if rest[:href] || rest[:navigate] || rest[:patch] do\n      ~H\"\"\"\n      <.link class={@class} {@rest}>\n        {render_slot(@inner_block)}\n      </.link>\n      \"\"\"\n    else\n      ~H\"\"\"\n      <button class={@class} {@rest}>\n        {render_slot(@inner_block)}\n      </button>\n      \"\"\"\n    end\n  end\n\n  @doc \"\"\"\n  Renders an input with label and error messages.\n\n  A `Phoenix.HTML.FormField` may be passed as argument,\n  which is used to retrieve the input name, id, and values.\n  Otherwise all attributes may be passed explicitly.\n\n  ## Types\n\n  This function accepts all HTML input types, considering that:\n\n    * You may also set `type=\"select\"` to render a `<select>` tag\n\n    * `type=\"checkbox\"` is used exclusively to render boolean values\n\n    * For live file uploads, see `Phoenix.Component.live_file_input/1`\n\n  See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input\n  for more information. Unsupported types, such as radio, are best\n  written directly in your templates.\n\n  ## Examples\n\n  ```heex\n  <.input field={@form[:email]} type=\"email\" />\n  <.input name=\"my-input\" errors={[\"oh no!\"]} />\n  ```\n\n  ## Select type\n\n  When using `type=\"select\"`, you must pass the `options` and optionally\n  a `value` to mark which option should be preselected.\n\n  ```heex\n  <.input field={@form[:user_type]} type=\"select\" options={[\"Admin\": \"admin\", \"User\": \"user\"]} />\n  ```\n\n  For more information on what kind of data can be passed to `options` see\n  [`options_for_select`](https://hexdocs.pm/phoenix_html/Phoenix.HTML.Form.html#options_for_select/2).\n  \"\"\"\n  attr :id, :any, default: nil\n  attr :name, :any\n  attr :label, :string, default: nil\n  attr :value, :any\n\n  attr :type, :string,\n    default: \"text\",\n    values: ~w(checkbox color date datetime-local email file month number password\n               search select tel text textarea time url week hidden)\n\n  attr :field, Phoenix.HTML.FormField,\n    doc: \"a form field struct retrieved from the form, for example: @form[:email]\"\n\n  attr :errors, :list, default: []\n  attr :checked, :boolean, doc: \"the checked flag for checkbox inputs\"\n  attr :prompt, :string, default: nil, doc: \"the prompt for select inputs\"\n  attr :options, :list, doc: \"the options to pass to Phoenix.HTML.Form.options_for_select/2\"\n  attr :multiple, :boolean, default: false, doc: \"the multiple flag for select inputs\"\n  attr :class, :any, default: nil, doc: \"the input class to use over defaults\"\n  attr :error_class, :any, default: nil, doc: \"the input error class to use over defaults\"\n\n  attr :rest, :global,\n    include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength\n                multiple pattern placeholder readonly required rows size step)\n\n  def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do\n    errors = if Phoenix.Component.used_input?(field), do: field.errors, else: []\n\n    assigns\n    |> assign(field: nil, id: assigns.id || field.id)\n    |> assign(:errors, Enum.map(errors, &translate_error(&1)))\n    |> assign_new(:name, fn -> if assigns.multiple, do: field.name <> \"[]\", else: field.name end)\n    |> assign_new(:value, fn -> field.value end)\n    |> input()\n  end\n\n  def input(%{type: \"hidden\"} = assigns) do\n    ~H\"\"\"\n    <input type=\"hidden\" id={@id} name={@name} value={@value} {@rest} />\n    \"\"\"\n  end\n\n  def input(%{type: \"checkbox\"} = assigns) do\n    assigns =\n      assign_new(assigns, :checked, fn ->\n        Phoenix.HTML.Form.normalize_value(\"checkbox\", assigns[:value])\n      end)\n\n    ~H\"\"\"\n    <div class=\"fieldset mb-2\">\n      <label for={@id}>\n        <input\n          type=\"hidden\"\n          name={@name}\n          value=\"false\"\n          disabled={@rest[:disabled]}\n          form={@rest[:form]}\n        />\n        <span class=\"label\">\n          <input\n            type=\"checkbox\"\n            id={@id}\n            name={@name}\n            value=\"true\"\n            checked={@checked}\n            class={@class || \"checkbox checkbox-sm\"}\n            {@rest}\n          />{@label}\n        </span>\n      </label>\n      <.error :for={msg <- @errors}>{msg}</.error>\n    </div>\n    \"\"\"\n  end\n\n  def input(%{type: \"select\"} = assigns) do\n    ~H\"\"\"\n    <div class=\"fieldset mb-2\">\n      <label for={@id}>\n        <span :if={@label} class=\"label mb-1\">{@label}</span>\n        <select\n          id={@id}\n          name={@name}\n          class={[@class || \"w-full select\", @errors != [] && (@error_class || \"select-error\")]}\n          multiple={@multiple}\n          {@rest}\n        >\n          <option :if={@prompt} value=\"\">{@prompt}</option>\n          {Phoenix.HTML.Form.options_for_select(@options, @value)}\n        </select>\n      </label>\n      <.error :for={msg <- @errors}>{msg}</.error>\n    </div>\n    \"\"\"\n  end\n\n  def input(%{type: \"textarea\"} = assigns) do\n    ~H\"\"\"\n    <div class=\"fieldset mb-2\">\n      <label for={@id}>\n        <span :if={@label} class=\"label mb-1\">{@label}</span>\n        <textarea\n          id={@id}\n          name={@name}\n          class={[\n            @class || \"w-full textarea\",\n            @errors != [] && (@error_class || \"textarea-error\")\n          ]}\n          {@rest}\n        >{Phoenix.HTML.Form.normalize_value(\"textarea\", @value)}</textarea>\n      </label>\n      <.error :for={msg <- @errors}>{msg}</.error>\n    </div>\n    \"\"\"\n  end\n\n  # All other inputs text, datetime-local, url, password, etc. are handled here...\n  def input(assigns) do\n    ~H\"\"\"\n    <div class=\"fieldset mb-2\">\n      <label for={@id}>\n        <span :if={@label} class=\"label mb-1\">{@label}</span>\n        <input\n          type={@type}\n          name={@name}\n          id={@id}\n          value={Phoenix.HTML.Form.normalize_value(@type, @value)}\n          class={[\n            @class || \"w-full input\",\n            @errors != [] && (@error_class || \"input-error\")\n          ]}\n          {@rest}\n        />\n      </label>\n      <.error :for={msg <- @errors}>{msg}</.error>\n    </div>\n    \"\"\"\n  end\n\n  # Helper used by inputs to generate form errors\n  defp error(assigns) do\n    ~H\"\"\"\n    <p class=\"mt-1.5 flex gap-2 items-center text-sm text-error\">\n      <.icon name=\"hero-exclamation-circle\" class=\"size-5\" />\n      {render_slot(@inner_block)}\n    </p>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a header with title.\n  \"\"\"\n  slot :inner_block, required: true\n  slot :subtitle\n  slot :actions\n\n  def header(assigns) do\n    ~H\"\"\"\n    <header class={[@actions != [] && \"flex items-center justify-between gap-6\", \"pb-4\"]}>\n      <div>\n        <h1 class=\"text-lg font-semibold leading-8\">\n          {render_slot(@inner_block)}\n        </h1>\n        <p :if={@subtitle != []} class=\"text-sm text-base-content/70\">\n          {render_slot(@subtitle)}\n        </p>\n      </div>\n      <div class=\"flex-none\">{render_slot(@actions)}</div>\n    </header>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a table with generic styling.\n\n  ## Examples\n\n      <.table id=\"users\" rows={@users}>\n        <:col :let={user} label=\"id\">{user.id}</:col>\n        <:col :let={user} label=\"username\">{user.username}</:col>\n      </.table>\n  \"\"\"\n  attr :id, :string, required: true\n  attr :rows, :list, required: true\n  attr :row_id, :any, default: nil, doc: \"the function for generating the row id\"\n  attr :row_click, :any, default: nil, doc: \"the function for handling phx-click on each row\"\n\n  attr :row_item, :any,\n    default: &Function.identity/1,\n    doc: \"the function for mapping each row before calling the :col and :action slots\"\n\n  slot :col, required: true do\n    attr :label, :string\n  end\n\n  slot :action, doc: \"the slot for showing user actions in the last table column\"\n\n  def table(assigns) do\n    assigns =\n      with %{rows: %Phoenix.LiveView.LiveStream{}} <- assigns do\n        assign(assigns, row_id: assigns.row_id || fn {id, _item} -> id end)\n      end\n\n    ~H\"\"\"\n    <table class=\"table table-zebra\">\n      <thead>\n        <tr>\n          <th :for={col <- @col}>{col[:label]}</th>\n          <th :if={@action != []}>\n            <span class=\"sr-only\"><%= maybe_eex_gettext.(\"Actions\", @gettext) %></span>\n          </th>\n        </tr>\n      </thead>\n      <tbody id={@id} phx-update={is_struct(@rows, Phoenix.LiveView.LiveStream) && \"stream\"}>\n        <tr :for={row <- @rows} id={@row_id && @row_id.(row)}>\n          <td\n            :for={col <- @col}\n            phx-click={@row_click && @row_click.(row)}\n            class={@row_click && \"hover:cursor-pointer\"}\n          >\n            {render_slot(col, @row_item.(row))}\n          </td>\n          <td :if={@action != []} class=\"w-0 font-semibold\">\n            <div class=\"flex gap-4\">\n              <%%= for action <- @action do %>\n                {render_slot(action, @row_item.(row))}\n              <%% end %>\n            </div>\n          </td>\n        </tr>\n      </tbody>\n    </table>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a data list.\n\n  ## Examples\n\n      <.list>\n        <:item title=\"Title\">{@post.title}</:item>\n        <:item title=\"Views\">{@post.views}</:item>\n      </.list>\n  \"\"\"\n  slot :item, required: true do\n    attr :title, :string, required: true\n  end\n\n  def list(assigns) do\n    ~H\"\"\"\n    <ul class=\"list\">\n      <li :for={item <- @item} class=\"list-row\">\n        <div class=\"list-col-grow\">\n          <div class=\"font-bold\">{item.title}</div>\n          <div>{render_slot(item)}</div>\n        </div>\n      </li>\n    </ul>\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Renders a [Heroicon](https://heroicons.com).\n\n  Heroicons come in three styles – outline, solid, and mini.\n  By default, the outline style is used, but solid and mini may\n  be applied by using the `-solid` and `-mini` suffix.\n\n  You can customize the size and colors of the icons by setting\n  width, height, and background color classes.\n\n  Icons are extracted from the `deps/heroicons` directory and bundled within\n  your compiled app.css by the plugin in `assets/vendor/heroicons.js`.\n\n  ## Examples\n\n      <.icon name=\"hero-x-mark\" />\n      <.icon name=\"hero-arrow-path\" class=\"ml-1 size-3 motion-safe:animate-spin\" />\n  \"\"\"\n  attr :name, :string, required: true\n  attr :class, :any, default: \"size-4\"\n\n  def icon(%{name: \"hero-\" <> _} = assigns) do\n    ~H\"\"\"\n    <span class={[@name, @class]} />\n    \"\"\"\n  end<%= if @live do %>\n\n  ## JS Commands\n\n  def show(js \\\\ %JS{}, selector) do\n    JS.show(js,\n      to: selector,\n      time: 300,\n      transition:\n        {\"transition-all ease-out duration-300\",\n         \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\",\n         \"opacity-100 translate-y-0 sm:scale-100\"}\n    )\n  end\n\n  def hide(js \\\\ %JS{}, selector) do\n    JS.hide(js,\n      to: selector,\n      time: 200,\n      transition:\n        {\"transition-all ease-in duration-200\", \"opacity-100 translate-y-0 sm:scale-100\",\n         \"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95\"}\n    )\n  end<% end %>\n\n  @doc \"\"\"\n  Translates an error message using gettext.\n  \"\"\"<%= if @gettext do %>\n  def translate_error({msg, opts}) do\n    # When using gettext, we typically pass the strings we want\n    # to translate as a static argument:\n    #\n    #     # Translate the number of files with plural rules\n    #     dngettext(\"errors\", \"1 file\", \"%{count} files\", count)\n    #\n    # However the error messages in our forms and APIs are generated\n    # dynamically, so we need to translate them by calling Gettext\n    # with our gettext backend as first argument. Translations are\n    # available in the errors.po file (as we use the \"errors\" domain).\n    if count = opts[:count] do\n      Gettext.dngettext(<%= @web_namespace %>.Gettext, \"errors\", msg, msg, count, opts)\n    else\n      Gettext.dgettext(<%= @web_namespace %>.Gettext, \"errors\", msg, opts)\n    end\n  end<% else %>\n  def translate_error({msg, opts}) do\n    # You can make use of gettext to translate error messages by\n    # uncommenting and adjusting the following code:\n\n    # if count = opts[:count] do\n    #   Gettext.dngettext(<%= @web_namespace %>.Gettext, \"errors\", msg, msg, count, opts)\n    # else\n    #   Gettext.dgettext(<%= @web_namespace %>.Gettext, \"errors\", msg, opts)\n    # end\n\n    Enum.reduce(opts, msg, fn {key, value}, acc ->\n      String.replace(acc, \"%{#{key}}\", fn _ -> to_string(value) end)\n    end)\n  end<% end %>\n\n  @doc \"\"\"\n  Translates the errors for a field from a keyword list of errors.\n  \"\"\"\n  def translate_errors(errors, field) when is_list(errors) do\n    for {^field, {msg, opts}} <- errors, do: translate_error({msg, opts})\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_web/components/layouts/root.html.heex.eex",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta name=\"csrf-token\" content={get_csrf_token()} />\n    <.live_title default=\"<%= @app_module %>\" suffix=\" · Phoenix Framework\">\n      {assigns[:page_title]}\n    </.live_title>\n    <link phx-track-static rel=\"stylesheet\" href={~p\"/assets/css/app.css\"} /><%= if not @css do %>\n    <link phx-track-static rel=\"stylesheet\" href={~p\"/assets/default.css\"} /><% end %>\n    <script defer phx-track-static type=\"text/javascript\" src={~p\"/assets/js/app.js\"}>\n    </script><%= if @css do %>\n    <script>\n      (() => {\n        const setTheme = (theme) => {\n          if (theme === \"system\") {\n            localStorage.removeItem(\"phx:theme\");\n            document.documentElement.removeAttribute(\"data-theme\");\n          } else {\n            localStorage.setItem(\"phx:theme\", theme);\n            document.documentElement.setAttribute(\"data-theme\", theme);\n          }\n        };\n        if (!document.documentElement.hasAttribute(\"data-theme\")) {\n          setTheme(localStorage.getItem(\"phx:theme\") || \"system\");\n        }\n        window.addEventListener(\"storage\", (e) => e.key === \"phx:theme\" && setTheme(e.newValue || \"system\"));\n        <%= if not @live do %>\n        document.addEventListener(\"DOMContentLoaded\", () => {\n          document\n            .querySelectorAll(\"button[data-phx-theme]\")\n            .forEach((el) => {\n              el.addEventListener(\"click\", () => {\n                setTheme(el.dataset.phxTheme)\n              })\n            })\n        })<% else %>\n        window.addEventListener(\"phx:set-theme\", (e) => setTheme(e.target.dataset.phxTheme));<% end %>\n      })();\n    </script><% end %>\n  </head>\n  <body>\n    {@inner_content}\n  </body>\n</html>\n"
  },
  {
    "path": "installer/templates/phx_web/components/layouts.ex.eex",
    "content": "defmodule <%= @web_namespace %>.Layouts do\n  @moduledoc \"\"\"\n  This module holds layouts and related functionality\n  used by your application.\n  \"\"\"\n  use <%= @web_namespace %>, :html\n\n  # Embed all files in layouts/* within this module.\n  # The default root.html.heex file contains the HTML\n  # skeleton of your application, namely HTML headers\n  # and other static content.\n  embed_templates \"layouts/*\"\n\n  @doc \"\"\"\n  Renders your app layout.\n\n  This function is typically invoked from every template,\n  and it often contains your application menu, sidebar,\n  or similar.\n\n  ## Examples\n\n      <Layouts.app flash={@flash}>\n        <h1>Content</h1>\n      </Layouts.app>\n\n  \"\"\"\n  attr :flash, :map, required: true, doc: \"the map of flash messages\"\n\n  attr :current_scope, :map,\n    default: nil,\n    doc: \"the current [scope](https://hexdocs.pm/phoenix/scopes.html)\"\n\n  slot :inner_block, required: true\n\n  def app(assigns) do\n    ~H\"\"\"\n    <header class=\"navbar px-4 sm:px-6 lg:px-8\">\n      <div class=\"flex-1\">\n        <a href=\"/\" class=\"flex-1 flex w-fit items-center gap-2\">\n          <img src={~p\"/images/logo.svg\"} width=\"36\" />\n          <span class=\"text-sm font-semibold\">v{Application.spec(:phoenix, :vsn)}</span>\n        </a>\n      </div>\n      <div class=\"flex-none\">\n        <ul class=\"flex flex-column px-1 space-x-4 items-center\">\n          <li>\n            <a href=\"https://phoenixframework.org/\" class=\"btn btn-ghost\">Website</a>\n          </li>\n          <li>\n            <a href=\"https://github.com/phoenixframework/phoenix\" class=\"btn btn-ghost\">GitHub</a>\n          </li><%= if @css do %>\n          <li>\n            <.theme_toggle />\n          </li><% end %>\n          <li>\n            <a href=\"https://hexdocs.pm/phoenix/overview.html\" class=\"btn btn-primary\">\n              Get Started <span aria-hidden=\"true\">&rarr;</span>\n            </a>\n          </li>\n        </ul>\n      </div>\n    </header>\n\n    <main class=\"px-4 py-20 sm:px-6 lg:px-8\">\n      <div class=\"mx-auto max-w-2xl space-y-4\">\n        {render_slot(@inner_block)}\n      </div>\n    </main>\n\n    <.flash_group flash={@flash} />\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Shows the flash group with standard titles and content.\n\n  ## Examples\n\n      <.flash_group flash={@flash} />\n  \"\"\"\n  attr :flash, :map, required: true, doc: \"the map of flash messages\"\n  attr :id, :string, default: \"flash-group\", doc: \"the optional id of flash container\"\n\n  def flash_group(assigns) do\n    ~H\"\"\"\n    <div id={@id} aria-live=\"polite\">\n      <.flash kind={:info} flash={@flash} />\n      <.flash kind={:error} flash={@flash} /><%= if @live and @javascript do %>\n\n      <.flash\n        id=\"client-error\"\n        kind={:error}\n        title=<%= maybe_heex_attr_gettext.(\"We can't find the internet\", @gettext) %>\n        phx-disconnected={show(\".phx-client-error #client-error\") |> JS.remove_attribute(\"hidden\")}\n        phx-connected={hide(\"#client-error\") |> JS.set_attribute({\"hidden\", \"\"})}\n        hidden\n      >\n        <%= maybe_eex_gettext.(\"Attempting to reconnect\", @gettext) %>\n        <.icon name=\"hero-arrow-path\" class=\"ml-1 size-3 motion-safe:animate-spin\" />\n      </.flash>\n\n      <.flash\n        id=\"server-error\"\n        kind={:error}\n        title=<%= maybe_heex_attr_gettext.(\"Something went wrong!\", @gettext) %>\n        phx-disconnected={show(\".phx-server-error #server-error\") |> JS.remove_attribute(\"hidden\")}\n        phx-connected={hide(\"#server-error\") |> JS.set_attribute({\"hidden\", \"\"})}\n        hidden\n      >\n        <%= maybe_eex_gettext.(\"Attempting to reconnect\", @gettext) %>\n        <.icon name=\"hero-arrow-path\" class=\"ml-1 size-3 motion-safe:animate-spin\" />\n      </.flash><% end %>\n    </div>\n    \"\"\"\n  end<%= if @css do %>\n\n  @doc \"\"\"\n  Provides dark vs light theme toggle based on themes defined in app.css.\n\n  See <head> in root.html.heex which applies the theme before page load.\n  \"\"\"\n  def theme_toggle(assigns) do\n    ~H\"\"\"\n    <div class=\"card relative flex flex-row items-center border-2 border-base-300 bg-base-300 rounded-full\">\n      <div class=\"absolute w-1/3 h-full rounded-full border-1 border-base-200 bg-base-100 brightness-200 left-0 [[data-theme=light]_&]:left-1/3 [[data-theme=dark]_&]:left-2/3 transition-[left]\" />\n\n      <button\n        class=\"flex p-2 cursor-pointer w-1/3\"<%= if @live do %>\n        phx-click={JS.dispatch(\"phx:set-theme\")}<% end %>\n        data-phx-theme=\"system\"\n      >\n        <.icon name=\"hero-computer-desktop-micro\" class=\"size-4 opacity-75 hover:opacity-100\" />\n      </button>\n\n      <button\n        class=\"flex p-2 cursor-pointer w-1/3\"<%= if @live do %>\n        phx-click={JS.dispatch(\"phx:set-theme\")}<% end %>\n        data-phx-theme=\"light\"\n      >\n        <.icon name=\"hero-sun-micro\" class=\"size-4 opacity-75 hover:opacity-100\" />\n      </button>\n\n      <button\n        class=\"flex p-2 cursor-pointer w-1/3\"<%= if @live do %>\n        phx-click={JS.dispatch(\"phx:set-theme\")}<% end %>\n        data-phx-theme=\"dark\"\n      >\n        <.icon name=\"hero-moon-micro\" class=\"size-4 opacity-75 hover:opacity-100\" />\n      </button>\n    </div>\n    \"\"\"\n  end<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_web/controllers/error_html.ex.eex",
    "content": "defmodule <%= @web_namespace %>.ErrorHTML do\n  @moduledoc \"\"\"\n  This module is invoked by your endpoint in case of errors on HTML requests.\n\n  See config/config.exs.\n  \"\"\"\n  use <%= @web_namespace %>, :html\n\n  # If you want to customize your error pages,\n  # uncomment the embed_templates/1 call below\n  # and add pages to the error directory:\n  #\n  #   * lib/<%= @lib_web_name %>/controllers/error_html/404.html.heex\n  #   * lib/<%= @lib_web_name %>/controllers/error_html/500.html.heex\n  #\n  # embed_templates \"error_html/*\"\n\n  # The default is to render a plain text page based on\n  # the template name. For example, \"404.html\" becomes\n  # \"Not Found\".\n  def render(template, _assigns) do\n    Phoenix.Controller.status_message_from_template(template)\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_web/controllers/error_json.ex.eex",
    "content": "defmodule <%= @web_namespace %>.ErrorJSON do\n  @moduledoc \"\"\"\n  This module is invoked by your endpoint in case of errors on JSON requests.\n\n  See config/config.exs.\n  \"\"\"\n\n  # If you want to customize a particular status code,\n  # you may add your own clauses, such as:\n  #\n  # def render(\"500.json\", _assigns) do\n  #   %{errors: %{detail: \"Internal Server Error\"}}\n  # end\n\n  # By default, Phoenix returns the status message from\n  # the template name. For example, \"404.json\" becomes\n  # \"Not Found\".\n  def render(template, _assigns) do\n    %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}}\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_web/controllers/page_controller.ex.eex",
    "content": "defmodule <%= @web_namespace %>.PageController do\n  use <%= @web_namespace %>, :controller\n\n  def home(conn, _params) do\n    render(conn, :home)\n  end\nend\n"
  },
  {
    "path": "installer/templates/phx_web/controllers/page_html/home.html.heex.eex",
    "content": "<Layouts.flash_group flash={@flash} />\n<div class=\"left-[40rem] fixed inset-y-0 right-0 z-0 hidden lg:block xl:left-[50rem]\">\n  <svg\n    viewBox=\"0 0 1480 957\"\n    fill=\"none\"\n    aria-hidden=\"true\"\n    class=\"absolute inset-0 h-full w-full\"\n    preserveAspectRatio=\"xMinYMid slice\"\n  >\n    <path fill=\"#EE7868\" d=\"M0 0h1480v957H0z\" />\n    <path\n      d=\"M137.542 466.27c-582.851-48.41-988.806-82.127-1608.412 658.2l67.39 810 3083.15-256.51L1535.94-49.622l-98.36 8.183C1269.29 281.468 734.115 515.799 146.47 467.012l-8.928-.742Z\"\n      fill=\"#FF9F92\"\n    />\n    <path\n      d=\"M371.028 528.664C-169.369 304.988-545.754 149.198-1361.45 665.565l-182.58 792.025 3014.73 694.98 389.42-1689.25-96.18-22.171C1505.28 697.438 924.153 757.586 379.305 532.09l-8.277-3.426Z\"\n      fill=\"#FA8372\"\n    />\n    <path\n      d=\"M359.326 571.714C-104.765 215.795-428.003-32.102-1349.55 255.554l-282.3 1224.596 3047.04 722.01 312.24-1354.467C1411.25 1028.3 834.355 935.995 366.435 577.166l-7.109-5.452Z\"\n      fill=\"#E96856\"\n      fill-opacity=\".6\"\n    />\n    <path\n      d=\"M1593.87 1236.88c-352.15 92.63-885.498-145.85-1244.602-613.557l-5.455-7.105C-12.347 152.31-260.41-170.8-1225-131.458l-368.63 1599.048 3057.19 704.76 130.31-935.47Z\"\n      fill=\"#C42652\"\n      fill-opacity=\".2\"\n    />\n    <path\n      d=\"M1411.91 1526.93c-363.79 15.71-834.312-330.6-1085.883-863.909l-3.822-8.102C72.704 125.95-101.074-242.476-1052.01-408.907l-699.85 1484.267 2837.75 1338.01 326.02-886.44Z\"\n      fill=\"#A41C42\"\n      fill-opacity=\".2\"\n    />\n    <path\n      d=\"M1116.26 1863.69c-355.457-78.98-720.318-535.27-825.287-1115.521l-1.594-8.816C185.286 163.833 112.786-237.016-762.678-643.898L-1822.83 608.665 571.922 2635.55l544.338-771.86Z\"\n      fill=\"#A41C42\"\n      fill-opacity=\".2\"\n    />\n  </svg><%= if @from_elixir_install do %>\n  <div class=\"sm:py-28 xl:py-32\">\n    <div class=\"relative px-8\">\n      <div class=\"relative space-y-6 bg-black/50 px-6 pt-1 pb-6 rounded-xl\">\n        <span class=\"flex absolute h-6 w-6 -top-1 -left-2 -mt-1 -mr-1\">\n          <span class=\"animate-ping absolute inline-flex h-full w-full rounded-full bg-white\">\n          </span>\n          <span class=\"relative inline-flex rounded-full h-6 w-6 bg-white/80\"></span>\n        </span>\n        <h2 class=\"text-4xl text-white font-semibold\">\n          Finish your Elixir setup!\n        </h2>\n        <p class=\"text-lg text-gray-300\"><%= if match?({:win32, _}, :os.type()) do %>\n          Elixir has been installed to\n          <code class=\"text-[1rem] px-2 py-1 rounded-md bg-black/50\">\n            %USERPROFILE%\\.elixir-install\n          </code>\n          and activated in your current shell. <br />\n          <br /> Add to your\n          <code class=\"text-[1rem] px-2 py-1 rounded-md bg-black/50\">%PROFILE</code>\n          or similar to ensure it is always activated:<% else %>\n          Elixir has been installed to\n          <code class=\"text-[1rem] px-2 py-1 rounded-md bg-black/50\">~/.elixir-install</code>\n          and activated in your current shell. <br />\n          <br /> Add to your\n          <code class=\"text-[1rem] px-2 py-1 rounded-md bg-black/50\">~/.bashrc</code>\n          or similar to ensure it is always activated:<% end %>\n        </p>\n        <div class=\"relative text-md text-gray-300 p-2 rounded-md bg-black/50\">\n          <button\n            class=\"absolute -right-2 -top-2 p-1 bg-white/70 hover:bg-white rounded-full\"\n            onclick=\"navigator.clipboard.writeText(document.getElementById('setup-elixir').innerText)\"\n          >\n            <svg\n              xmlns=\"http://www.w3.org/2000/svg\"\n              fill=\"none\"\n              viewBox=\"0 0 24 24\"\n              stroke-width=\"1.5\"\n              stroke=\"currentColor\"\n              class=\"size-5 stroke-black/80\"\n            >\n              <path\n                stroke-linecap=\"round\"\n                stroke-linejoin=\"round\"\n                d=\"M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0c.055.194.084.4.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0c0-.212.03-.418.084-.612m7.332 0c.646.049 1.288.11 1.927.184 1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48.208 48.208 0 0 1 1.927-.184\"\n              />\n            </svg>\n          </button>\n          <%= if match?({:win32, _}, :os.type()) do %><code\n            id=\"setup-elixir\"\n            class=\"block px-1 overflow-x-auto whitespace-pre\"\n            phx-no-format\n          ># If you are using powershell, add:\n\n$env:PATH = \"$env:USERPROFILE\\<%= @elixir_install_otp_bin_path %>;$env:PATH\"\n$env:PATH = \"$env:USERPROFILE\\<%= @elixir_install_bin_path %>;$env:PATH\"\n\n# If you are using cmd, add:\n\nset PATH=%%USERPROFILE%%\\<%= @elixir_install_otp_bin_path %>;%%PATH%%\nset PATH=%%USERPROFILE%%\\<%= @elixir_install_bin_path %>;%%PATH%%</code><% else %><code\n            id=\"setup-elixir\"\n            class=\"block px-1 overflow-x-auto whitespace-pre\"\n            phx-no-format\n          >export PATH=$HOME/<%= @elixir_install_otp_bin_path %>:$PATH\nexport PATH=$HOME/<%= @elixir_install_bin_path %>:$PATH</code><% end %>\n        </div>\n      </div>\n    </div>\n  </div><% end %>\n</div>\n<div class=\"px-4 py-10 sm:px-6 sm:py-28 lg:px-8 xl:px-28 xl:py-32\">\n  <div class=\"mx-auto max-w-xl lg:mx-0\">\n    <svg viewBox=\"0 0 71 48\" class=\"h-12\" aria-hidden=\"true\">\n      <path\n        d=\"m26.371 33.477-.552-.1c-3.92-.729-6.397-3.1-7.57-6.829-.733-2.324.597-4.035 3.035-4.148 1.995-.092 3.362 1.055 4.57 2.39 1.557 1.72 2.984 3.558 4.514 5.305 2.202 2.515 4.797 4.134 8.347 3.634 3.183-.448 5.958-1.725 8.371-3.828.363-.316.761-.592 1.144-.886l-.241-.284c-2.027.63-4.093.841-6.205.735-3.195-.16-6.24-.828-8.964-2.582-2.486-1.601-4.319-3.746-5.19-6.611-.704-2.315.736-3.934 3.135-3.6.948.133 1.746.56 2.463 1.165.583.493 1.143 1.015 1.738 1.493 2.8 2.25 6.712 2.375 10.265-.068-5.842-.026-9.817-3.24-13.308-7.313-1.366-1.594-2.7-3.216-4.095-4.785-2.698-3.036-5.692-5.71-9.79-6.623C12.8-.623 7.745.14 2.893 2.361 1.926 2.804.997 3.319 0 4.149c.494 0 .763.006 1.032 0 2.446-.064 4.28 1.023 5.602 3.024.962 1.457 1.415 3.104 1.761 4.798.513 2.515.247 5.078.544 7.605.761 6.494 4.08 11.026 10.26 13.346 2.267.852 4.591 1.135 7.172.555ZM10.751 3.852c-.976.246-1.756-.148-2.56-.962 1.377-.343 2.592-.476 3.897-.528-.107.848-.607 1.306-1.336 1.49Zm32.002 37.924c-.085-.626-.62-.901-1.04-1.228-1.857-1.446-4.03-1.958-6.333-2-1.375-.026-2.735-.128-4.031-.61-.595-.22-1.26-.505-1.244-1.272.015-.78.693-1 1.31-1.184.505-.15 1.026-.247 1.6-.382-1.46-.936-2.886-1.065-4.787-.3-2.993 1.202-5.943 1.06-8.926-.017-1.684-.608-3.179-1.563-4.735-2.408l-.043.03a2.96 2.96 0 0 0 .04-.029c-.038-.117-.107-.12-.197-.054l.122.107c1.29 2.115 3.034 3.817 5.004 5.271 3.793 2.8 7.936 4.471 12.784 3.73A66.714 66.714 0 0 1 37 40.877c1.98-.16 3.866.398 5.753.899Zm-9.14-30.345c-.105-.076-.206-.266-.42-.069 1.745 2.36 3.985 4.098 6.683 5.193 4.354 1.767 8.773 2.07 13.293.51 3.51-1.21 6.033-.028 7.343 3.38.19-3.955-2.137-6.837-5.843-7.401-2.084-.318-4.01.373-5.962.94-5.434 1.575-10.485.798-15.094-2.553Zm27.085 15.425c.708.059 1.416.123 2.124.185-1.6-1.405-3.55-1.517-5.523-1.404-3.003.17-5.167 1.903-7.14 3.972-1.739 1.824-3.31 3.87-5.903 4.604.043.078.054.117.066.117.35.005.699.021 1.047.005 3.768-.17 7.317-.965 10.14-3.7.89-.86 1.685-1.817 2.544-2.71.716-.746 1.584-1.159 2.645-1.07Zm-8.753-4.67c-2.812.246-5.254 1.409-7.548 2.943-1.766 1.18-3.654 1.738-5.776 1.37-.374-.066-.75-.114-1.124-.17l-.013.156c.135.07.265.151.405.207.354.14.702.308 1.07.395 4.083.971 7.992.474 11.516-1.803 2.221-1.435 4.521-1.707 7.013-1.336.252.038.503.083.756.107.234.022.479.255.795.003-2.179-1.574-4.526-2.096-7.094-1.872Zm-10.049-9.544c1.475.051 2.943-.142 4.486-1.059-.452.04-.643.04-.827.076-2.126.424-4.033-.04-5.733-1.383-.623-.493-1.257-.974-1.889-1.457-2.503-1.914-5.374-2.555-8.514-2.5.05.154.054.26.108.315 3.417 3.455 7.371 5.836 12.369 6.008Zm24.727 17.731c-2.114-2.097-4.952-2.367-7.578-.537 1.738.078 3.043.632 4.101 1.728.374.388.763.768 1.182 1.106 1.6 1.29 4.311 1.352 5.896.155-1.861-.726-1.861-.726-3.601-2.452Zm-21.058 16.06c-1.858-3.46-4.981-4.24-8.59-4.008a9.667 9.667 0 0 1 2.977 1.39c.84.586 1.547 1.311 2.243 2.055 1.38 1.473 3.534 2.376 4.962 2.07-.656-.412-1.238-.848-1.592-1.507Zm17.29-19.32c0-.023.001-.045.003-.068l-.006.006.006-.006-.036-.004.021.018.012.053Zm-20 14.744a7.61 7.61 0 0 0-.072-.041.127.127 0 0 0 .015.043c.005.008.038 0 .058-.002Zm-.072-.041-.008-.034-.008.01.008-.01-.022-.006.005.026.024.014Z\"\n        fill=\"#FD4F00\"\n      />\n    </svg>\n    <div class=\"mt-10 flex justify-between items-center\">\n      <h1 class=\"flex items-center text-sm font-semibold leading-6\">\n        Phoenix Framework\n        <small class=\"badge badge-warning badge-sm ml-3\">\n          v{Application.spec(:phoenix, :vsn)}\n        </small>\n      </h1><%= if @css do %>\n      <Layouts.theme_toggle /><% end %>\n    </div>\n\n    <p class=\"text-[2rem] mt-4 font-semibold leading-10 tracking-tighter text-balance\">\n      Peace of mind from prototype to production.\n    </p>\n    <p class=\"mt-4 leading-7 text-base-content/70\">\n      Build rich, interactive web applications quickly, with less code and fewer moving parts. Join our growing community of developers using Phoenix to craft APIs, HTML5 apps and more, for fun or at scale.\n    </p>\n    <div class=\"flex\">\n      <div class=\"w-full sm:w-auto\">\n        <div class=\"mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-3\">\n          <a\n            href=\"https://hexdocs.pm/phoenix/overview.html\"\n            class=\"group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6\"\n          >\n            <span class=\"absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105\">\n            </span>\n            <span class=\"relative flex items-center gap-4 sm:flex-col\">\n              <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" class=\"h-6 w-6\">\n                <path d=\"m12 4 10-2v18l-10 2V4Z\" fill=\"currentColor\" fill-opacity=\".15\" />\n                <path\n                  d=\"M12 4 2 2v18l10 2m0-18v18m0-18 10-2v18l-10 2\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n              </svg>\n              Guides &amp; Docs\n            </span>\n          </a>\n          <a\n            href=\"https://github.com/phoenixframework/phoenix\"\n            class=\"group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6\"\n          >\n            <span class=\"absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105\">\n            </span>\n            <span class=\"relative flex items-center gap-4 sm:flex-col\">\n              <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\" class=\"h-6 w-6\">\n                <path\n                  fill=\"currentColor\"\n                  fill-rule=\"evenodd\"\n                  clip-rule=\"evenodd\"\n                  d=\"M12 0C5.37 0 0 5.506 0 12.303c0 5.445 3.435 10.043 8.205 11.674.6.107.825-.262.825-.585 0-.292-.015-1.261-.015-2.291C6 21.67 5.22 20.346 4.98 19.654c-.135-.354-.72-1.446-1.23-1.738-.42-.23-1.02-.8-.015-.815.945-.015 1.62.892 1.845 1.261 1.08 1.86 2.805 1.338 3.495 1.015.105-.8.42-1.338.765-1.645-2.67-.308-5.46-1.37-5.46-6.075 0-1.338.465-2.446 1.23-3.307-.12-.308-.54-1.569.12-3.26 0 0 1.005-.323 3.3 1.26.96-.276 1.98-.415 3-.415s2.04.139 3 .416c2.295-1.6 3.3-1.261 3.3-1.261.66 1.691.24 2.952.12 3.26.765.861 1.23 1.953 1.23 3.307 0 4.721-2.805 5.767-5.475 6.075.435.384.81 1.122.81 2.276 0 1.645-.015 2.968-.015 3.383 0 .323.225.707.825.585a12.047 12.047 0 0 0 5.919-4.489A12.536 12.536 0 0 0 24 12.304C24 5.505 18.63 0 12 0Z\"\n                />\n              </svg>\n              Source Code\n            </span>\n          </a>\n          <a\n            href={\"https://github.com/phoenixframework/phoenix/blob/v#{Application.spec(:phoenix, :vsn)}/CHANGELOG.md\"}\n            class=\"group relative rounded-box px-6 py-4 text-sm font-semibold leading-6 sm:py-6\"\n          >\n            <span class=\"absolute inset-0 rounded-box bg-base-200 transition group-hover:bg-base-300 sm:group-hover:scale-105\">\n            </span>\n            <span class=\"relative flex items-center gap-4 sm:flex-col\">\n              <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\" class=\"h-6 w-6\">\n                <path\n                  d=\"M12 1v6M12 17v6\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n                <circle\n                  cx=\"12\"\n                  cy=\"12\"\n                  r=\"4\"\n                  fill=\"currentColor\"\n                  fill-opacity=\".15\"\n                  stroke=\"currentColor\"\n                  stroke-width=\"2\"\n                  stroke-linecap=\"round\"\n                  stroke-linejoin=\"round\"\n                />\n              </svg>\n              Changelog\n            </span>\n          </a>\n        </div>\n        <div class=\"mt-10 grid grid-cols-1 gap-y-4 text-sm leading-6 text-base-content/80 sm:grid-cols-2\">\n          <div>\n            <a\n              href=\"https://elixirforum.com\"\n              class=\"group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content\"\n            >\n              <svg\n                viewBox=\"0 0 16 16\"\n                aria-hidden=\"true\"\n                class=\"h-4 w-4 fill-base-content/40 group-hover:fill-base-content\"\n              >\n                <path d=\"M8 13.833c3.866 0 7-2.873 7-6.416C15 3.873 11.866 1 8 1S1 3.873 1 7.417c0 1.081.292 2.1.808 2.995.606 1.05.806 2.399.086 3.375l-.208.283c-.285.386-.01.905.465.85.852-.098 2.048-.318 3.137-.81a3.717 3.717 0 0 1 1.91-.318c.263.027.53.041.802.041Z\" />\n              </svg>\n              Discuss on the Elixir Forum\n            </a>\n          </div>\n          <div>\n            <a\n              href=\"https://discord.gg/elixir\"\n              class=\"group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content\"\n            >\n              <svg\n                viewBox=\"0 0 16 16\"\n                aria-hidden=\"true\"\n                class=\"h-4 w-4 fill-base-content/40 group-hover:fill-base-content\"\n              >\n                <path d=\"M13.545 2.995c-1.02-.46-2.114-.8-3.257-.994a.05.05 0 0 0-.052.024c-.141.246-.297.567-.406.82a12.377 12.377 0 0 0-3.658 0 8.238 8.238 0 0 0-.412-.82.052.052 0 0 0-.052-.024 13.315 13.315 0 0 0-3.257.994.046.046 0 0 0-.021.018C.356 6.063-.213 9.036.066 11.973c.001.015.01.029.02.038a13.353 13.353 0 0 0 3.996 1.987.052.052 0 0 0 .056-.018c.308-.414.582-.85.818-1.309a.05.05 0 0 0-.028-.069 8.808 8.808 0 0 1-1.248-.585.05.05 0 0 1-.005-.084c.084-.062.168-.126.248-.191a.05.05 0 0 1 .051-.007c2.619 1.176 5.454 1.176 8.041 0a.05.05 0 0 1 .053.006c.08.065.164.13.248.192a.05.05 0 0 1-.004.084c-.399.23-.813.423-1.249.585a.05.05 0 0 0-.027.07c.24.457.514.893.817 1.307a.051.051 0 0 0 .056.019 13.31 13.31 0 0 0 4.001-1.987.05.05 0 0 0 .021-.037c.334-3.396-.559-6.345-2.365-8.96a.04.04 0 0 0-.021-.02Zm-8.198 7.19c-.789 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.637 1.587-1.438 1.587Zm5.316 0c-.788 0-1.438-.712-1.438-1.587 0-.874.637-1.586 1.438-1.586.807 0 1.45.718 1.438 1.586 0 .875-.63 1.587-1.438 1.587Z\" />\n              </svg>\n              Join our Discord server\n            </a>\n          </div>\n          <div>\n            <a\n              href=\"https://elixir-slack.community/\"\n              class=\"group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content\"\n            >\n              <svg\n                viewBox=\"0 0 16 16\"\n                aria-hidden=\"true\"\n                class=\"h-4 w-4 fill-base-content/40 group-hover:fill-base-content\"\n              >\n                <path d=\"M3.361 10.11a1.68 1.68 0 1 1-1.68-1.681h1.68v1.682ZM4.209 10.11a1.68 1.68 0 1 1 3.361 0v4.21a1.68 1.68 0 1 1-3.361 0v-4.21ZM5.89 3.361a1.68 1.68 0 1 1 1.681-1.68v1.68H5.89ZM5.89 4.209a1.68 1.68 0 1 1 0 3.361H1.68a1.68 1.68 0 1 1 0-3.361h4.21ZM12.639 5.89a1.68 1.68 0 1 1 1.68 1.681h-1.68V5.89ZM11.791 5.89a1.68 1.68 0 1 1-3.361 0V1.68a1.68 1.68 0 0 1 3.361 0v4.21ZM10.11 12.639a1.68 1.68 0 1 1-1.681 1.68v-1.68h1.682ZM10.11 11.791a1.68 1.68 0 1 1 0-3.361h4.21a1.68 1.68 0 1 1 0 3.361h-4.21Z\" />\n              </svg>\n              Join us on Slack\n            </a>\n          </div>\n          <div>\n            <a\n              href=\"https://fly.io/docs/elixir/getting-started/\"\n              class=\"group -mx-2 -my-0.5 inline-flex items-center gap-3 rounded-lg px-2 py-0.5 hover:bg-base-200 hover:text-base-content\"\n            >\n              <svg\n                viewBox=\"0 0 20 20\"\n                aria-hidden=\"true\"\n                class=\"h-4 w-4 fill-base-content/40 group-hover:fill-base-content\"\n              >\n                <path d=\"M1 12.5A4.5 4.5 0 005.5 17H15a4 4 0 001.866-7.539 3.504 3.504 0 00-4.504-4.272A4.5 4.5 0 004.06 8.235 4.502 4.502 0 001 12.5z\" />\n              </svg>\n              Deploy your application\n            </a>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "installer/templates/phx_web/controllers/page_html.ex.eex",
    "content": "defmodule <%= @web_namespace %>.PageHTML do\n  @moduledoc \"\"\"\n  This module contains pages rendered by PageController.\n\n  See the `page_html` directory for all templates available.\n  \"\"\"\n  use <%= @web_namespace %>, :html\n\n  embed_templates \"page_html/*\"\nend\n"
  },
  {
    "path": "installer/templates/phx_web/endpoint.ex.eex",
    "content": "defmodule <%= @endpoint_module %> do\n  use Phoenix.Endpoint, otp_app: :<%= @web_app_name %>\n\n  # The session will be stored in the cookie and signed,\n  # this means its contents can be read but not tampered with.\n  # Set :encryption_salt if you would also like to encrypt it.\n  @session_options [\n    store: :cookie,\n    key: \"_<%= @web_app_name %>_key\",\n    signing_salt: \"<%= @signing_salt %>\",\n    same_site: \"Lax\"\n  ]\n\n  <%= if !(@dashboard || @live) do %><%= \"# \" %><% end %>socket \"/live\", Phoenix.LiveView.Socket,\n  <%= if !(@dashboard || @live) do %><%= \"# \" %><% end %>  websocket: [connect_info: [session: @session_options]],\n  <%= if !(@dashboard || @live) do %><%= \"# \" %><% end %>  longpoll: [connect_info: [session: @session_options]]\n\n  # Serve at \"/\" the static files from \"priv/static\" directory.\n  #\n  # When code reloading is disabled (e.g., in production),\n  # the `gzip` option is enabled to serve compressed\n  # static files generated by running `phx.digest`.\n  plug Plug.Static,\n    at: \"/\",\n    from: :<%= @web_app_name %>,\n    gzip: not code_reloading?,\n    only: <%= @web_namespace %>.static_paths(),\n    raise_on_missing_only: code_reloading?\n\n  # Code reloading can be explicitly enabled under the\n  # :code_reloader configuration of your endpoint.\n  if code_reloading? do<%= if @html do %>\n    socket \"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket\n    plug Phoenix.LiveReloader<% end %>\n    plug Phoenix.CodeReloader<%= if @ecto do %>\n    plug Phoenix.Ecto.CheckRepoStatus, otp_app: :<%= @web_app_name %><% end %>\n  end<%= if @dashboard do %>\n\n  plug Phoenix.LiveDashboard.RequestLogger,\n    param_key: \"request_logger\",\n    cookie_key: \"request_logger\"<% end %>\n\n  plug Plug.RequestId\n  plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]\n\n  plug Plug.Parsers,\n    parsers: [:urlencoded, :multipart, :json],\n    pass: [\"*/*\"],\n    json_decoder: Phoenix.json_library()\n\n  plug Plug.MethodOverride\n  plug Plug.Head\n  plug Plug.Session, @session_options\n  plug <%= @web_namespace %>.Router\nend\n"
  },
  {
    "path": "installer/templates/phx_web/router.ex.eex",
    "content": "defmodule <%= @web_namespace %>.Router do\n  use <%= @web_namespace %>, :router<%= if @html do %>\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :fetch_live_flash\n    plug :put_root_layout, html: {<%= @web_namespace %>.Layouts, :root}\n    plug :protect_from_forgery\n    plug :put_secure_browser_headers\n  end<% end %>\n\n  pipeline :api do\n    plug :accepts, [\"json\"]\n  end<%= if @html do %>\n\n  scope \"/\", <%= @web_namespace %> do\n    pipe_through :browser\n\n    get \"/\", PageController, :home\n  end\n\n  # Other scopes may use custom stacks.\n  # scope \"/api\", <%= @web_namespace %> do\n  #   pipe_through :api\n  # end<% else %>\n\n  scope \"/api\", <%= @web_namespace %> do\n    pipe_through :api\n  end<% end %><%= if @dashboard || @mailer do %>\n\n  # Enable <%= [@dashboard && \"LiveDashboard\", @mailer && \"Swoosh mailbox preview\"] |> Enum.filter(&(&1)) |> Enum.join(\" and \") %> in development\n  if Application.compile_env(:<%= @web_app_name %>, :dev_routes) do<%= if @dashboard do %>\n    # If you want to use the LiveDashboard in production, you should put\n    # it behind authentication and allow only admins to access it.\n    # If your application does not have an admins-only section yet,\n    # you can use Plug.BasicAuth to set up some basic authentication\n    # as long as you are also using SSL (which you should anyway).\n    import Phoenix.LiveDashboard.Router<% end %>\n\n    scope \"/dev\" do<%= if @html do %>\n      pipe_through :browser<% else %>\n      pipe_through [:fetch_session, :protect_from_forgery]<% end %>\n<%= if @dashboard do %>\n      live_dashboard \"/dashboard\", metrics: <%= @web_namespace %>.Telemetry<% end %><%= if @mailer do %>\n      forward \"/mailbox\", Plug.Swoosh.MailboxPreview<% end %>\n    end\n  end<% end %>\nend\n"
  },
  {
    "path": "installer/templates/phx_web/telemetry.ex.eex",
    "content": "defmodule <%= @web_namespace %>.Telemetry do\n  use Supervisor\n  import Telemetry.Metrics\n\n  def start_link(arg) do\n    Supervisor.start_link(__MODULE__, arg, name: __MODULE__)\n  end\n\n  @impl true\n  def init(_arg) do\n    children = [\n      # Telemetry poller will execute the given period measurements\n      # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics\n      {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}\n      # Add reporters as children of your supervision tree.\n      # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}\n    ]\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  def metrics do\n    [\n      # Phoenix Metrics\n      summary(\"phoenix.endpoint.start.system_time\",\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.endpoint.stop.duration\",\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.router_dispatch.start.system_time\",\n        tags: [:route],\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.router_dispatch.exception.duration\",\n        tags: [:route],\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.router_dispatch.stop.duration\",\n        tags: [:route],\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.socket_connected.duration\",\n        unit: {:native, :millisecond}\n      ),\n      sum(\"phoenix.socket_drain.count\"),\n      summary(\"phoenix.channel_joined.duration\",\n        unit: {:native, :millisecond}\n      ),\n      summary(\"phoenix.channel_handled_in.duration\",\n        tags: [:event],\n        unit: {:native, :millisecond}\n      ),<%= if @ecto do %>\n\n      # Database Metrics\n      summary(\"<%= @app_name %>.repo.query.total_time\",\n        unit: {:native, :millisecond},\n        description: \"The sum of the other measurements\"\n      ),\n      summary(\"<%= @app_name %>.repo.query.decode_time\",\n        unit: {:native, :millisecond},\n        description: \"The time spent decoding the data received from the database\"\n      ),\n      summary(\"<%= @app_name %>.repo.query.query_time\",\n        unit: {:native, :millisecond},\n        description: \"The time spent executing the query\"\n      ),\n      summary(\"<%= @app_name %>.repo.query.queue_time\",\n        unit: {:native, :millisecond},\n        description: \"The time spent waiting for a database connection\"\n      ),\n      summary(\"<%= @app_name %>.repo.query.idle_time\",\n        unit: {:native, :millisecond},\n        description:\n          \"The time the connection spent waiting before being checked out for the query\"\n      ),<% end %>\n\n      # VM Metrics\n      summary(\"vm.memory.total\", unit: {:byte, :kilobyte}),\n      summary(\"vm.total_run_queue_lengths.total\"),\n      summary(\"vm.total_run_queue_lengths.cpu\"),\n      summary(\"vm.total_run_queue_lengths.io\")\n    ]\n  end\n\n  defp periodic_measurements do\n    [\n      # A module, function and arguments to be invoked periodically.\n      # This function must call :telemetry.execute/3 and a metric must be added above.\n      # {<%= @web_namespace %>, :count_users, []}\n    ]\n  end\nend\n"
  },
  {
    "path": "installer/templates/usage-rules/assets.md",
    "content": "### JS and CSS guidelines\n\n- **Use Tailwind CSS classes and custom CSS rules** to create polished, responsive, and visually stunning interfaces.\n- Tailwindcss v4 **no longer needs a tailwind.config.js** and uses a new import syntax in `app.css`:\n\n      @import \"tailwindcss\" source(none);\n      @source \"../css\";\n      @source \"../js\";\n      @source \"../../lib/my_app_web\";\n\n- **Always use and maintain this import syntax** in the app.css file for projects generated with `phx.new`\n- **Never** use `@apply` when writing raw css\n- **Always** manually write your own tailwind-based components instead of using daisyUI for a unique, world-class design\n- Out of the box **only the app.js and app.css bundles are supported**\n  - You cannot reference an external vendor'd script `src` or link `href` in the layouts\n  - You must import the vendor deps into app.js and app.css to use them\n  - **Never write inline <script>custom js</script> tags within templates**\n\n### UI/UX & design guidelines\n\n- **Produce world-class UI designs** with a focus on usability, aesthetics, and modern design principles\n- Implement **subtle micro-interactions** (e.g., button hover effects, and smooth transitions)\n- Ensure **clean typography, spacing, and layout balance** for a refined, premium look\n- Focus on **delightful details** like hover effects, loading states, and smooth page transitions\n"
  },
  {
    "path": "installer/templates/usage-rules/phoenix.md",
    "content": "### Phoenix v1.8 guidelines\n\n- **Always** begin your LiveView templates with `<Layouts.app flash={@flash} ...>` which wraps all inner content\n- The `MyAppWeb.Layouts` module is aliased in the `my_app_web.ex` file, so you can use it without needing to alias it again\n- Anytime you run into errors with no `current_scope` assign:\n  - You failed to follow the Authenticated Routes guidelines, or you failed to pass `current_scope` to `<Layouts.app>`\n  - **Always** fix the `current_scope` error by moving your routes to the proper `live_session` and ensure you pass `current_scope` as needed\n- Phoenix v1.8 moved the `<.flash_group>` component to the `Layouts` module. You are **forbidden** from calling `<.flash_group>` outside of the `layouts.ex` module\n- Out of the box, `core_components.ex` imports an `<.icon name=\"hero-x-mark\" class=\"w-5 h-5\"/>` component for hero icons. **Always** use the `<.icon>` component for icons, **never** use `Heroicons` modules or similar\n- **Always** use the imported `<.input>` component for form inputs from `core_components.ex` when available. `<.input>` is imported and using it will save steps and prevent errors\n- If you override the default input classes (`<.input class=\"myclass px-2 py-1 rounded-lg\">)`) class with your own values, no default classes are inherited, so your\ncustom classes must fully style the input\n"
  },
  {
    "path": "installer/templates/usage-rules/project.md",
    "content": "This is a web application written using the Phoenix web framework.\n\n## Project guidelines\n\n- Use `mix precommit` alias when you are done with all changes and fix any pending issues\n- Use the already included and available `:req` (`Req`) library for HTTP requests, **avoid** `:httpoison`, `:tesla`, and `:httpc`. Req is included by default and is the preferred HTTP client for Phoenix apps\n"
  },
  {
    "path": "installer/test/mix_helper.exs",
    "content": "# Get Mix output sent to the current\n# process to avoid polluting tests.\nMix.shell(Mix.Shell.Process)\n\ndefmodule MixHelper do\n  import ExUnit.Assertions\n  import ExUnit.CaptureIO\n\n  def tmp_path do\n    Path.expand(\"../../tmp\", __DIR__)\n  end\n\n  defp random_string(len) do\n    len |> :crypto.strong_rand_bytes() |> Base.url_encode64() |> binary_part(0, len)\n  end\n\n  def in_tmp(which, function) do\n    base = Path.join([tmp_path(), random_string(10)])\n    path = Path.join([base, to_string(which)])\n\n    try do\n      File.rm_rf!(path)\n      File.mkdir_p!(path)\n      File.cd!(path, function)\n    after\n      File.rm_rf!(base)\n    end\n  end\n\n  def in_tmp_project(which, function) do\n    conf_before = Application.get_env(:phoenix, :generators) || []\n    base = Path.join([tmp_path(), random_string(10)])\n    path = Path.join([base, to_string(which)])\n\n    try do\n      File.rm_rf!(path)\n      File.mkdir_p!(path)\n\n      File.cd!(path, fn ->\n        File.touch!(\"mix.exs\")\n\n        File.write!(\".formatter.exs\", \"\"\"\n        [\n          import_deps: [:phoenix, :ecto, :ecto_sql],\n          inputs: [\"*.exs\"]\n        ]\n        \"\"\")\n\n        function.()\n      end)\n    after\n      File.rm_rf!(base)\n      Application.put_env(:phoenix, :generators, conf_before)\n    end\n  end\n\n  def in_tmp_umbrella_project(which, function) do\n    conf_before = Application.get_env(:phoenix, :generators) || []\n    base = Path.join([tmp_path(), random_string(10)])\n    path = Path.join([base, to_string(which)])\n\n    try do\n      apps_path = Path.join(path, \"apps\")\n      config_path = Path.join(path, \"config\")\n      File.rm_rf!(path)\n      File.mkdir_p!(path)\n      File.mkdir_p!(apps_path)\n      File.mkdir_p!(config_path)\n      File.touch!(Path.join(path, \"mix.exs\"))\n\n      for file <- ~w(config.exs dev.exs test.exs prod.exs) do\n        File.write!(Path.join(config_path, file), \"import Config\\n\")\n      end\n\n      File.cd!(apps_path, function)\n    after\n      Application.put_env(:phoenix, :generators, conf_before)\n      File.rm_rf!(base)\n    end\n  end\n\n  def in_project(app, path, fun) do\n    %{name: name, file: file} = Mix.Project.pop()\n\n    try do\n      capture_io(:stderr, fn ->\n        Mix.Project.in_project(app, path, [prune_code_paths: false], fn mod ->\n          fun.(mod)\n          Mix.Project.clear_deps_cache()\n        end)\n      end)\n    after\n      Mix.Project.push(name, file)\n    end\n  end\n\n  def assert_file(file) do\n    assert File.regular?(file), \"Expected #{file} to exist, but does not\"\n  end\n\n  def refute_file(file) do\n    refute File.regular?(file), \"Expected #{file} to not exist, but it does\"\n  end\n\n  def assert_file(file, match) do\n    cond do\n      is_list(match) ->\n        assert_file(file, &Enum.each(match, fn m -> assert &1 =~ m end))\n\n      is_binary(match) or is_struct(match, Regex) ->\n        assert_file(file, &assert(&1 =~ match))\n\n      is_function(match, 1) ->\n        assert_file(file)\n        match.(File.read!(file))\n\n      true ->\n        raise inspect({file, match})\n    end\n  end\n\n  def modify_file(path, function) when is_binary(path) and is_function(function, 1) do\n    path\n    |> File.read!()\n    |> function.()\n    |> write_file!(path)\n  end\n\n  defp write_file!(content, path) do\n    File.write!(path, content)\n  end\n\n  def with_generator_env(app_name \\\\ :phoenix, new_env, fun) do\n    config_before = Application.fetch_env(app_name, :generators)\n    Application.put_env(app_name, :generators, new_env)\n\n    try do\n      fun.()\n    after\n      case config_before do\n        {:ok, config} -> Application.put_env(app_name, :generators, config)\n        :error -> Application.delete_env(app_name, :generators)\n      end\n    end\n  end\n\n  def with_scope_env(app_name, new_env, fun) do\n    config_before = Application.fetch_env(app_name, :scopes)\n    Application.put_env(app_name, :scopes, new_env)\n\n    try do\n      fun.()\n    after\n      case config_before do\n        {:ok, config} -> Application.put_env(app_name, :scopes, config)\n        :error -> Application.delete_env(app_name, :scopes)\n      end\n    end\n  end\n\n  def umbrella_mixfile_contents do\n    \"\"\"\n    defmodule Umbrella.MixProject do\n      use Mix.Project\n\n      def project do\n        [\n          apps_path: \"apps\",\n          deps: deps()\n        ]\n      end\n\n      defp deps do\n        []\n      end\n    end\n    \"\"\"\n  end\n\n  def flush do\n    receive do\n      _ -> flush()\n    after\n      0 -> :ok\n    end\n  end\nend\n"
  },
  {
    "path": "installer/test/phx_new_ecto_test.exs",
    "content": "Code.require_file \"mix_helper.exs\", __DIR__\n\ndefmodule Mix.Tasks.Phx.New.EctoTest do\n  use ExUnit.Case\n  import MixHelper\n  import ExUnit.CaptureIO\n\n  setup do\n    # The shell asks to install deps.\n    # We will politely say not.\n    send self(), {:mix_shell_input, :yes?, false}\n    :ok\n  end\n\n  @app_name \"phx_ecto\"\n\n  test \"new without args\" do\n    assert capture_io(fn -> Mix.Tasks.Phx.New.Ecto.run([]) end) =~\n           \"Creates a new Ecto project within an umbrella project.\"\n  end\n\n  test \"new with barebones umbrella\" do\n    in_tmp_umbrella_project \"new with barebones umbrella\", fn ->\n      files = ~w[../config/dev.exs ../config/test.exs ../config/prod.exs ../config/runtime.exs]\n      Enum.each(files, &File.rm/1)\n\n      assert_file \"../config/config.exs\", &refute(&1 =~ ~S[import_config \"#{config_env()}.exs\"])\n      Mix.Tasks.Phx.New.Ecto.run([@app_name])\n      assert_file \"../config/config.exs\", &assert(&1 =~ ~S[import_config \"#{config_env()}.exs\"])\n    end\n  end\n\n  test \"new outside umbrella\", config do\n    in_tmp config.test, fn ->\n      assert_raise Mix.Error, ~r\"The ecto task can only be run within an umbrella's apps directory\", fn ->\n        Mix.Tasks.Phx.New.Ecto.run [\"007invalid\"]\n      end\n    end\n  end\n\n  test \"new with defaults\", config do\n    in_tmp_umbrella_project config.test, fn ->\n      Mix.Tasks.Phx.New.Ecto.run([@app_name])\n\n      # Install dependencies?\n      assert_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n      # Instructions\n      assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n      assert msg =~ \"$ cd phx_ecto\"\n      assert msg =~ \"$ mix deps.get\"\n\n      assert_received {:mix_shell, :info, [\"Then configure your database in config/dev.exs\" <> _]}\n    end\n  end\nend\n"
  },
  {
    "path": "installer/test/phx_new_test.exs",
    "content": "Code.require_file(\"mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.NewTest do\n  use ExUnit.Case, async: false\n  import MixHelper\n  import ExUnit.CaptureIO\n\n  @app_name \"phx_blog\"\n\n  setup do\n    # The shell asks to install deps.\n    # We will politely say not.\n    send(self(), {:mix_shell_input, :yes?, false})\n    :ok\n  end\n\n  test \"assets are in sync with priv\" do\n    for file <- ~w(favicon.ico phoenix.png) do\n      assert File.read!(\"../priv/static/#{file}\") ==\n               File.read!(\"templates/phx_static/#{file}\")\n    end\n  end\n\n  test \"returns the version\" do\n    Mix.Tasks.Phx.New.run([\"-v\"])\n    assert_received {:mix_shell, :info, [\"Phoenix installer v\" <> _]}\n  end\n\n  test \"new with defaults\" do\n    in_tmp(\"new with defaults\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name])\n\n      assert_file(\"phx_blog/README.md\")\n\n      assert_file(\"phx_blog/AGENTS.md\", fn file ->\n        assert file =~ \"### UI/UX & design guidelines\"\n      end)\n\n      assert_file(\"phx_blog/.formatter.exs\", fn file ->\n        assert file =~ \"import_deps: [:ecto, :ecto_sql, :phoenix]\"\n        assert file =~ \"subdirectories: [\\\"priv/*/migrations\\\"]\"\n        assert file =~ \"plugins: [Phoenix.LiveView.HTMLFormatter]\"\n\n        assert file =~\n                 \"inputs: [\\\"*.{heex,ex,exs}\\\", \\\"{config,lib,test}/**/*.{heex,ex,exs}\\\", \\\"priv/*/seeds.exs\\\"]\"\n      end)\n\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ \"app: :phx_blog\"\n        refute file =~ \"deps_path: \\\"../../deps\\\"\"\n        refute file =~ \"lockfile: \\\"../../mix.lock\\\"\"\n      end)\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        assert file =~ \"ecto_repos: [PhxBlog.Repo]\"\n        assert file =~ \"generators: [timestamp_type: :utc_datetime]\"\n        assert file =~ \"config :phoenix, :json_library, Jason\"\n        assert file =~ ~s[cd: Path.expand(\"../assets\", __DIR__),]\n        refute file =~ \"namespace: PhxBlog\"\n        refute file =~ \"config :phx_blog, :generators\"\n      end)\n\n      assert_file(\"phx_blog/config/prod.exs\", fn file ->\n        assert file =~ \"config :logger, level: :info\"\n      end)\n\n      assert_file(\"phx_blog/config/runtime.exs\", fn file ->\n        assert file =~\n                 ~r/^  http: \\[port: String.to_integer\\(System.get_env\\(\"PORT\", \"4000\"\\)\\)\\]$/m\n\n        assert file =~ ~r/^\\s+ip: {0, 0, 0, 0, 0, 0, 0, 0}$/m\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog/application.ex\", ~r/defmodule PhxBlog.Application do/)\n      assert_file(\"phx_blog/lib/phx_blog.ex\", ~r/defmodule PhxBlog do/)\n\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ \"mod: {PhxBlog.Application, []}\"\n        assert file =~ \"{:jason,\"\n        assert file =~ \"{:phoenix_live_dashboard,\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web.ex\", fn file ->\n        assert file =~ \"defmodule PhxBlogWeb do\"\n        assert file =~ \"import Phoenix.HTML\"\n        assert file =~ \"Phoenix.LiveView\"\n      end)\n\n      assert_file(\"phx_blog/test/phx_blog_web/controllers/page_controller_test.exs\")\n      assert_file(\"phx_blog/test/phx_blog_web/controllers/error_html_test.exs\")\n      assert_file(\"phx_blog/test/phx_blog_web/controllers/error_json_test.exs\")\n      assert_file(\"phx_blog/test/support/conn_case.ex\")\n      assert_file(\"phx_blog/test/test_helper.exs\")\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/controllers/page_controller.ex\",\n        ~r/defmodule PhxBlogWeb.PageController/\n      )\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/controllers/page_html.ex\",\n        ~r/defmodule PhxBlogWeb.PageHTML/\n      )\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/controllers/error_html.ex\",\n        ~r/defmodule PhxBlogWeb.ErrorHTML/\n      )\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/controllers/error_json.ex\",\n        ~r/defmodule PhxBlogWeb.ErrorJSON/\n      )\n\n      assert_file(\"phx_blog/lib/phx_blog_web/components/layouts.ex\", fn file ->\n        assert file =~ \"defmodule PhxBlogWeb.Layouts\"\n        assert file =~ ~S|gettext(\"Attempting to reconnect\")|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/components/core_components.ex\", fn file ->\n        assert file =~ ~S|gettext(\"close\")|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", fn file ->\n        assert file =~ \"defmodule PhxBlogWeb.Router\"\n        assert file =~ \"live_dashboard\"\n        assert file =~ \"import Phoenix.LiveDashboard.Router\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        assert file =~ ~s|defmodule PhxBlogWeb.Endpoint|\n        assert file =~ ~s|socket \"/live\"|\n        assert file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~ ~s|<meta name=\"csrf-token\" content={get_csrf_token()} />|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/controllers/page_html/home.html.heex\")\n\n      # assets\n      assert_file(\"phx_blog/priv/static/images/logo.svg\")\n\n      assert_file(\"phx_blog/.gitignore\", fn file ->\n        assert file =~ \"/priv/static/assets/\"\n        assert file =~ \"phx_blog-*.tar\"\n        assert file =~ ~r/\\n$/\n      end)\n\n      assert_file(\"phx_blog/config/dev.exs\", fn file ->\n        assert file =~ \"esbuild: {Esbuild,\"\n        assert file =~ \"lib/phx_blog_web/router\\\\.ex$\"\n        assert file =~ \"lib/phx_blog_web/(controllers|live|components)/.*\\\\.(ex|heex)$\"\n        assert file =~ \"http: [ip: {127, 0, 0, 1}]\"\n      end)\n\n      # tailwind\n      assert_file(\"phx_blog/assets/css/app.css\", fn file ->\n        assert file =~ \"lib/phx_blog_web\"\n      end)\n\n      refute File.exists?(\"phx_blog/priv/static/assets/css/app.css\")\n      refute File.exists?(\"phx_blog/priv/static/assets/js/app.js\")\n      assert File.exists?(\"phx_blog/assets/vendor\")\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        assert file =~ \"cd: Path.expand(\\\"../assets\\\", __DIR__)\"\n        assert file =~ \"config :esbuild\"\n      end)\n\n      # Ecto\n      config = ~r/config :phx_blog, PhxBlog.Repo,/\n\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ \"{:phoenix_ecto,\"\n        assert file =~ \"aliases: aliases()\"\n        assert file =~ \"ecto.setup\"\n        assert file =~ \"ecto.reset\"\n      end)\n\n      assert_file(\"phx_blog/config/dev.exs\", config)\n      assert_file(\"phx_blog/config/test.exs\", config)\n\n      assert_file(\"phx_blog/config/runtime.exs\", fn file ->\n        assert file =~ config\n\n        assert file =~\n                 ~S|maybe_ipv6 = if System.get_env(\"ECTO_IPV6\") in ~w(true 1), do: [:inet6], else: []|\n\n        assert file =~ ~S|socket_options: maybe_ipv6|\n\n        assert file =~ \"\"\"\n               if System.get_env(\"PHX_SERVER\") do\n                 config :phx_blog, PhxBlogWeb.Endpoint, server: true\n               end\n               \"\"\"\n\n        assert file =~ ~S[host = System.get_env(\"PHX_HOST\") || \"example.com\"]\n        assert file =~ ~S|url: [host: host, port: 443, scheme: \"https\"],|\n      end)\n\n      assert_file(\n        \"phx_blog/config/test.exs\",\n        ~r/database: \"phx_blog_test#\\{System.get_env\\(\"MIX_TEST_PARTITION\"\\)\\}\"/\n      )\n\n      assert_file(\"phx_blog/lib/phx_blog/repo.ex\", ~r\"defmodule PhxBlog.Repo\")\n      assert_file(\"phx_blog/lib/phx_blog_web.ex\", ~r\"defmodule PhxBlogWeb\")\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/endpoint.ex\",\n        ~r\"plug Phoenix.Ecto.CheckRepoStatus, otp_app: :phx_blog\"\n      )\n\n      assert_file(\"phx_blog/priv/repo/seeds.exs\", ~r\"PhxBlog.Repo.insert!\")\n      assert_file(\"phx_blog/test/support/data_case.ex\", ~r\"defmodule PhxBlog.DataCase\")\n      assert_file(\"phx_blog/priv/repo/migrations/.formatter.exs\", ~r\"import_deps: \\[:ecto_sql\\]\")\n\n      # LiveView\n      refute_file(\"phx_blog/lib/phx_blog_web/live/page_live_view.ex\")\n\n      assert_file(\"phx_blog/assets/js/app.js\", fn file ->\n        assert file =~ ~s|import {LiveSocket} from \"phoenix_live_view\"|\n        assert file =~ ~s|liveSocket.connect()|\n      end)\n\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ ~r\":phoenix_live_view\"\n        assert file =~ ~r\":lazy_html\"\n      end)\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web/router.ex\",\n        &assert(&1 =~ ~s[plug :fetch_live_flash])\n      )\n\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", &assert(&1 =~ ~s[plug :put_root_layout]))\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", &assert(&1 =~ ~s[PageController]))\n\n      # Telemetry\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ \"{:telemetry_metrics,\"\n        assert file =~ \"{:telemetry_poller,\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/telemetry.ex\", fn file ->\n        assert file =~ \"defmodule PhxBlogWeb.Telemetry do\"\n        assert file =~ \"{:telemetry_poller, measurements: periodic_measurements()\"\n        assert file =~ \"defp periodic_measurements do\"\n        assert file =~ \"# {PhxBlogWeb, :count_users, []}\"\n        assert file =~ \"def metrics do\"\n        assert file =~ \"summary(\\\"phoenix.endpoint.stop.duration\\\",\"\n        assert file =~ \"summary(\\\"phoenix.router_dispatch.stop.duration\\\",\"\n        assert file =~ \"# Database Metrics\"\n        assert file =~ \"summary(\\\"phx_blog.repo.query.total_time\\\",\"\n      end)\n\n      # Mailer\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        assert file =~ \"{:swoosh, \\\"~> 1.16\\\"}\"\n        assert file =~ \"{:req, \\\"~> 0.5\\\"}\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog/mailer.ex\", fn file ->\n        assert file =~ \"defmodule PhxBlog.Mailer do\"\n        assert file =~ \"use Swoosh.Mailer, otp_app: :phx_blog\"\n      end)\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        assert file =~ \"config :phx_blog, PhxBlog.Mailer, adapter: Swoosh.Adapters.Local\"\n      end)\n\n      assert_file(\"phx_blog/config/test.exs\", fn file ->\n        assert file =~ \"config :swoosh\"\n        assert file =~ \"config :phx_blog, PhxBlog.Mailer, adapter: Swoosh.Adapters.Test\"\n      end)\n\n      assert_file(\"phx_blog/config/dev.exs\", fn file ->\n        assert file =~ \"config :swoosh\"\n      end)\n\n      assert_file(\"phx_blog/config/prod.exs\", fn file ->\n        assert file =~\n                 \"config :swoosh, api_client: Swoosh.ApiClient.Req\"\n      end)\n\n      # Install dependencies?\n      assert_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n      # Instructions\n      assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n      assert msg =~ \"$ cd phx_blog\"\n      assert msg =~ \"$ mix deps.get\"\n\n      assert_received {:mix_shell, :info, [\"Then configure your database in config/dev.exs\" <> _]}\n      assert_received {:mix_shell, :info, [\"Start your Phoenix app\" <> _]}\n\n      # Gettext\n      assert_file(\"phx_blog/lib/phx_blog_web/gettext.ex\", [\n        ~r\"defmodule PhxBlogWeb.Gettext\",\n        ~r\"use Gettext\\.Backend, otp_app: :phx_blog\"\n      ])\n\n      assert File.exists?(\"phx_blog/priv/gettext/errors.pot\")\n      assert File.exists?(\"phx_blog/priv/gettext/en/LC_MESSAGES/errors.po\")\n    end)\n  end\n\n  test \"new without defaults\" do\n    in_tmp(\"new without defaults\", fn ->\n      Mix.Tasks.Phx.New.run([\n        @app_name,\n        \"--no-html\",\n        \"--no-assets\",\n        \"--no-ecto\",\n        \"--no-gettext\",\n        \"--no-dashboard\",\n        \"--no-mailer\"\n      ])\n\n      # No assets\n      assert_file(\"phx_blog/.gitignore\", fn file ->\n        refute file =~ \"/priv/static/assets/\"\n        assert file =~ ~r/\\n$/\n      end)\n\n      refute File.exists?(\"phx_blog/priv/static/images/logo.svg\")\n\n      assert_file(\"phx_blog/config/dev.exs\", ~r/watchers: \\[\\]/)\n\n      # No assets & No HTML\n      refute_file(\"phx_blog/priv/static/assets/css/app.css\")\n      refute_file(\"phx_blog/priv/static/assets/js/app.js\")\n\n      # No Ecto\n      config = ~r/config :phx_blog, PhxBlog.Repo,/\n      refute File.exists?(\"phx_blog/lib/phx_blog/repo.ex\")\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        refute file =~ \"plug Phoenix.Ecto.CheckRepoStatus, otp_app: :phx_blog\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/telemetry.ex\", fn file ->\n        refute file =~ \"# Database Metrics\"\n        refute file =~ \"summary(\\\"phx_blog.repo.query.total_time\\\",\"\n      end)\n\n      assert_file(\"phx_blog/.formatter.exs\", fn file ->\n        assert file =~ \"import_deps: [:phoenix]\"\n        assert file =~ \"inputs: [\\\"*.{ex,exs}\\\", \\\"{config,lib,test}/**/*.{ex,exs}\\\"]\"\n        refute file =~ \"subdirectories:\"\n      end)\n\n      assert_file(\"phx_blog/mix.exs\", &refute(&1 =~ ~r\":phoenix_ecto\"))\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        refute file =~ \"config :esbuild\"\n        refute file =~ \"config :phx_blog, :generators\"\n        refute file =~ \"ecto_repos:\"\n      end)\n\n      assert_file(\"phx_blog/config/dev.exs\", fn file ->\n        refute file =~ config\n        assert file =~ \"config :phoenix, :plug_init_mode, :runtime\"\n      end)\n\n      assert_file(\"phx_blog/config/test.exs\", &refute(&1 =~ config))\n      assert_file(\"phx_blog/config/runtime.exs\", &refute(&1 =~ config))\n      assert_file(\"phx_blog/lib/phx_blog_web.ex\", &refute(&1 =~ ~r\"alias PhxBlog.Repo\"))\n\n      # No gettext\n      refute_file(\"phx_blog/lib/phx_blog_web/gettext.ex\")\n      refute_file(\"phx_blog/priv/gettext/en/LC_MESSAGES/errors.po\")\n      refute_file(\"phx_blog/priv/gettext/errors.pot\")\n      assert_file(\"phx_blog/mix.exs\", &refute(&1 =~ ~r\":gettext\"))\n\n      assert_file(\n        \"phx_blog/lib/phx_blog_web.ex\",\n        &refute(&1 =~ ~r\"use Gettext, backend: AmsMockWeb.Gettext\")\n      )\n\n      assert_file(\"phx_blog/config/dev.exs\", &refute(&1 =~ ~r\"gettext\"))\n\n      # No HTML\n      assert File.exists?(\"phx_blog/test/phx_blog_web/controllers\")\n\n      assert File.exists?(\"phx_blog/lib/phx_blog_web/controllers\")\n\n      refute File.exists?(\"phx_blog/test/web/controllers/pager_controller_test.exs\")\n      refute File.exists?(\"phx_blog/lib/phx_blog_web/controllers/page_controller.ex\")\n      refute File.exists?(\"phx_blog/lib/phx_blog_web/controllers/page_html\")\n      refute File.exists?(\"phx_blog/lib/phx_blog_web/controllers/error_html.ex\")\n      refute File.exists?(\"phx_blog/lib/phx_blog_web/components\")\n\n      assert_file(\"phx_blog/mix.exs\", &refute(&1 =~ ~r\":phoenix_html\"))\n      assert_file(\"phx_blog/mix.exs\", &refute(&1 =~ ~r\":phoenix_live_reload\"))\n\n      assert_file(\"phx_blog/lib/phx_blog_web.ex\", fn file ->\n        refute file =~ \"html_helpers\"\n        refute file =~ \"Phoenix.HTML\"\n        refute file =~ \"Phoenix.LiveView\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        refute file =~ ~r\"Phoenix.LiveReloader\"\n        refute file =~ ~r\"Phoenix.LiveReloader.Socket\"\n      end)\n\n      refute_file(\"phx_blog/lib/phx_blog_web/controllers/error_html.ex\")\n      assert_file(\"phx_blog/lib/phx_blog_web/controllers/error_json.ex\")\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", &refute(&1 =~ ~r\"pipeline :browser\"))\n\n      # No Dashboard\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        refute file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", fn file ->\n        refute file =~ \"live_dashboard\"\n        refute file =~ \"import Phoenix.LiveDashboard.Router\"\n      end)\n\n      # No mailer or emails\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        refute file =~ \"{:swoosh\"\n        refute file =~ \"{:req\"\n      end)\n\n      refute File.exists?(\"phx_blog/lib/phx_blog/mailer.ex\")\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        refute file =~ \"config :swoosh\"\n        refute file =~ \"config :phx_blog, PhxBlog.Mailer, adapter: Swoosh.Adapters.Local\"\n      end)\n\n      assert_file(\"phx_blog/config/test.exs\", fn file ->\n        refute file =~ \"config :swoosh\"\n        refute file =~ \"config :phx_blog, PhxBlog.Mailer, adapter: Swoosh.Adapters.Test\"\n      end)\n\n      assert_file(\"phx_blog/config/dev.exs\", fn file ->\n        refute file =~ \"config :swoosh\"\n      end)\n\n      assert_file(\"phx_blog/config/prod.exs\", fn file ->\n        refute file =~ \"config :swoosh\"\n      end)\n    end)\n  end\n\n  test \"new with --no-dashboard\" do\n    in_tmp(\"new with no_dashboard\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-dashboard\"])\n\n      assert_file(\"phx_blog/mix.exs\", &refute(&1 =~ ~r\":phoenix_live_dashboard\"))\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        assert file =~ ~s|defmodule PhxBlogWeb.Endpoint|\n        assert file =~ ~s|  socket \"/live\"|\n        refute file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n    end)\n  end\n\n  test \"new with --no-dashboard and --no-live\" do\n    in_tmp(\"new with no_dashboard and no_live\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-dashboard\", \"--no-live\"])\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        assert file =~ ~s|defmodule PhxBlogWeb.Endpoint|\n        assert file =~ ~s|# socket \"/live\"|\n        refute file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n    end)\n  end\n\n  test \"new with --no-html\" do\n    in_tmp(\"new with no_html\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-html\"])\n\n      assert_file(\"phx_blog/mix.exs\", fn file ->\n        refute file =~ ~s|:phoenix_live_view|\n        refute file =~ ~s|:phoenix_html|\n        assert file =~ ~s|:phoenix_live_dashboard|\n      end)\n\n      assert_file(\"phx_blog/.formatter.exs\", fn file ->\n        assert file =~ \"import_deps: [:ecto, :ecto_sql, :phoenix]\"\n        assert file =~ \"subdirectories: [\\\"priv/*/migrations\\\"]\"\n\n        assert file =~\n                 \"inputs: [\\\"*.{ex,exs}\\\", \\\"{config,lib,test}/**/*.{ex,exs}\\\", \\\"priv/*/seeds.exs\\\"]\"\n\n        refute file =~ \"plugins:\"\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/endpoint.ex\", fn file ->\n        assert file =~ ~s|defmodule PhxBlogWeb.Endpoint|\n        assert file =~ ~s|socket \"/live\"|\n        assert file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web.ex\", fn file ->\n        refute file =~ ~s|Phoenix.HTML|\n        refute file =~ ~s|Phoenix.LiveView|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/router.ex\", fn file ->\n        refute file =~ ~s|pipeline :browser|\n        assert file =~ ~s|pipe_through [:fetch_session, :protect_from_forgery]|\n      end)\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        refute file =~ ~s|config :phoenix_live_view|\n      end)\n\n      assert_file(\"phx_blog/config/test.exs\", fn file ->\n        refute file =~ ~s|config :phoenix_live_view|\n      end)\n    end)\n  end\n\n  test \"new with --no-assets\" do\n    in_tmp(\"new no_assets\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-assets\"])\n\n      assert_file(\"phx_blog/.gitignore\", fn file ->\n        refute file =~ \"/priv/static/assets/\"\n      end)\n\n      assert_file(\"phx_blog/.gitignore\")\n      assert_file(\"phx_blog/.gitignore\", ~r/\\n$/)\n      assert_file(\"phx_blog/priv/static/assets/css/app.css\")\n      assert_file(\"phx_blog/priv/static/assets/js/app.js\")\n      assert_file(\"phx_blog/priv/static/favicon.ico\")\n\n      assert_file(\"phx_blog/config/config.exs\", fn file ->\n        refute file =~ \"config :esbuild\"\n      end)\n\n      assert_file(\"phx_blog/config/prod.exs\", fn file ->\n        refute file =~ \"config :phx_blog, PhxBlogWeb.Endpoint, cache_static_manifest:\"\n      end)\n    end)\n  end\n\n  test \"new with --no-ecto\" do\n    in_tmp(\"new with no_ecto\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-ecto\"])\n\n      assert_file(\"phx_blog/.formatter.exs\", fn file ->\n        assert file =~ \"import_deps: [:phoenix]\"\n        assert file =~ \"plugins: [Phoenix.LiveView.HTMLFormatter]\"\n        assert file =~ \"inputs: [\\\"*.{heex,ex,exs}\\\", \\\"{config,lib,test}/**/*.{heex,ex,exs}\\\"]\"\n        refute file =~ \"subdirectories:\"\n      end)\n    end)\n  end\n\n  test \"new with --no-gettext\" do\n    in_tmp(\"new with no_gettext\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-gettext\"])\n\n      assert_file(\"phx_blog/lib/phx_blog_web/components/layouts.ex\", fn file ->\n        assert file =~ ~S|Attempting to reconnect|\n        refute file =~ ~S|gettext(\"Attempting to reconnect\")|\n      end)\n\n      assert_file(\"phx_blog/lib/phx_blog_web/components/core_components.ex\", fn file ->\n        assert file =~ ~S|aria-label=\"close\"|\n        refute file =~ ~S|gettext(\"close\")|\n      end)\n    end)\n  end\n\n  test \"new with binary_id\" do\n    in_tmp(\"new with binary_id\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--binary-id\"])\n      assert_file(\"phx_blog/config/config.exs\", ~r/generators: \\[.*binary_id: true\\.*]/)\n    end)\n  end\n\n  test \"new with path, app and module\" do\n    in_tmp(\"new with path, app and module\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path, \"--app\", @app_name, \"--module\", \"PhoteuxBlog\"])\n\n      assert_file(\"custom_path/.gitignore\")\n      assert_file(\"custom_path/.gitignore\", ~r/\\n$/)\n      assert_file(\"custom_path/mix.exs\", ~r/app: :phx_blog/)\n      assert_file(\"custom_path/lib/phx_blog_web/endpoint.ex\", ~r/app: :phx_blog/)\n      assert_file(\"custom_path/config/config.exs\", ~r/namespace: PhoteuxBlog/)\n    end)\n  end\n\n  test \"new inside umbrella\" do\n    in_tmp(\"new inside umbrella\", fn ->\n      File.write!(\"mix.exs\", MixHelper.umbrella_mixfile_contents())\n      File.mkdir!(\"apps\")\n\n      File.cd!(\"apps\", fn ->\n        Mix.Tasks.Phx.New.run([@app_name])\n\n        assert_file(\"phx_blog/mix.exs\", fn file ->\n          assert file =~ \"deps_path: \\\"../../deps\\\"\"\n          assert file =~ \"lockfile: \\\"../../mix.lock\\\"\"\n        end)\n\n        refute_file(\"phx_blog/config/config.exs\")\n      end)\n\n      assert_file(\"config/config.exs\", fn file ->\n        assert file =~ \"PhxBlogWeb.Endpoint\"\n        assert file =~ ~s[cd: Path.expand(\"../apps/phx_blog/assets\", __DIR__),]\n      end)\n\n      assert_file(\"config/config.exs\", \"PhxBlogWeb.Endpoint\")\n    end)\n  end\n\n  test \"new with --no-install\" do\n    in_tmp(\"new with no install\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-install\"])\n\n      # Does not prompt to install dependencies\n      refute_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n      # Instructions\n      assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n      assert msg =~ \"$ cd phx_blog\"\n      assert msg =~ \"$ mix deps.get\"\n\n      assert_received {:mix_shell, :info, [\"Then configure your database in config/dev.exs\" <> _]}\n      assert_received {:mix_shell, :info, [\"Start your Phoenix app\" <> _]}\n    end)\n  end\n\n  test \"new defaults to pg adapter\" do\n    in_tmp(\"new defaults to pg adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path])\n\n      assert_file(\"custom_path/mix.exs\", \":postgrex\")\n\n      assert_file(\"custom_path/config/dev.exs\", [\n        ~r/username: \"postgres\"/,\n        ~r/password: \"postgres\"/,\n        ~r/hostname: \"localhost\"/\n      ])\n\n      assert_file(\"custom_path/config/test.exs\", [\n        ~r/username: \"postgres\"/,\n        ~r/password: \"postgres\"/,\n        ~r/hostname: \"localhost\"/\n      ])\n\n      assert_file(\"custom_path/config/runtime.exs\", [~r/url: database_url/])\n      assert_file(\"custom_path/lib/custom_path/repo.ex\", \"Ecto.Adapters.Postgres\")\n\n      assert_file(\"custom_path/test/support/conn_case.ex\", \"DataCase.setup_sandbox(tags)\")\n\n      assert_file(\n        \"custom_path/test/support/data_case.ex\",\n        \"Ecto.Adapters.SQL.Sandbox.start_owner\"\n      )\n    end)\n  end\n\n  test \"new with mysql adapter\" do\n    in_tmp(\"new with mysql adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path, \"--database\", \"mysql\"])\n\n      assert_file(\"custom_path/mix.exs\", \":myxql\")\n      assert_file(\"custom_path/config/dev.exs\", [~r/username: \"root\"/, ~r/password: \"\"/])\n      assert_file(\"custom_path/config/test.exs\", [~r/username: \"root\"/, ~r/password: \"\"/])\n      assert_file(\"custom_path/config/runtime.exs\", [~r/url: database_url/])\n      assert_file(\"custom_path/lib/custom_path/repo.ex\", \"Ecto.Adapters.MyXQL\")\n\n      assert_file(\"custom_path/test/support/conn_case.ex\", \"DataCase.setup_sandbox(tags)\")\n\n      assert_file(\n        \"custom_path/test/support/data_case.ex\",\n        \"Ecto.Adapters.SQL.Sandbox.start_owner\"\n      )\n    end)\n  end\n\n  test \"new with sqlite3 adapter\" do\n    in_tmp(\"new with sqlite3 adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path, \"--database\", \"sqlite3\"])\n\n      assert_file(\"custom_path/mix.exs\", \":ecto_sqlite3\")\n      assert_file(\"custom_path/config/dev.exs\", [~r/database: .*_dev.db/])\n      assert_file(\"custom_path/config/test.exs\", [~r/database: .*_test.db/])\n      assert_file(\"custom_path/config/runtime.exs\", [~r/database: database_path/])\n      assert_file(\"custom_path/lib/custom_path/repo.ex\", \"Ecto.Adapters.SQLite3\")\n\n      assert_file(\"custom_path/lib/custom_path/application.ex\", fn file ->\n        assert file =~ \"{Ecto.Migrator\"\n        assert file =~ \"repos: Application.fetch_env!(:custom_path, :ecto_repos)\"\n        assert file =~ \"skip: skip_migrations?()\"\n\n        assert file =~ \"defp skip_migrations?() do\"\n        assert file =~ ~s/System.get_env(\"RELEASE_NAME\") == nil/\n      end)\n\n      assert_file(\"custom_path/test/support/conn_case.ex\", \"DataCase.setup_sandbox(tags)\")\n\n      assert_file(\n        \"custom_path/test/support/data_case.ex\",\n        \"Ecto.Adapters.SQL.Sandbox.start_owner\"\n      )\n\n      assert_file(\"custom_path/.gitignore\", \"*.db\")\n      assert_file(\"custom_path/.gitignore\", \"*.db-*\")\n    end)\n  end\n\n  test \"new with mssql adapter\" do\n    in_tmp(\"new with mssql adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path, \"--database\", \"mssql\"])\n\n      assert_file(\"custom_path/mix.exs\", \":tds\")\n\n      assert_file(\"custom_path/config/dev.exs\", [\n        ~r/username: \"sa\"/,\n        ~r/password: \"some!Password\"/\n      ])\n\n      assert_file(\"custom_path/config/test.exs\", [\n        ~r/username: \"sa\"/,\n        ~r/password: \"some!Password\"/\n      ])\n\n      assert_file(\"custom_path/config/runtime.exs\", [~r/url: database_url/])\n      assert_file(\"custom_path/lib/custom_path/repo.ex\", \"Ecto.Adapters.Tds\")\n\n      assert_file(\"custom_path/test/support/conn_case.ex\", \"DataCase.setup_sandbox(tags)\")\n\n      assert_file(\n        \"custom_path/test/support/data_case.ex\",\n        \"Ecto.Adapters.SQL.Sandbox.start_owner\"\n      )\n    end)\n  end\n\n  test \"new with invalid database adapter\" do\n    in_tmp(\"new with invalid database adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n\n      assert_raise Mix.Error, ~s(Unknown database \"invalid\"), fn ->\n        Mix.Tasks.Phx.New.run([project_path, \"--database\", \"invalid\"])\n      end\n    end)\n  end\n\n  test \"new with bandit web adapter\" do\n    in_tmp(\"new with bandit web adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n      Mix.Tasks.Phx.New.run([project_path, \"--adapter\", \"bandit\"])\n      assert_file(\"custom_path/mix.exs\", \":bandit\")\n\n      assert_file(\"custom_path/config/config.exs\", \"adapter: Bandit.PhoenixAdapter\")\n    end)\n  end\n\n  test \"new with invalid args\" do\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"007invalid\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"valid\", \"--app\", \"007invalid\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"exInvalidAppName\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name must be a valid Elixir alias\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid\", \"--module\", \"not.valid\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"string\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid\", \"--app\", \"mix\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid\", \"--module\", \"String\"])\n    end\n  end\n\n  test \"invalid options\" do\n    assert_raise OptionParser.ParseError, fn ->\n      Mix.Tasks.Phx.New.run([\"valid\", \"-database\", \"mysql\"])\n    end\n  end\n\n  test \"new without args\" do\n    in_tmp(\"new without args\", fn ->\n      assert capture_io(fn -> Mix.Tasks.Phx.New.run([]) end) =~\n               \"Creates a new Phoenix project.\"\n    end)\n  end\n\n  test \"new with reserved name\" do\n    assert_raise Mix.Error, ~r/Application name cannot be \"server\" as it is reserved/, fn ->\n      Mix.Tasks.Phx.New.run([\"server\"])\n    end\n\n    assert_raise Mix.Error, ~r/Application name cannot be \"table\" as it is reserved/, fn ->\n      Mix.Tasks.Phx.New.run([\"table\"])\n    end\n  end\n\n  test \"new from inside docker machine (simulated)\" do\n    in_tmp(\"new without defaults\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--inside-docker-env\"])\n\n      assert_file(\"phx_blog/config/dev.exs\", fn file ->\n        assert file =~ \"http: [ip: {0, 0, 0, 0}\"\n      end)\n    end)\n  end\n\n  test \"new with --no-agents-md\" do\n    in_tmp(\"new with no agents md\", fn ->\n      Mix.Tasks.Phx.New.run([@app_name, \"--no-agents-md\"])\n\n      refute_file(\"phx_blog/AGENTS.md\")\n    end)\n  end\n\n  describe \"PHX_NEW_CACHE_DIR\" do\n    @phx_new_cache_dir System.get_env(\"PHX_NEW_CACHE_DIR\")\n    test \"new with PHX_NEW_CACHE_DIR\" do\n      System.put_env(\"PHX_NEW_CACHE_DIR\", __DIR__)\n      cache_files = File.ls!(__DIR__)\n\n      in_tmp(\"new with cache dir\", fn ->\n        Mix.Tasks.Phx.New.run([@app_name])\n        project_files = File.ls!(Path.join(File.cwd!(), @app_name))\n        assert \"mix.exs\" in project_files\n\n        for file <- cache_files do\n          assert file in project_files, \"#{file} not copied to new project\"\n        end\n      end)\n    after\n      if @phx_new_cache_dir do\n        System.put_env(\"PHX_NEW_CACHE_DIR\", @phx_new_cache_dir)\n      else\n        System.delete_env(\"PHX_NEW_CACHE_DIR\")\n      end\n    end\n\n    test \"new with PHX_NEW_CACHE_DIR that doesn't exist\" do\n      cache_dir = Path.join(__DIR__, \"does-not-exist\")\n      System.put_env(\"PHX_NEW_CACHE_DIR\", cache_dir)\n      refute File.exists?(cache_dir)\n\n      in_tmp(\"new with cache dir\", fn ->\n        Mix.Tasks.Phx.New.run([@app_name])\n        project_files = File.ls!(Path.join(File.cwd!(), @app_name))\n        assert \"mix.exs\" in project_files\n      end)\n    after\n      if @phx_new_cache_dir do\n        System.put_env(\"PHX_NEW_CACHE_DIR\", @phx_new_cache_dir)\n      else\n        System.delete_env(\"PHX_NEW_CACHE_DIR\")\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "installer/test/phx_new_umbrella_test.exs",
    "content": "Code.require_file(\"mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.New.UmbrellaTest do\n  use ExUnit.Case, async: false\n  import MixHelper\n\n  @app \"phx_umb\"\n\n  setup config do\n    # The shell asks to install deps.\n    # We will politely say not.\n    decline_prompt()\n    {:ok, tmp_dir: to_string(config.test)}\n  end\n\n  defp decline_prompt do\n    send(self(), {:mix_shell_input, :yes?, false})\n  end\n\n  defp root_path(app, path \\\\ \"\") do\n    Path.join([\"#{app}_umbrella\", path])\n  end\n\n  defp app_path(app, path) do\n    Path.join([\"#{app}_umbrella/apps/#{app}\", path])\n  end\n\n  defp web_path(app, path) do\n    Path.join([\"#{app}_umbrella/apps/#{app}_web\", path])\n  end\n\n  test \"new with umbrella and defaults\" do\n    in_tmp(\"new with umbrella and defaults\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\"])\n\n      assert_file(root_path(@app, \"README.md\"))\n      assert_file(root_path(@app, \"AGENTS.md\"))\n      assert_file(root_path(@app, \".gitignore\"))\n\n      assert_file(app_path(@app, \"README.md\"))\n      assert_file(app_path(@app, \".gitignore\"), \"#{@app}-*.tar\")\n\n      assert_file(web_path(@app, \"README.md\"))\n\n      assert_file(root_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"apps_path: \\\"apps\\\"\"\n      end)\n\n      # Phoenix.LiveView.HTMLFormatter\n      assert_file(root_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"{:phoenix_live_view, \\\">= 0.0.0\\\"}\"\n      end)\n\n      assert_file(app_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"app: :phx_umb\"\n        assert file =~ ~S{build_path: \"../../_build\"}\n        assert file =~ ~S{config_path: \"../../config/config.exs\"}\n        assert file =~ ~S{deps_path: \"../../deps\"}\n        assert file =~ ~S{lockfile: \"../../mix.lock\"}\n      end)\n\n      assert_file(root_path(@app, \"config/config.exs\"), fn file ->\n        assert file =~ ~r/config :esbuild/\n        assert file =~ \"cd: Path.expand(\\\"../apps/phx_umb_web/assets\\\", __DIR__)\"\n        assert file =~ ~S[import_config \"#{config_env()}.exs\"]\n        assert file =~ \"config :phoenix, :json_library, Jason\"\n        assert file =~ \"ecto_repos: [PhxUmb.Repo]\"\n        assert file =~ \":phx_umb_web, PhxUmbWeb.Endpoint\"\n        assert file =~ \"generators: [context_app: :phx_umb]\\n\"\n        refute file =~ \"namespace\"\n      end)\n\n      assert_file(root_path(@app, \"config/dev.exs\"), fn file ->\n        assert file =~ ~r[esbuild: {Esbuild]\n        assert file =~ \"lib/#{@app}_web/router\\\\.ex$\"\n        assert file =~ \"lib/#{@app}_web/(controllers|live|components)/.*\\\\.(ex|heex)$\"\n        assert file =~ \"config :#{@app}_web, dev_routes: true\"\n      end)\n\n      assert_file(root_path(@app, \"config/prod.exs\"), fn file ->\n        assert file =~ \"port: 80\"\n      end)\n\n      assert_file(root_path(@app, \"config/runtime.exs\"), fn file ->\n        assert file =~\n                 ~r/^\\s+config :phx_umb_web, PhxUmbWeb.Endpoint,\\n\\s+http: \\[port: String\\.to_integer\\(System\\.get_env\\(\"PORT\", \"4000\"\\)\\)\\]$/m\n\n        assert file =~ ~r/^\\s+ip: {0, 0, 0, 0, 0, 0, 0, 0}$/m\n      end)\n\n      assert_file(root_path(@app, \".formatter.exs\"), fn file ->\n        assert file =~ \"plugins: [Phoenix.LiveView.HTMLFormatter]\"\n        assert file =~ \"inputs: [\\\"mix.exs\\\", \\\"config/*.exs\\\"]\"\n        assert file =~ \"subdirectories: [\\\"apps/*\\\"]\"\n      end)\n\n      assert_file(app_path(@app, \".formatter.exs\"), fn file ->\n        assert file =~ \"import_deps: [:ecto, :ecto_sql]\"\n        assert file =~ \"subdirectories: [\\\"priv/*/migrations\\\"]\"\n        assert file =~ \"plugins: [Phoenix.LiveView.HTMLFormatter]\"\n\n        assert file =~\n                 \"inputs: [\\\"*.{heex,ex,exs}\\\", \\\"{config,lib,test}/**/*.{heex,ex,exs}\\\", \\\"priv/*/seeds.exs\\\"]\"\n      end)\n\n      assert_file(web_path(@app, \".formatter.exs\"), fn file ->\n        assert file =~ \"import_deps: [:phoenix]\"\n        assert file =~ \"plugins: [Phoenix.LiveView.HTMLFormatter]\"\n        assert file =~ \"inputs: [\\\"*.{heex,ex,exs}\\\", \\\"{config,lib,test}/**/*.{heex,ex,exs}\\\"]\"\n        refute file =~ \"import_deps: [:ecto]\"\n        refute file =~ \"subdirectories:\"\n      end)\n\n      assert_file(\n        app_path(@app, \"lib/#{@app}/application.ex\"),\n        ~r/defmodule PhxUmb.Application do/\n      )\n\n      assert_file(app_path(@app, \"lib/#{@app}/application.ex\"), ~r/PhxUmb.Repo/)\n      assert_file(app_path(@app, \"lib/#{@app}.ex\"), ~r/defmodule PhxUmb do/)\n\n      assert_file(app_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"mod: {PhxUmb.Application, []}\"\n        assert file =~ \"{:phoenix_pubsub, \\\"~> 2.1\\\"}\"\n      end)\n\n      assert_file(app_path(@app, \"test/test_helper.exs\"))\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/application.ex\"),\n        ~r/defmodule PhxUmbWeb.Application do/\n      )\n\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"mod: {PhxUmbWeb.Application, []}\"\n        assert file =~ \"{:jason\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web.ex\"), fn file ->\n        assert file =~ \"defmodule PhxUmbWeb do\"\n        assert file =~ \"import Phoenix.HTML\"\n        assert file =~ \"Phoenix.LiveView\"\n      end)\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/endpoint.ex\"),\n        ~r/defmodule PhxUmbWeb.Endpoint do/\n      )\n\n      assert_file(web_path(@app, \"test/#{@app}_web/controllers/page_controller_test.exs\"))\n      assert_file(web_path(@app, \"test/#{@app}_web/controllers/error_html_test.exs\"))\n      assert_file(web_path(@app, \"test/#{@app}_web/controllers/error_json_test.exs\"))\n      assert_file(web_path(@app, \"test/support/conn_case.ex\"))\n      assert_file(web_path(@app, \"test/test_helper.exs\"))\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/controllers/page_controller.ex\"),\n        ~r/defmodule PhxUmbWeb.PageController/\n      )\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/controllers/page_html.ex\"),\n        ~r/defmodule PhxUmbWeb.PageHTML/\n      )\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/router.ex\"), fn file ->\n        assert file =~ \"defmodule PhxUmbWeb.Router\"\n        assert file =~ \"Application.compile_env(:#{@app}_web, :dev_routes)\"\n      end)\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/components/core_components.ex\"),\n        \"defmodule PhxUmbWeb.CoreComponents\"\n      )\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/components/layouts.ex\"),\n        \"defmodule PhxUmbWeb.Layouts\"\n      )\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/components/layouts/root.html.heex\"), fn file ->\n        assert file =~ ~s|<meta name=\"csrf-token\" content={get_csrf_token()} />|\n      end)\n\n      # assets\n      assert_file(web_path(@app, \".gitignore\"), \"/priv/static/assets/\")\n      assert_file(web_path(@app, \".gitignore\"), \"#{@app}_web-*.tar\")\n      assert_file(web_path(@app, \".gitignore\"), ~r/\\n$/)\n\n      assert_file(web_path(@app, \"assets/css/app.css\"), fn file ->\n        assert file =~ \"lib/phx_umb_web\"\n      end)\n\n      assert_file(web_path(@app, \"priv/static/favicon.ico\"))\n\n      refute File.exists?(web_path(@app, \"priv/static/assets/css/app.css\"))\n      refute File.exists?(web_path(@app, \"priv/static/assets/js/app.js\"))\n      assert File.exists?(web_path(@app, \"assets/vendor\"))\n\n      # web deps\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"{:phx_umb, in_umbrella: true}\"\n        assert file =~ \"{:phoenix,\"\n        assert file =~ \"{:phoenix_live_view,\"\n        assert file =~ \"{:gettext,\"\n        assert file =~ \"{:bandit,\"\n      end)\n\n      # app deps\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"{:phoenix_ecto,\"\n        assert file =~ \"{:jason,\"\n      end)\n\n      # Ecto\n      config = ~r/config :phx_umb, PhxUmb.Repo,/\n      assert_file(root_path(@app, \"config/dev.exs\"), config)\n      assert_file(root_path(@app, \"config/test.exs\"), config)\n      assert_file(root_path(@app, \"config/runtime.exs\"), config)\n\n      assert_file(app_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"aliases: aliases()\"\n        assert file =~ \"ecto.setup\"\n        assert file =~ \"ecto.reset\"\n        assert file =~ \"{:jason,\"\n      end)\n\n      assert_file(app_path(@app, \"lib/#{@app}/repo.ex\"), ~r\"defmodule PhxUmb.Repo\")\n      assert_file(app_path(@app, \"priv/repo/seeds.exs\"), ~r\"PhxUmb.Repo.insert!\")\n      assert_file(app_path(@app, \"test/support/data_case.ex\"), ~r\"defmodule PhxUmb.DataCase\")\n\n      assert_file(\n        app_path(@app, \"priv/repo/migrations/.formatter.exs\"),\n        ~r\"import_deps: \\[:ecto_sql\\]\"\n      )\n\n      # Telemetry\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"{:telemetry_metrics,\"\n        assert file =~ \"{:telemetry_poller,\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/telemetry.ex\"), fn file ->\n        assert file =~ \"defmodule PhxUmbWeb.Telemetry do\"\n        assert file =~ \"{:telemetry_poller, measurements: periodic_measurements()\"\n        assert file =~ \"defp periodic_measurements do\"\n        assert file =~ \"# {PhxUmbWeb, :count_users, []}\"\n        assert file =~ \"def metrics do\"\n        assert file =~ \"summary(\\\"phoenix.endpoint.stop.duration\\\",\"\n        assert file =~ \"summary(\\\"phoenix.router_dispatch.stop.duration\\\",\"\n        assert file =~ \"# Database Metrics\"\n        assert file =~ \"summary(\\\"phx_umb.repo.query.total_time\\\",\"\n      end)\n\n      # Live\n      assert_file(web_path(@app, \"assets/js/app.js\"), fn file ->\n        assert file =~ ~s[import {LiveSocket} from \"phoenix_live_view\"]\n      end)\n\n      assert_file(root_path(@app, \"config/config.exs\"), fn file ->\n        assert file =~ \"live_view:\"\n        assert file =~ \"signing_salt:\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web.ex\"), fn file ->\n        assert file =~ \"def live_view do\"\n        assert file =~ \"def live_component do\"\n      end)\n\n      assert_file(\n        web_path(@app, \"lib/phx_umb_web/endpoint.ex\"),\n        ~s[socket \"/live\", Phoenix.LiveView.Socket]\n      )\n\n      assert_file(web_path(@app, \"lib/phx_umb_web/router.ex\"), fn file ->\n        assert file =~ ~s[plug :fetch_live_flash]\n        assert file =~ ~s[plug :put_root_layout, html: {PhxUmbWeb.Layouts, :root}]\n        assert file =~ ~s[get \"/\", PageController]\n      end)\n\n      # Mailer\n      assert_file(app_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"{:swoosh, \\\"~> 1.16\\\"}\"\n        assert file =~ \"{:req, \\\"~> 0.5\\\"}\"\n      end)\n\n      assert_file(app_path(@app, \"lib/#{@app}/mailer.ex\"), fn file ->\n        assert file =~ \"defmodule PhxUmb.Mailer do\"\n        assert file =~ \"use Swoosh.Mailer, otp_app: :phx_umb\"\n      end)\n\n      assert_file(root_path(@app, \"config/config.exs\"), fn file ->\n        assert file =~ \"config :phx_umb, PhxUmb.Mailer, adapter: Swoosh.Adapters.Local\"\n      end)\n\n      assert_file(root_path(@app, \"config/test.exs\"), fn file ->\n        assert file =~ \"config :swoosh\"\n        assert file =~ \"config :phx_umb, PhxUmb.Mailer, adapter: Swoosh.Adapters.Test\"\n      end)\n\n      assert_file(root_path(@app, \"config/dev.exs\"), fn file ->\n        assert file =~ \"config :swoosh\"\n      end)\n\n      assert_file(root_path(@app, \"config/prod.exs\"), fn file ->\n        assert file =~ \"config :swoosh, :api_client, Swoosh.ApiClient.Req\"\n      end)\n\n      # Install dependencies?\n      assert_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n      # Instructions\n      assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n      assert msg =~ \"$ cd phx_umb\"\n      assert msg =~ \"$ mix deps.get\"\n\n      assert_received {:mix_shell, :info, [\"Then configure your database in config/dev.exs\" <> _]}\n      assert_received {:mix_shell, :info, [\"Start your Phoenix app\" <> _]}\n\n      # Gettext\n      assert_file(web_path(@app, \"lib/#{@app}_web/gettext.ex\"), [\n        ~r\"defmodule PhxUmbWeb.Gettext\",\n        ~r\"use Gettext\\.Backend, otp_app: :phx_umb_web\"\n      ])\n\n      assert File.exists?(web_path(@app, \"priv/gettext/errors.pot\"))\n      assert File.exists?(web_path(@app, \"priv/gettext/en/LC_MESSAGES/errors.po\"))\n    end)\n  end\n\n  test \"new without defaults\" do\n    in_tmp(\"new without defaults\", fn ->\n      Mix.Tasks.Phx.New.run([\n        @app,\n        \"--umbrella\",\n        \"--no-html\",\n        \"--no-assets\",\n        \"--no-ecto\",\n        \"--no-live\",\n        \"--no-mailer\"\n      ])\n\n      # No assets\n      assert_file(web_path(@app, \".gitignore\"), fn file ->\n        assert file =~ ~r/\\n$/\n        refute file =~ \"/priv/static/assets/\"\n      end)\n\n      assert_file(root_path(@app, \"config/dev.exs\"), ~r/watchers: \\[\\]/)\n\n      # No assets & No HTML\n      refute_file(web_path(@app, \"priv/static/assets/js/app.js\"))\n      refute_file(web_path(@app, \"priv/static/assets/css/app.css\"))\n\n      # No Ecto\n      config = ~r/config :phx_umb, PhxUmb.Repo,/\n      refute File.exists?(app_path(@app, \"lib/#{@app}_web/repo.ex\"))\n\n      assert_file(app_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":phoenix_ecto\"))\n\n      assert_file(root_path(@app, \"config/config.exs\"), fn file ->\n        refute file =~ \"config :esbuild\"\n        refute file =~ \"config :phx_blog_web, :generators\"\n        refute file =~ \"ecto_repos:\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/telemetry.ex\"), fn file ->\n        refute file =~ \"# Database Metrics\"\n        refute file =~ \"summary(\\\"phx_umb.repo.query.total_time\\\",\"\n      end)\n\n      assert_file(root_path(@app, \"config/dev.exs\"), &refute(&1 =~ config))\n      assert_file(root_path(@app, \"config/test.exs\"), &refute(&1 =~ config))\n      assert_file(root_path(@app, \"config/runtime.exs\"), &refute(&1 =~ config))\n\n      assert_file(app_path(@app, \"lib/#{@app}/application.ex\"), ~r/Supervisor.start_link\\(/)\n\n      # No LiveView (in web_path)\n      assert_file(web_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":phoenix_live_view\"))\n      assert_file(web_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":lazy_html\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/templates/page/hero.html.heex\"))\n\n      refute_file(web_path(@app, \"assets/js/live.js\"))\n\n      # No HTML\n      assert File.exists?(web_path(@app, \"test/#{@app}_web/controllers\"))\n      refute File.exists?(web_path(@app, \"test/#{@app}_web/controllers/error_html_test.exs\"))\n      assert File.exists?(web_path(@app, \"lib/#{@app}_web/controllers\"))\n      refute File.exists?(web_path(@app, \"test/controllers/page_controller_test.exs\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/controllers/page_controller.ex\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/controllers/error_html.ex\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/controllers/page_html.ex\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/controllers/page_html\"))\n      refute File.exists?(web_path(@app, \"lib/#{@app}_web/components\"))\n\n      assert_file(web_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":phoenix_html\"))\n      assert_file(web_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":phoenix_live_reload\"))\n\n      assert_file(web_path(@app, \"lib/#{@app}_web.ex\"), fn file ->\n        refute file =~ \"defp html_helpers do\"\n        refute file =~ \"Phoenix.HTML\"\n        refute file =~ \"Phoenix.LiveView\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/endpoint.ex\"), fn file ->\n        refute file =~ ~r\"Phoenix.LiveReloader\"\n        refute file =~ ~r\"Phoenix.LiveReloader.Socket\"\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/controllers/error_json.ex\"), ~r\".json\")\n\n      assert_file(\n        web_path(@app, \"lib/#{@app}_web/router.ex\"),\n        &refute(&1 =~ ~r\"pipeline :browser\")\n      )\n\n      # Without mailer\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        refute file =~ \"{:swoosh\"\n        refute file =~ \"{:req\"\n      end)\n\n      refute File.exists?(app_path(@app, \"lib/#{@app}/mailer.ex\"))\n\n      assert_file(root_path(@app, \"config/config.exs\"), fn file ->\n        refute file =~ \"config :swoosh\"\n        refute file =~ \"config :phx_umb, PhxUmb.Mailer, adapter: Swoosh.Adapters.Local\"\n      end)\n\n      assert_file(root_path(@app, \"config/test.exs\"), fn file ->\n        refute file =~ \"config :phx_umb, PhxUmb.Mailer, adapter: Swoosh.Adapters.Test\"\n      end)\n\n      assert_file(root_path(@app, \"config/prod.exs\"), fn file ->\n        refute file =~ \"config :swoosh\"\n      end)\n    end)\n  end\n\n  test \"new with --no-dashboard\" do\n    in_tmp(\"new with no_dashboard\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--no-dashboard\"])\n\n      assert_file(web_path(@app, \"mix.exs\"), &refute(&1 =~ ~r\":phoenix_live_dashboard\"))\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/endpoint.ex\"), fn file ->\n        assert file =~ ~s|defmodule PhxUmbWeb.Endpoint|\n        assert file =~ ~s|socket \"/live\"|\n        refute file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n    end)\n  end\n\n  test \"new with --no-dashboard and --no-live\" do\n    in_tmp(\"new with no_dashboard and no_live\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--no-dashboard\", \"--no-live\"])\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/endpoint.ex\"), fn file ->\n        assert file =~ ~s|# socket \"/live\"|\n      end)\n\n      assert_file(web_path(@app, \"assets/js/app.js\"), fn file ->\n        assert file =~ ~s|// import {Socket} from \"phoenix\"|\n        assert file =~ ~s|// import {LiveSocket} from \"phoenix_live_view\"|\n        assert file =~ ~s|// import topbar from \"../vendor/topbar\"|\n        assert file =~ ~s|// liveSocket.connect()|\n      end)\n    end)\n  end\n\n  test \"new with --no-html\" do\n    in_tmp(\"new with no_html\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--no-html\"])\n\n      assert_file(root_path(@app, \"mix.exs\"), fn file ->\n        assert file =~ \"defp deps do\\n    []\"\n      end)\n\n      refute_file(web_path(@app, \"test/#{@app}_web/controllers/error_html_test.exs\"))\n\n      assert_file(web_path(@app, \"mix.exs\"), fn file ->\n        refute file =~ ~s|:phoenix_live_view|\n        assert file =~ ~s|:phoenix_live_dashboard|\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/endpoint.ex\"), fn file ->\n        assert file =~ ~s|defmodule PhxUmbWeb.Endpoint|\n        assert file =~ ~s|socket \"/live\"|\n        assert file =~ ~s|plug Phoenix.LiveDashboard.RequestLogger|\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web.ex\"), fn file ->\n        refute file =~ ~s|Phoenix.HTML|\n        refute file =~ ~s|Phoenix.LiveView|\n      end)\n\n      assert_file(web_path(@app, \"lib/#{@app}_web/router.ex\"), fn file ->\n        refute file =~ ~s|pipeline :browser|\n        assert file =~ ~s|pipe_through [:fetch_session, :protect_from_forgery]|\n      end)\n    end)\n  end\n\n  test \"new with --no-assets\" do\n    in_tmp(\"new with no_assets\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--no-assets\"])\n\n      refute File.read!(web_path(@app, \".gitignore\")) |> String.contains?(\"/priv/static/assets/\")\n      assert_file(web_path(@app, \".gitignore\"), ~r/\\n$/)\n      assert_file(web_path(@app, \"priv/static/assets/js/app.js\"))\n      assert_file(web_path(@app, \"priv/static/assets/css/app.css\"))\n      assert_file(web_path(@app, \"priv/static/favicon.ico\"))\n    end)\n  end\n\n  test \"new with binary_id\" do\n    in_tmp(\"new with binary_id\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--binary-id\"])\n\n      assert_file(\n        root_path(@app, \"config/config.exs\"),\n        ~r/generators: \\[context_app: :phx_umb, binary_id: true\\]/\n      )\n    end)\n  end\n\n  test \"new with path, app and module\" do\n    in_tmp(\"new with path, app and module\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n\n      Mix.Tasks.Phx.New.run([\n        project_path,\n        \"--umbrella\",\n        \"--app\",\n        @app,\n        \"--module\",\n        \"PhoteuxBlog\"\n      ])\n\n      assert_file(\"custom_path_umbrella/apps/phx_umb/mix.exs\", ~r/app: :phx_umb/)\n\n      assert_file(\n        \"custom_path_umbrella/apps/phx_umb_web/lib/phx_umb_web/endpoint.ex\",\n        ~r/app: :#{@app}_web/\n      )\n\n      assert_file(\n        \"custom_path_umbrella/apps/phx_umb/lib/phx_umb/application.ex\",\n        ~r/defmodule PhoteuxBlog.Application/\n      )\n\n      assert_file(\n        \"custom_path_umbrella/apps/phx_umb/mix.exs\",\n        ~r/mod: {PhoteuxBlog.Application, \\[\\]}/\n      )\n\n      assert_file(\n        \"custom_path_umbrella/apps/phx_umb_web/lib/phx_umb_web/application.ex\",\n        ~r/defmodule PhoteuxBlogWeb.Application/\n      )\n\n      assert_file(\n        \"custom_path_umbrella/apps/phx_umb_web/mix.exs\",\n        ~r/mod: {PhoteuxBlogWeb.Application, \\[\\]}/\n      )\n\n      assert_file(\"custom_path_umbrella/config/config.exs\", ~r/namespace: PhoteuxBlogWeb/)\n      assert_file(\"custom_path_umbrella/config/config.exs\", ~r/namespace: PhoteuxBlog/)\n    end)\n  end\n\n  test \"new inside umbrella\" do\n    in_tmp(\"new inside umbrella\", fn ->\n      File.write!(\"mix.exs\", MixHelper.umbrella_mixfile_contents())\n      File.mkdir!(\"apps\")\n\n      File.cd!(\"apps\", fn ->\n        assert_raise Mix.Error, \"Unable to nest umbrella project within apps\", fn ->\n          Mix.Tasks.Phx.New.run([@app, \"--umbrella\"])\n        end\n      end)\n    end)\n  end\n\n  test \"new defaults to pg adapter\" do\n    in_tmp(\"new defaults to pg adapter\", fn ->\n      app = \"custom_path\"\n      project_path = Path.join(File.cwd!(), app)\n      Mix.Tasks.Phx.New.run([project_path, \"--umbrella\"])\n\n      assert_file(app_path(app, \"mix.exs\"), \":postgrex\")\n      assert_file(app_path(app, \"lib/custom_path/repo.ex\"), \"Ecto.Adapters.Postgres\")\n\n      assert_file(root_path(app, \"config/dev.exs\"), [\n        ~r/username: \"postgres\"/,\n        ~r/password: \"postgres\"/,\n        ~r/hostname: \"localhost\"/\n      ])\n\n      assert_file(root_path(app, \"config/test.exs\"), [\n        ~r/username: \"postgres\"/,\n        ~r/password: \"postgres\"/,\n        ~r/hostname: \"localhost\"/\n      ])\n\n      assert_file(root_path(app, \"config/runtime.exs\"), [~r/url: database_url/])\n\n      assert_file(web_path(app, \"test/support/conn_case.ex\"), \"DataCase.setup_sandbox(tags)\")\n    end)\n  end\n\n  test \"new with mysql adapter\" do\n    in_tmp(\"new with mysql adapter\", fn ->\n      app = \"custom_path\"\n      project_path = Path.join(File.cwd!(), app)\n      Mix.Tasks.Phx.New.run([project_path, \"--umbrella\", \"--database\", \"mysql\"])\n\n      assert_file(app_path(app, \"mix.exs\"), \":myxql\")\n      assert_file(app_path(app, \"lib/custom_path/repo.ex\"), \"Ecto.Adapters.MyXQL\")\n\n      assert_file(root_path(app, \"config/dev.exs\"), [~r/username: \"root\"/, ~r/password: \"\"/])\n      assert_file(root_path(app, \"config/test.exs\"), [~r/username: \"root\"/, ~r/password: \"\"/])\n      assert_file(root_path(app, \"config/runtime.exs\"), [~r/url: database_url/])\n\n      assert_file(web_path(app, \"test/support/conn_case.ex\"), \"DataCase.setup_sandbox(tags)\")\n    end)\n  end\n\n  test \"new with sqlite3 adapter\" do\n    in_tmp(\"new with sqlite3 adapter\", fn ->\n      app = \"custom_path\"\n      project_path = Path.join(File.cwd!(), app)\n      Mix.Tasks.Phx.New.run([project_path, \"--umbrella\", \"--database\", \"sqlite3\"])\n\n      assert_file(app_path(app, \"mix.exs\"), \":ecto_sqlite3\")\n      assert_file(app_path(app, \"lib/custom_path/repo.ex\"), \"Ecto.Adapters.SQLite3\")\n\n      assert_file(app_path(app, \"lib/custom_path/application.ex\"), fn file ->\n        assert file =~ \"{Ecto.Migrator\"\n        assert file =~ \"repos: Application.fetch_env!(:custom_path, :ecto_repos)\"\n        assert file =~ \"skip: skip_migrations?()\"\n\n        assert file =~ \"defp skip_migrations?() do\"\n        assert file =~ ~s/System.get_env(\"RELEASE_NAME\") == nil/\n      end)\n\n      assert_file(root_path(app, \"config/dev.exs\"), [~r/database: .*_dev.db/])\n      assert_file(root_path(app, \"config/test.exs\"), [~r/database: .*_test.db/])\n      assert_file(root_path(app, \"config/runtime.exs\"), [~r/database: database_path/])\n\n      assert_file(web_path(app, \"test/support/conn_case.ex\"), \"DataCase.setup_sandbox(tags)\")\n\n      assert_file(root_path(app, \".gitignore\"), \"*.db\")\n      assert_file(root_path(app, \".gitignore\"), \"*.db-*\")\n    end)\n  end\n\n  test \"new with mssql adapter\" do\n    in_tmp(\"new with mssql adapter\", fn ->\n      app = \"custom_path\"\n      project_path = Path.join(File.cwd!(), app)\n      Mix.Tasks.Phx.New.run([project_path, \"--umbrella\", \"--database\", \"mssql\"])\n\n      assert_file(app_path(app, \"mix.exs\"), \":tds\")\n      assert_file(app_path(app, \"lib/custom_path/repo.ex\"), \"Ecto.Adapters.Tds\")\n\n      assert_file(root_path(app, \"config/dev.exs\"), [\n        ~r/username: \"sa\"/,\n        ~r/password: \"some!Password\"/\n      ])\n\n      assert_file(root_path(app, \"config/test.exs\"), [\n        ~r/username: \"sa\"/,\n        ~r/password: \"some!Password\"/\n      ])\n\n      assert_file(root_path(app, \"config/runtime.exs\"), [~r/url: database_url/])\n\n      assert_file(web_path(app, \"test/support/conn_case.ex\"), \"DataCase.setup_sandbox(tags)\")\n    end)\n  end\n\n  test \"new with invalid database adapter\" do\n    in_tmp(\"new with invalid database adapter\", fn ->\n      project_path = Path.join(File.cwd!(), \"custom_path\")\n\n      assert_raise Mix.Error, ~s(Unknown database \"invalid\"), fn ->\n        Mix.Tasks.Phx.New.run([project_path, \"--umbrella\", \"--database\", \"invalid\"])\n      end\n    end)\n  end\n\n  test \"new with cowboy web adapter\" do\n    in_tmp(\"new with cowboy web adapter\", fn ->\n      app = \"custom_path\"\n      project_path = Path.join(File.cwd!(), app)\n      Mix.Tasks.Phx.New.run([project_path, \"--umbrella\", \"--adapter\", \"cowboy\"])\n      assert_file(web_path(app, \"mix.exs\"), \":plug_cowboy\")\n\n      assert_file(root_path(app, \"config/config.exs\"), \"adapter: Phoenix.Endpoint.Cowboy2Adapter\")\n    end)\n  end\n\n  test \"new with invalid args\" do\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"007invalid\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"valid1\", \"--app\", \"007invalid\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Application name must start with a letter and \", fn ->\n      Mix.Tasks.Phx.New.run([\"valid1\", \"--app\", \"exInvalidAppAnme\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name must be a valid Elixir alias\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid2\", \"--module\", \"not.valid\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"string\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid3\", \"--app\", \"mix\", \"--umbrella\"])\n    end\n\n    assert_raise Mix.Error, ~r\"Module name \\w+ is already taken\", fn ->\n      Mix.Tasks.Phx.New.run([\"valid4\", \"--module\", \"String\", \"--umbrella\"])\n    end\n  end\n\n  test \"invalid options\" do\n    assert_raise OptionParser.ParseError, fn ->\n      Mix.Tasks.Phx.New.run([\"valid5\", \"-database\", \"mysql\", \"--umbrella\"])\n    end\n  end\n\n  test \"umbrella with --no-agents-md\" do\n    in_tmp(\"umbrella with no agents md\", fn ->\n      Mix.Tasks.Phx.New.run([@app, \"--umbrella\", \"--no-agents-md\"])\n\n      refute_file(root_path(@app, \"AGENTS.md\"))\n    end)\n  end\n\n  describe \"ecto task\" do\n    test \"can only be run within an umbrella app dir\", %{tmp_dir: tmp_dir} do\n      in_tmp(tmp_dir, fn ->\n        cwd = File.cwd!()\n        umbrella_path = root_path(@app)\n        Mix.Tasks.Phx.New.run([@app, \"--umbrella\"])\n        flush()\n\n        for dir <- [cwd, umbrella_path] do\n          File.cd!(dir, fn ->\n            assert_raise Mix.Error,\n                         ~r\"The ecto task can only be run within an umbrella's apps directory\",\n                         fn ->\n                           Mix.Tasks.Phx.New.Ecto.run([\"valid\"])\n                         end\n          end)\n        end\n      end)\n    end\n  end\n\n  describe \"web task\" do\n    test \"can only be run within an umbrella app dir\", %{tmp_dir: tmp_dir} do\n      in_tmp(tmp_dir, fn ->\n        cwd = File.cwd!()\n        umbrella_path = root_path(@app)\n        Mix.Tasks.Phx.New.run([@app, \"--umbrella\"])\n        flush()\n\n        for dir <- [cwd, umbrella_path] do\n          File.cd!(dir, fn ->\n            assert_raise Mix.Error,\n                         ~r\"The web task can only be run within an umbrella's apps directory\",\n                         fn ->\n                           Mix.Tasks.Phx.New.Web.run([\"valid\"])\n                         end\n          end)\n        end\n      end)\n    end\n\n    test \"generates web-only files\", %{tmp_dir: tmp_dir} do\n      in_tmp(tmp_dir, fn ->\n        umbrella_path = root_path(@app)\n        Mix.Tasks.Phx.New.run([@app, \"--umbrella\"])\n        flush()\n\n        File.cd!(Path.join(umbrella_path, \"apps\"))\n        decline_prompt()\n        Mix.Tasks.Phx.New.Web.run([\"another\"])\n\n        assert_file(\"another/README.md\")\n\n        assert_file(\"another/mix.exs\", fn file ->\n          assert file =~ \"app: :another\"\n          assert file =~ \"deps_path: \\\"../../deps\\\"\"\n          assert file =~ \"lockfile: \\\"../../mix.lock\\\"\"\n        end)\n\n        assert_file(\"../config/config.exs\", fn file ->\n          assert file =~ \"ecto_repos: [Another.Repo]\"\n        end)\n\n        assert_file(\"../config/prod.exs\", fn file ->\n          assert file =~ \"port: 80\"\n        end)\n\n        assert_file(\"../config/runtime.exs\", ~r/ip: {0, 0, 0, 0, 0, 0, 0, 0}/)\n\n        assert_file(\"another/lib/another/application.ex\", ~r/defmodule Another.Application do/)\n        assert_file(\"another/mix.exs\", ~r/mod: {Another.Application, \\[\\]}/)\n        assert_file(\"another/lib/another.ex\", ~r/defmodule Another do/)\n        assert_file(\"another/lib/another/endpoint.ex\", ~r/defmodule Another.Endpoint do/)\n\n        assert_file(\"another/test/another/controllers/page_controller_test.exs\")\n        assert_file(\"another/test/another/controllers/error_html_test.exs\")\n        assert_file(\"another/test/another/controllers/error_json_test.exs\")\n        assert_file(\"another/test/support/conn_case.ex\")\n        assert_file(\"another/test/test_helper.exs\")\n\n        assert_file(\n          \"another/lib/another/controllers/page_controller.ex\",\n          ~r/defmodule Another.PageController/\n        )\n\n        assert File.dir?(\"another/lib/another/controllers/page_html\")\n\n        assert_file(\n          \"another/lib/another/controllers/page_html.ex\",\n          ~r/defmodule Another.PageHTML/\n        )\n\n        assert_file(\"another/lib/another/router.ex\", \"defmodule Another.Router\")\n        assert_file(\"another/lib/another.ex\", \"defmodule Another\")\n        assert_file(\"another/lib/another/components/layouts/root.html.heex\")\n\n        # assets\n        assert_file(\"another/.gitignore\", ~r/\\n$/)\n        assert_file(\"another/priv/static/favicon.ico\")\n        assert_file(\"another/assets/css/app.css\")\n\n        refute File.exists?(\"another/priv/static/assets/css/app.css\")\n        refute File.exists?(\"another/priv/static/assets/js/app.js\")\n        assert File.exists?(\"another/assets/vendor\")\n\n        # Ecto\n        assert_file(\"another/mix.exs\", fn file ->\n          assert file =~ \"{:phoenix_ecto,\"\n        end)\n\n        assert_file(\"another/lib/another.ex\", ~r\"defmodule Another\")\n        refute_file(\"another/lib/another/repo.ex\")\n        refute_file(\"another/priv/repo/seeds.exs\")\n        refute_file(\"another/test/support/data_case.ex\")\n\n        # Install dependencies?\n        assert_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n        # Instructions\n        assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n        assert msg =~ \"$ cd another\"\n        assert msg =~ \"$ mix deps.get\"\n\n        refute_received {:mix_shell, :info, [\"Then configure your database\" <> _]}\n        assert_received {:mix_shell, :info, [\"Start your Phoenix app\" <> _]}\n\n        # Gettext\n        assert_file(\"another/lib/another/gettext.ex\", ~r\"defmodule Another.Gettext\")\n        assert File.exists?(\"another/priv/gettext/errors.pot\")\n        assert File.exists?(\"another/priv/gettext/en/LC_MESSAGES/errors.po\")\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "installer/test/phx_new_web_test.exs",
    "content": "Code.require_file \"mix_helper.exs\", __DIR__\n\ndefmodule Mix.Tasks.Phx.New.WebTest do\n  use ExUnit.Case\n  import MixHelper\n  import ExUnit.CaptureIO\n\n  @app_name \"phx_web\"\n\n  setup do\n    # The shell asks to install deps.\n    # We will politely say not.\n    send self(), {:mix_shell_input, :yes?, false}\n    :ok\n  end\n\n  test \"new without args\" do\n    assert capture_io(fn -> Mix.Tasks.Phx.New.Web.run([]) end) =~\n           \"Creates a new Phoenix web project within an umbrella project.\"\n  end\n\n  test \"new with barebones umbrella\" do\n    in_tmp_umbrella_project \"new with barebones umbrella\", fn ->\n      files = ~w[../config/dev.exs ../config/test.exs ../config/prod.exs ../config/runtime.exs]\n      Enum.each(files, &File.rm/1)\n\n      assert_file \"../config/config.exs\", &refute(&1 =~ ~S[import_config \"#{config_env()}.exs\"])\n      Mix.Tasks.Phx.New.Web.run([@app_name])\n      assert_file \"../config/config.exs\", &assert(&1 =~ ~S[import_config \"#{config_env()}.exs\"])\n    end\n  end\n\n  test \"new outside umbrella\", config do\n    in_tmp config.test, fn ->\n      assert_raise Mix.Error, ~r\"The web task can only be run within an umbrella's apps directory\", fn ->\n        Mix.Tasks.Phx.New.Web.run [\"007invalid\"]\n      end\n    end\n  end\n\n  test \"new with defaults\" do\n    in_tmp_umbrella_project \"new with defaults\", fn ->\n      Mix.Tasks.Phx.New.Web.run([@app_name])\n\n      assert_file \"../config/config.exs\", fn file ->\n        assert file =~ \"generators: [context_app: false]\"\n      end\n\n      assert_file \"#{@app_name}/mix.exs\", fn file ->\n        assert file =~ \"{:jason,\"\n      end\n\n      # Install dependencies?\n      assert_received {:mix_shell, :yes?, [\"\\nFetch and install dependencies?\"]}\n\n      # Instructions\n      assert_received {:mix_shell, :info, [\"\\nWe are almost there\" <> _ = msg]}\n      assert msg =~ \"$ cd phx_web\"\n      assert msg =~ \"$ mix deps.get\"\n\n      assert_received {:mix_shell, :info, [\"Your web app requires a PubSub server to be running.\" <> _]}\n\n      assert_received {:mix_shell, :info, [\"Start your Phoenix app\" <> _]}\n    end\n  end\n\n  test \"app_name is included in tailwind config\" do\n    in_tmp_umbrella_project \"new with defaults\", fn ->\n      Mix.Tasks.Phx.New.Web.run([\"testweb\"])\n\n      assert_file \"testweb/assets/vendor/heroicons.js\", fn file ->\n        assert file =~ \"/deps/heroicons/optimized\"\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "installer/test/test_helper.exs",
    "content": "ExUnit.start()\n"
  },
  {
    "path": "integration_test/README.md",
    "content": "## Phoenix Integration Tests\n\nThis project contains integration tests for phoenix's generated projects.\n\n## Running tests\n\nTo install dependencies, run:\n\n    $ mix deps.get\n\nThen run the basic test suite (no dependencies on the database) with:\n\n    $ mix test\n\nTo run the test suite with tests that test a specific database, run:\n\n    $ mix test --include database:postgresql\n    $ mix test --include database:mysql\n    $ mix test --include database:mssql\n    $ mix test --include database:sqlite3\n\nFor convenience, there is also a `docker-compose.yml` file that allows for starting up all of the supported databases locally.\n\n    $ docker-compose up\n\nThis allows all tests to be run with the following command\n\n    $ mix test --include database\n\nOr alternatively, with docker and docker compose installed, you can just run `./docker.sh`.\n\n## How tests are written\n\nIn order to have consistent, repeatable builds, all dependencies for all phoenix\nproject variations are listed in `mix.exs` and locked via `mix.lock`. If a\ndependency version needs to be updated, it can be updated with `mix.exs` or\nusing `mix deps.update <dep name>`.\n\nIt is also important to note that dependencies are initially compiled with\n`MIX_ENV=test` and then copied to `_build/dev_` to improve test speed.\nTherefore, dependencies should not be listed in `mix.exs` with an `only: <env>`\noption.\n"
  },
  {
    "path": "integration_test/config/config.exs",
    "content": "import Config\n\nconfig :phoenix, :json_library, Jason\n\nconfig :swoosh, api_client: false\n\nconfig :tailwind, :version, \"4.1.12\"\n\nconfig :phoenix_live_view, enable_expensive_runtime_checks: true\n"
  },
  {
    "path": "integration_test/docker-compose.yml",
    "content": "version: '3'\nservices:\n  postgres:\n    image: postgres\n    ports:\n      - \"5432:5432\"\n    environment:\n      POSTGRES_PASSWORD: postgres\n  mysql:\n    image: mysql\n    ports:\n      - \"3306:3306\"\n    environment:\n      MYSQL_ALLOW_EMPTY_PASSWORD: \"yes\"\n  mssql:\n    image: mcr.microsoft.com/mssql/server:2019-latest\n    environment:\n      ACCEPT_EULA: Y\n      SA_PASSWORD: some!Password\n    ports:\n      - \"1433:1433\"\n"
  },
  {
    "path": "integration_test/docker.sh",
    "content": "#!/usr/bin/env sh -e\n\n# adapt with versions from .github/versions/ci.yml if necessary;\n# you can also override these with environment variables\nELIXIR=\"${ELIXIR:-1.19.5}\"\nERLANG=\"${ERLANG:-28.3.3}\"\nSUFFIX=\"${SUFFIX:-alpine-3.20.9}\"\n\n# Get the directory of the script\nSCRIPT_DIR=$(dirname \"$(readlink -f \"$0\")\")\n\n# Get the parent directory\nPARENT_DIR=$(dirname \"$SCRIPT_DIR\")\n\n# Check if docker-compose is available\nif command -v docker-compose &> /dev/null\nthen\n    COMPOSE_CMD=\"docker-compose\"\nelif docker compose version &> /dev/null\nthen\n    COMPOSE_CMD=\"docker compose\"\nelse\n    echo \"Error: Neither docker-compose nor the docker compose plugin is available.\"\n    exit 1\nfi\n\n# Start databases\n$COMPOSE_CMD up -d\n\n# Run test script in container\ndocker run --rm --network=integration_test_default \\\n    -w $PARENT_DIR -v $PARENT_DIR:$PARENT_DIR \\\n    -it hexpm/elixir:$ELIXIR-erlang-$ERLANG-$SUFFIX sh integration_test/test.sh\n\n$COMPOSE_CMD down\n"
  },
  {
    "path": "integration_test/mix.exs",
    "content": "for path <- :code.get_path(),\n    Regex.match?(~r/phx_new-[\\w\\.\\-]+\\/ebin$/, List.to_string(path)) do\n  Code.delete_path(path)\nend\n\ndefmodule Phoenix.Integration.MixProject do\n  use Mix.Project\n\n  def project do\n    [\n      app: :phoenix_integration,\n      version: \"0.1.0\",\n      elixir: \"~> 1.15\",\n      elixirc_paths: elixirc_paths(Mix.env()),\n      start_permanent: Mix.env() == :prod,\n      deps: deps()\n    ]\n  end\n\n  defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  def application do\n    [\n      extra_applications: [:logger, :inets]\n    ]\n  end\n\n  # IMPORTANT: Dependencies are initially compiled with `MIX_ENV=test` and then\n  # copied to `_build/dev` to save time. Any dependencies with `only: :dev` set\n  # will not be copied.\n  defp deps do\n    [\n      {:phx_new, path: \"../installer\"},\n      {:phoenix, path: \"..\", override: true},\n      {:phoenix_ecto, \"~> 4.5\"},\n      {:esbuild, \"~> 0.10\", runtime: false},\n      {:ecto_sql, \"~> 3.13\"},\n      {:postgrex, \">= 0.0.0\"},\n      {:myxql, \">= 0.0.0\"},\n      {:tds, \">= 0.0.0\"},\n      {:ecto_sqlite3, \">= 0.0.0\"},\n      {:phoenix_html, \"~> 4.1\"},\n      {:phoenix_live_view, \"~> 1.1.0\"},\n      {:dns_cluster, \"~> 0.2.0\"},\n      {:lazy_html, \">= 0.1.0\"},\n      {:phoenix_live_reload, \"~> 1.2\"},\n      {:phoenix_live_dashboard, \"~> 0.8.3\"},\n      {:telemetry_metrics, \"~> 1.0\"},\n      {:telemetry_poller, \"~> 1.0\"},\n      {:gettext, \"~> 1.0\"},\n      {:jason, \"~> 1.2\"},\n      {:swoosh, \"~> 1.16\"},\n      {:bandit, \"~> 1.0\"},\n      {:bcrypt_elixir, \"~> 3.0\"},\n      {:argon2_elixir, \"~> 4.0\"},\n      {:pbkdf2_elixir, \"~> 2.0\"},\n      {:tailwind, \"~> 0.3\"},\n      {:heroicons,\n       github: \"tailwindlabs/heroicons\",\n       tag: \"v2.2.0\",\n       sparse: \"optimized\",\n       app: false,\n       compile: false,\n       depth: 1},\n      {:req, \"~> 0.5\"}\n    ]\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_defaults_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithDefaultsTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"new with defaults\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\")\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.html\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string status:enum:unpublished:published:deleted order:integer:unique), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.json\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", PhxBlogWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", PhxBlogWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.live\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--live\"])\n\n        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string p:boolean s:enum:a:b:c), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--live\"])\n\n        mix_run!(~w(phx.gen.live Blog Post posts title body:string public:boolean status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.auth + bcrypt\" do\n    test \"has no compilation or formatter warnings (--live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    test \"has no compilation or formatter warnings (--no-live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite (--live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite (--no-live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_mssql_adapter_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithMSSQLAdapterTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"phx.gen.html\" do\n    @tag database: :mssql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mssql_app\", [\"--database\", \"mssql\"])\n\n        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mssql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultMssqlAppWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.json\" do\n    @tag database: :mssql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mssql_app\", [\"--database\", \"mssql\"])\n\n        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mssql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", DefaultMssqlAppWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.live\" do\n    @tag database: :mssql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mssql_app\", [\"--database\", \"mssql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mssql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultMssqlAppWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.auth + pbkdf2 + existing context\" do\n    test \"has no compilation or formatter warnings (--live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mssql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/groups\", GroupController\n            end\n          \"\"\")\n        end)\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context --live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    test \"has no compilation or formatter warnings (--no-live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mssql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/groups\", GroupController\n            end\n          \"\"\")\n        end)\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context --no-live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :mssql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults (--live)\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mssql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/groups\", GroupController\n            end\n          \"\"\")\n        end)\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context --live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    @tag database: :mssql\n    test \"has a passing test suite (--no-live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mssql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.html Accounts Group groups name), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/phx_blog_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", PhxBlogWeb do\n              pipe_through [:browser]\n\n              resources \"/groups\", GroupController\n            end\n          \"\"\")\n        end)\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib pbkdf2 --merge-with-existing-context --no-live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_mysql_adapter_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithMySqlAdapterTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"phx.gen.html\" do\n    @tag database: :mysql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mysql_app\", [\"--database\", \"mysql\"])\n\n        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mysql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultMysqlAppWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.json\" do\n    @tag database: :mysql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mysql_app\", [\"--database\", \"mysql\"])\n\n        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mysql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", DefaultMysqlAppWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.live\" do\n    @tag database: :mysql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_mysql_app\", [\"--database\", \"mysql\", \"--live\"])\n\n        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_mysql_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultMysqlAppWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.auth + argon2\" do\n    test \"has no compilation or formatter warnings (--live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mysql\", \"--binary-id\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib argon2 --live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    test \"has no compilation or formatter warnings (--no-live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"mysql\", \"--binary-id\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib argon2 --no-live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :mysql\n    test \"has a passing test suite (--live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\", [\"--database\", \"mysql\", \"--binary-id\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib argon2 --live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    @tag database: :mysql\n    test \"has a passing test suite (--no-live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\", [\"--database\", \"mysql\", \"--binary-id\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --hashing-lib argon2 --no-live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_no_options_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithNoOptionsTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  @epoch {{1970, 1, 1}, {0, 0, 0}}\n\n  test \"newly generated app has no warnings or errors\" do\n    with_installer_tmp(\"app_with_no_options\", fn tmp_dir ->\n      {app_root_path, _} =\n        generate_phoenix_app(tmp_dir, \"phx_blog\", [\n          \"--no-html\",\n          \"--no-assets\",\n          \"--no-ecto\",\n          \"--no-gettext\",\n          \"--no-mailer\",\n          \"--no-dashboard\"\n        ])\n\n      assert_no_compilation_warnings(app_root_path)\n      assert_passes_formatter_check(app_root_path)\n      assert_tests_pass(app_root_path)\n    end)\n  end\n\n  test \"development workflow works as expected\" do\n    with_installer_tmp(\"development_workflow\", [autoremove?: false], fn tmp_dir ->\n      {app_root_path, _} =\n        generate_phoenix_app(tmp_dir, \"phx_blog\", [\n          \"--no-assets\",\n          \"--no-ecto\",\n          \"--no-gettext\",\n          \"--no-mailer\",\n          \"--no-dashboard\"\n        ])\n\n      assert_no_compilation_warnings(app_root_path)\n\n      File.touch!(Path.join(app_root_path, \"lib/phx_blog_web/components/core_components.ex\"), @epoch)\n      File.touch!(Path.join(app_root_path, \"lib/phx_blog_web/controllers/page_html.ex\"), @epoch)\n\n      spawn_link(fn ->\n        run_phx_server(app_root_path)\n      end)\n\n      :inets.start()\n      {:ok, response} = request_with_retries(\"http://localhost:4000\", 20)\n      assert response.status_code == 200\n      assert response.body =~ \"PhxBlog\"\n\n      assert File.stat!(Path.join(app_root_path, \"lib/phx_blog_web/components/core_components.ex\")) > @epoch\n      assert File.stat!(Path.join(app_root_path, \"lib/phx_blog_web/controllers/page_html.ex\")) > @epoch\n      assert_tests_pass(app_root_path)\n    end)\n  end\n\n  defp run_phx_server(app_root_path) do\n    {_output, 0} =\n      System.cmd(\n        \"elixir\",\n        [\n          \"--no-halt\",\n          \"-e\",\n          \"spawn fn -> IO.gets([]) && System.halt(0) end\",\n          \"-S\",\n          \"mix\",\n          \"phx.server\"\n        ],\n        cd: app_root_path\n      )\n  end\n\n  defp request_with_retries(url, retries)\n\n  defp request_with_retries(_url, 0), do: {:error, :out_of_retries}\n\n  defp request_with_retries(url, retries) do\n    case url |> to_charlist() |> :httpc.request() do\n      {:ok, httpc_response} ->\n        {{_, status_code, _}, raw_headers, body} = httpc_response\n\n        {:ok,\n         %{\n           status_code: status_code,\n           headers: for({k, v} <- raw_headers, do: {to_string(k), to_string(v)}),\n           body: to_string(body)\n         }}\n\n      {:error, {:failed_connect, _}} ->\n        Process.sleep(5_000)\n        request_with_retries(url, retries - 1)\n\n      {:error, reason} ->\n        {:error, reason}\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_scopes_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithScopesTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"phx.gen.auth\" do\n    test \"generates scope for phx.gen.live\" do\n      with_installer_tmp(\"scopes\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"scopes\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n        # we need to wait, otherwise we'd generate two migrations with the same version...\n        Process.sleep(1500)\n        mix_run!(~w(phx.gen.live Blog Post posts title:string), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          String.replace(\n            file,\n            \"\"\"\n            live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n            \"\"\",\n            \"\"\"\n            live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n\n                  live \"/posts\", PostLive.Index, :index\n                  live \"/posts/new\", PostLive.Form, :new\n                  live \"/posts/:id\", PostLive.Show, :show\n                  live \"/posts/:id/edit\", PostLive.Form, :edit\n            \"\"\"\n          )\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    test \"generates scope for phx.gen.html\" do\n      with_installer_tmp(\"scopes\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"scopes\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n        # we need to wait, otherwise we'd generate two migrations with the same version...\n        Process.sleep(1500)\n        mix_run!(~w(phx.gen.html Blog Post posts title:string), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          String.replace(\n            file,\n            \"\"\"\n            get \"/users/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n            \"\"\",\n            \"\"\"\n            get \"/users/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n\n                resources \"/posts\", PostController\n            \"\"\"\n          )\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    test \"generates scope for phx.gen.json\" do\n      with_installer_tmp(\"scopes\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"scopes\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n        # we need to wait, otherwise we'd generate two migrations with the same version...\n        Process.sleep(1500)\n        mix_run!(~w(phx.gen.json Blog Post posts title:string), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", ScopesWeb do\n              pipe_through [\n                :api,\n                :fetch_session,\n                :fetch_current_scope_for_user,\n                :require_authenticated_user\n              ]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"custom scope\" do\n    test \"route_prefix and route_access_path with all generators\" do\n      with_installer_tmp(\"scopes\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"scopes\", [\"--live\"])\n\n        # First generate authentication system\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n\n        # sleep to have fresh migration name\n        Process.sleep(1500)\n        mix_run!(~w(ecto.gen.migration AddOrganizationAndOrgUser), app_root_path)\n\n        assert [migration] =\n                 Path.wildcard(\n                   Path.join(\n                     app_root_path,\n                     \"priv/repo/migrations/*_add_organization_and_org_user.exs\"\n                   )\n                 )\n\n        File.write!(migration, \"\"\"\n        defmodule Scopes.Repo.Migrations.AddOrganizationAndOrgUser do\n          use Ecto.Migration\n\n          def change do\n            create table(:organizations) do\n              add :name, :string\n              add :slug, :string\n              add :user_id, references(:users, type: :id, on_delete: :delete_all)\n\n              timestamps(type: :utc_datetime)\n            end\n\n            create unique_index(:organizations, [:slug])\n\n            create table(:organizations_users) do\n              add :role, :string\n              add :organization_id, references(:organizations, on_delete: :nothing)\n              add :user_id, references(:users, on_delete: :delete_all)\n\n              timestamps(type: :utc_datetime)\n            end\n\n            create index(:organizations_users, [:organization_id])\n            create index(:organizations_users, [:user_id])\n          end\n        end\n        \"\"\")\n\n        File.write!(Path.join(app_root_path, \"lib/scopes/accounts/organization.ex\"), \"\"\"\n        defmodule Scopes.Accounts.Organization do\n          use Ecto.Schema\n          import Ecto.Changeset\n          alias Scopes.Accounts.User\n\n          @derive {Phoenix.Param, key: :slug}\n          schema \"organizations\" do\n            field :name, :string\n            field :slug, :string\n\n            many_to_many :users, User, join_through: \"organizations_users\"\n\n            timestamps(type: :utc_datetime)\n          end\n\n          @doc false\n          def changeset(organization, attrs) do\n            organization\n            |> cast(attrs, [:name, :slug])\n            |> validate_required([:name, :slug])\n            |> unique_constraint(:slug)\n          end\n        end\n        \"\"\")\n\n        File.write!(Path.join(app_root_path, \"lib/scopes/accounts/organization_user.ex\"), \"\"\"\n        defmodule Scopes.Accounts.OrganizationUser do\n          use Ecto.Schema\n          import Ecto.Changeset\n\n          schema \"organizations_users\" do\n            field :role, :string\n            belongs_to :organization, Scopes.Accounts.Organization\n            belongs_to :user, Scopes.Accounts.User\n\n            timestamps(type: :utc_datetime)\n          end\n\n          @doc false\n          def changeset(organization_user, attrs, user_scope \\\\\\\\ nil) do\n            organization_user\n            |> cast(attrs, [:role, :organization_id, :user_id])\n            |> validate_required([:role, :organization_id])\n            |> maybe_put_user_id(user_scope)\n          end\n\n          defp maybe_put_user_id(changeset, %{user: %{id: user_id}}) do\n            put_change(changeset, :user_id, user_id)\n          end\n\n          defp maybe_put_user_id(changeset, _), do: changeset\n        end\n        \"\"\")\n\n        # Update context\n        modify_file(Path.join(app_root_path, \"lib/scopes/accounts.ex\"), fn file ->\n          inject_before_final_end(file, ~S'''\n          ## Organization operations\n\n          alias Scopes.Accounts.{Organization, OrganizationUser}\n\n          @doc \"\"\"\n          Returns the list of organizations the user has access to.\n          \"\"\"\n          def list_organizations(scope) do\n            user_id = scope.user.id\n\n            OrganizationUser\n            |> where([ou], ou.user_id == ^user_id)\n            |> join(:inner, [ou], o in Organization, on: o.id == ou.organization_id)\n            |> select([_ou, o], o)\n            |> Repo.all()\n          end\n\n          @doc \"\"\"\n          Gets an organization by slug.\n          \"\"\"\n          def get_organization_by_slug!(scope, slug) when is_binary(slug) do\n            # This function would typically check if the user has access to the organization\n            # For this example, we'll just return the organization by slug\n            Repo.one!(\n              from o in Organization,\n                join: ou in OrganizationUser, on: ou.organization_id == o.id,\n                join: u in User, on: ou.user_id == u.id,\n                where: u.id == ^scope.user.id,\n                select: o\n            )\n          end\n\n          @doc \"\"\"\n          Creates an organization.\n          \"\"\"\n          def create_organization(scope, attrs \\\\ %{}) do\n            %Organization{}\n            |> Organization.changeset(attrs)\n            |> Repo.insert()\n            |> case do\n              {:ok, organization} ->\n                # Create a membership for the user who created the organization\n                %OrganizationUser{}\n                |> OrganizationUser.changeset(%{\n                  organization_id: organization.id,\n                  user_id: scope.user.id,\n                  role: \"owner\"\n                })\n                |> Repo.insert()\n\n                {:ok, organization}\n\n              error ->\n                error\n            end\n          end\n\n          ''')\n        end)\n\n        # Update Scope struct to include organization\n        modify_file(Path.join(app_root_path, \"lib/scopes/accounts/scope.ex\"), fn file ->\n          String.replace(file, \"defstruct user: nil\", \"defstruct user: nil, organization: nil\")\n          |> inject_before_final_end(\"\"\"\n          def put_organization(%__MODULE__{} = scope, %Scopes.Accounts.Organization{} = organization) do\n            %{scope | organization: organization}\n          end\n          \"\"\")\n        end)\n\n        # Update the fixtures to support organization_scope_fixture\n        modify_file(\n          Path.join(app_root_path, \"test/support/fixtures/accounts_fixtures.ex\"),\n          fn file ->\n            inject_before_final_end(file, \"\"\"\n\n            def valid_organization_attributes(attrs \\\\\\\\ %{}) do\n              Enum.into(attrs, %{\n                name: \"org\\#{System.unique_integer()}\",\n                slug: \"org\\#{System.unique_integer()}\"\n              })\n            end\n\n            def organization_fixture(scope \\\\\\\\ user_scope_fixture()) do\n              attrs = valid_organization_attributes()\n              {:ok, organization} = Accounts.create_organization(scope, attrs)\n              organization\n            end\n\n            def organization_scope_fixture(scope \\\\\\\\ user_scope_fixture()) do\n              org = organization_fixture(scope)\n              Scope.put_organization(scope, org)\n            end\n            \"\"\")\n          end\n        )\n\n        # Add the register_and_log_in_user_with_org helper to ConnCase\n        modify_file(Path.join(app_root_path, \"test/support/conn_case.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n          def register_and_log_in_user_with_org(context) do\n            %{conn: conn, user: user, scope: scope} = register_and_log_in_user(context)\n            scope = Scopes.AccountsFixtures.organization_scope_fixture(scope)\n            %{conn: conn, user: user, scope: scope}\n          end\n          \"\"\")\n        end)\n\n        # Modify the scopes config to include route_prefix and route_access_path for organization\n        modify_file(Path.join(app_root_path, \"config/config.exs\"), fn file ->\n          String.replace(\n            file,\n            \"\"\"\n            config :scopes, :scopes,\n              user: [\n                default: true,\n                module: Scopes.Accounts.Scope,\n                assign_key: :current_scope,\n                access_path: [:user, :id],\n                schema_key: :user_id,\n                schema_type: :id,\n                schema_table: :users,\n                test_data_fixture: Scopes.AccountsFixtures,\n                test_setup_helper: :register_and_log_in_user\n              ]\n            \"\"\",\n            \"\"\"\n            config :scopes, :scopes,\n            user: [\n              default: true,\n              module: Scopes.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:user, :id],\n              schema_key: :user_id,\n              schema_type: :id,\n              schema_table: :users,\n              test_data_fixture: Scopes.AccountsFixtures,\n              test_setup_helper: :register_and_log_in_user\n            ],\n            organization: [\n              module: Scopes.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:organization, :id],\n              route_prefix: \"/orgs/:slug\",\n              schema_key: :organization_id,\n              schema_type: :id,\n              schema_table: :organizations,\n              test_data_fixture: Scopes.AccountsFixtures,\n              test_setup_helper: :register_and_log_in_user_with_org\n            ]\n            \"\"\"\n          )\n        end)\n\n        # Extend the user auth module to assign org to scope\n        modify_file(\n          Path.join(app_root_path, \"lib/scopes_web/user_auth.ex\"),\n          fn file ->\n            inject_before_final_end(file, \"\"\"\n\n            def assign_org_to_scope(conn, _opts) do\n              current_scope = conn.assigns.current_scope\n              if slug = conn.params[\"slug\"] do\n                org = Scopes.Accounts.get_organization_by_slug!(current_scope, slug)\n                assign(conn, :current_scope, Scopes.Accounts.Scope.put_organization(current_scope, org))\n              else\n                conn\n              end\n            end\n            \"\"\")\n          end\n        )\n\n        # Add the LiveView hook for organization assignment\n        modify_file(\n          Path.join(app_root_path, \"lib/scopes_web/user_auth.ex\"),\n          fn file ->\n            String.replace(\n              file,\n              \"\"\"\n                def on_mount(:mount_current_scope, _params, session, socket) do\n                  {:cont, mount_current_scope(socket, session)}\n                end\n              \"\"\",\n              \"\"\"\n              def on_mount(:mount_current_scope, _params, session, socket) do\n                {:cont, mount_current_scope(socket, session)}\n              end\n\n              def on_mount(:assign_org_to_scope, %{\"slug\" => slug}, _session, socket) do\n                socket =\n                  case socket.assigns.current_scope do\n                    %{organization: nil} = scope ->\n                      org = Scopes.Accounts.get_organization_by_slug!(socket.assigns.current_scope, slug)\n                      Phoenix.Component.assign(socket, :current_scope, Scope.put_organization(scope, org))\n\n                    _ ->\n                      socket\n                  end\n\n                {:cont, socket}\n              end\n\n              def on_mount(:assign_org_to_scope, _params, _session, socket), do: {:cont, socket}\n              \"\"\"\n            )\n          end\n        )\n\n        # Update the router to use the assign_org_to_scope plug\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          String.replace(file, \"plug :fetch_current_scope_for_user\", \"\"\"\n          plug :fetch_current_scope_for_user\n          plug :assign_org_to_scope\n          \"\"\")\n        end)\n\n        # Generate resources with all three generators - use different contexts to avoid naming conflicts\n        Process.sleep(1500)\n\n        mix_run!(\n          ~w(phx.gen.html Blog1 Article articles title:string --scope organization),\n          app_root_path\n        )\n\n        Process.sleep(1500)\n\n        mix_run!(\n          ~w(phx.gen.live Blog2 Post posts title:string --scope organization),\n          app_root_path\n        )\n\n        Process.sleep(1500)\n\n        mix_run!(\n          ~w(phx.gen.json Blog3 Comment comments content:string --scope organization),\n          app_root_path\n        )\n\n        # Update LiveView routes in router.ex\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          String.replace(\n            file,\n            \"{ScopesWeb.UserAuth, :require_authenticated}\",\n            \"{ScopesWeb.UserAuth, :require_authenticated}, {ScopesWeb.UserAuth, :assign_org_to_scope}\"\n          )\n          |> String.replace(\n            \"live \\\"/users/settings/confirm-email/:token\\\", UserLive.Settings, :confirm_email\",\n            \"\"\"\n            live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n\n            live \"/orgs/:slug/posts\", PostLive.Index, :index\n            live \"/orgs/:slug/posts/new\", PostLive.Form, :new\n            live \"/orgs/:slug/posts/:id\", PostLive.Show, :show\n            live \"/orgs/:slug/posts/:id/edit\", PostLive.Form, :edit\n            \"\"\"\n          )\n        end)\n\n        # Add the HTML routes\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          String.replace(\n            file,\n            \"post \\\"/users/update-password\\\", UserSessionController, :update_password\",\n            \"\"\"\n            post \"/users/update-password\", UserSessionController, :update_password\n\n            resources \"/orgs/:slug/articles\", ArticleController\n            \"\"\"\n          )\n        end)\n\n        # Add the JSON API routes\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          router_code = \"\"\"\n\n          # API routes\n          scope \"/api\", ScopesWeb do\n            pipe_through [:api, :fetch_session, :fetch_current_scope_for_user, :assign_org_to_scope, :require_authenticated_user]\n\n            resources \"/orgs/:slug/comments\", CommentController, except: [:new, :edit]\n          end\n          \"\"\"\n\n          inject_before_final_end(file, router_code)\n        end)\n\n        # Test HTML generator (Articles)\n        assert_file(\n          Path.join(app_root_path, \"lib/scopes_web/controllers/article_html/index.html.heex\"),\n          fn file ->\n            assert file =~\n                     ~s|href={~p\"/orgs/\\#{@current_scope.organization}/articles/new\"|\n\n            assert file =~\n                     ~s|navigate={~p\"/orgs/\\#{@current_scope.organization}/articles/\\#{article}\"|\n          end\n        )\n\n        assert_file(\n          Path.join(app_root_path, \"lib/scopes_web/controllers/article_controller.ex\"),\n          fn file ->\n            assert file =~\n                     ~s|redirect(to: ~p\"/orgs/\\#{conn.assigns.current_scope.organization}/articles/\\#{article}\"|\n          end\n        )\n\n        assert_file(\n          Path.join(app_root_path, \"test/scopes_web/controllers/article_controller_test.exs\"),\n          fn file ->\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization}/articles\"|\n          end\n        )\n\n        # Test LiveView generator (Posts)\n        assert_file(Path.join(app_root_path, \"lib/scopes_web/live/post_live/index.ex\"), fn file ->\n          assert file =~\n                   ~s|navigate={~p\"/orgs/\\#{@current_scope.organization}/posts/new\"|\n\n          assert file =~\n                   ~s|JS.navigate(~p\"/orgs/\\#{@current_scope.organization}/posts/\\#{post}\")|\n        end)\n\n        assert_file(Path.join(app_root_path, \"lib/scopes_web/live/post_live/show.ex\"), fn file ->\n          assert file =~ ~s|navigate={~p\"/orgs/\\#{@current_scope.organization}/posts\"|\n        end)\n\n        assert_file(\n          Path.join(app_root_path, \"test/scopes_web/live/post_live_test.exs\"),\n          fn file ->\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization}/posts\"|\n          end\n        )\n\n        # Test JSON generator (Comments)\n        assert_file(\n          Path.join(app_root_path, \"lib/scopes_web/controllers/comment_controller.ex\"),\n          fn file ->\n            assert file =~\n                     ~s|~p\"/api/orgs/\\#{conn.assigns.current_scope.organization}/comments/\\#{comment}\"|\n          end\n        )\n\n        assert_file(\n          Path.join(app_root_path, \"test/scopes_web/controllers/comment_controller_test.exs\"),\n          fn file ->\n            assert file =~ ~s|~p\"/api/orgs/\\#{scope.organization}/comments\"|\n          end\n        )\n\n        # Final app validations\n        assert_no_compilation_warnings(app_root_path)\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    test \"phx.gen.json\" do\n      with_installer_tmp(\"scopes\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"scopes\")\n\n        modify_file(Path.join(app_root_path, \"config/config.exs\"), fn file ->\n          String.replace(file, \"import Config\", \"\"\"\n          import Config\n\n          config :scopes, :scopes,\n            user: [\n              default: false,\n              module: Scopes.UserScope,\n              assign_key: :user_scope,\n              access_path: [:u, :id],\n              schema_key: :user_id,\n              schema_type: :integer,\n              schema_migration_type: :bigint,\n              schema_table: nil,\n              test_data_fixture: Scopes.UserScopeFixtures,\n              test_setup_helper: :assign_scope\n            ]\\\n          \"\"\")\n        end)\n\n        mix_run!(~w(phx.gen.json Blog Post posts title:string --scope user), app_root_path)\n\n        File.write!(Path.join(app_root_path, \"test/support/fixtures/user_scope_fixtures.ex\"), \"\"\"\n        defmodule Scopes.UserScopeFixtures do\n          alias Scopes.UserScope\n\n          def user_scope_fixture(id \\\\\\\\ System.unique_integer()) do\n            %UserScope{u: %{id: id}}\n          end\n        end\n        \"\"\")\n\n        modify_file(Path.join(app_root_path, \"test/support/conn_case.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            def assign_scope(%{conn: conn}) do\n              id = System.unique_integer()\n              scope = Scopes.UserScopeFixtures.user_scope_fixture(id)\n\n              conn =\n                conn\n                |> Phoenix.ConnTest.init_test_session(%{})\n                |> Plug.Conn.put_session(:user_id, id)\n\n              %{conn: conn, scope: scope}\n            end\n          \"\"\")\n        end)\n\n        File.write!(Path.join(app_root_path, \"lib/scopes/user_scope.ex\"), \"\"\"\n        defmodule Scopes.UserScope do\n          defstruct u: nil\n\n          def new(attrs) do\n            %Scopes.UserScope{u: attrs.u}\n          end\n        end\n        \"\"\")\n\n        modify_file(Path.join(app_root_path, \"lib/scopes_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            defp assign_scope(conn, _opts) do\n              conn = Plug.Conn.fetch_session(conn)\n              id = Plug.Conn.get_session(conn, :user_id) || raise \"no user id found in session\"\n              assign(conn, :user_scope, Scopes.UserScope.new(%{u: %{id: id}}))\n            end\n\n            scope \"/api\", ScopesWeb do\n              pipe_through [:api, :assign_scope]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/app_with_sqlite3_adapter_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.AppWithSQLite3AdapterTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"phx.gen.html\" do\n    @tag database: :sqlite3\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_sqlite3_app\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_sqlite3_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultSqlite3AppWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.json\" do\n    @tag database: :sqlite3\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_sqlite3_app\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_sqlite3_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", DefaultSqlite3AppWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.live\" do\n    @tag database: :sqlite3\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"default_sqlite3_app\", [\"--database\", \"sqlite3\", \"--live\"])\n\n        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), app_root_path)\n\n        modify_file(Path.join(app_root_path, \"lib/default_sqlite3_app_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", DefaultSqlite3AppWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.auth + bcrypt\" do\n    test \"has no compilation or formatter warnings (--live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    test \"has no compilation or formatter warnings (--no-live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :sqlite3\n    test \"has a passing test suite (--live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    test \"has a passing test suite (--no-live)\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"default_app\", [\"--database\", \"sqlite3\"])\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), app_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/code_generation/umbrella_app_with_defaults_test.exs",
    "content": "defmodule Phoenix.Integration.CodeGeneration.UmbrellaAppWithDefaultsTest do\n  use Phoenix.Integration.CodeGeneratorCase, async: true\n\n  describe \"new umbrella app\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"phx_blog\", [\"--umbrella\"])\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} =\n          generate_phoenix_app(tmp_dir, \"umbrella_with_defaults\", [\"--umbrella\"])\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.html\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.html Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", RainyDayWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.html Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", RainyDayWeb do\n              pipe_through [:browser]\n\n              resources \"/posts\", PostController\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.json\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.json Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", RainyDayWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.json Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/api\", RainyDayWeb do\n              pipe_through [:api]\n\n              resources \"/posts\", PostController, except: [:new, :edit]\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.live\" do\n    test \"has no compilation or formatter warnings\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\", \"--live\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.live Blog Post posts title:unique body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", RainyDayWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite\" do\n      with_installer_tmp(\"umbrella_app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\", \"--live\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.live Blog Post posts title body:string status:enum:unpublished:published:deleted), web_root_path)\n\n        modify_file(Path.join(web_root_path, \"lib/rainy_day_web/router.ex\"), fn file ->\n          inject_before_final_end(file, \"\"\"\n\n            scope \"/\", RainyDayWeb do\n              pipe_through [:browser]\n\n              live \"/posts\", PostLive.Index, :index\n              live \"/posts/new\", PostLive.Form, :new\n              live \"/posts/:id\", PostLive.Show, :show\n              live \"/posts/:id/edit\", PostLive.Form, :edit\n            end\n          \"\"\")\n        end)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\n\n  describe \"phx.gen.auth + bcrypt\" do\n    test \"has no compilation or formatter warnings (--live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), web_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    test \"has no compilation or formatter warnings (--no-live)\" do\n      with_installer_tmp(\"new with defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), web_root_path)\n\n        assert_no_compilation_warnings(app_root_path)\n        assert_passes_formatter_check(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite --live\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --live), web_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n\n    @tag database: :postgresql\n    test \"has a passing test suite --no-live\" do\n      with_installer_tmp(\"app_with_defaults\", fn tmp_dir ->\n        {app_root_path, _} = generate_phoenix_app(tmp_dir, \"rainy_day\", [\"--umbrella\"])\n        web_root_path = Path.join(app_root_path, \"apps/rainy_day_web\")\n\n        mix_run!(~w(phx.gen.auth Accounts User users --no-live), web_root_path)\n\n        drop_test_database(app_root_path)\n        assert_tests_pass(app_root_path)\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "integration_test/test/support/code_generator_case.ex",
    "content": "defmodule Phoenix.Integration.CodeGeneratorCase do\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      import unquote(__MODULE__)\n    end\n  end\n\n  def generate_phoenix_app(tmp_dir, app_name, opts \\\\ [])\n      when is_binary(app_name) and is_list(opts) do\n    app_path = Path.expand(app_name, tmp_dir)\n    integration_test_root_path = Path.expand(\"../../\", __DIR__)\n    app_root_path = get_app_root_path(tmp_dir, app_name, opts)\n\n    output =\n      mix_run!([\"phx.new\", app_path, \"--dev\", \"--no-install\"] ++ opts, integration_test_root_path)\n\n    for path <- ~w(mix.lock deps _build) do\n      File.cp_r!(\n        Path.join(integration_test_root_path, path),\n        Path.join(app_root_path, path)\n      )\n    end\n\n    {app_root_path, output}\n  end\n\n  def mix_run!(args, app_path, opts \\\\ [])\n      when is_list(args) and is_binary(app_path) and is_list(opts) do\n    case mix_run(args, app_path, opts) do\n      {output, 0} ->\n        output\n\n      {output, exit_code} ->\n        raise \"\"\"\n        mix command failed with exit code: #{inspect(exit_code)}\n\n        mix #{Enum.join(args, \" \")}\n\n        #{output}\n\n        Options\n        cd: #{Path.expand(app_path)}\n        env: #{opts |> Keyword.get(:env, []) |> inspect()}\n        \"\"\"\n    end\n  end\n\n  def mix_run(args, app_path, opts \\\\ [])\n      when is_list(args) and is_binary(app_path) and is_list(opts) do\n    System.cmd(\"mix\", args, [stderr_to_stdout: true, cd: Path.expand(app_path)] ++ opts)\n  end\n\n  def assert_dir(path) do\n    assert File.dir?(path), \"Expected #{path} to be a directory, but is not\"\n  end\n\n  def assert_file(file) do\n    assert File.regular?(file), \"Expected #{file} to exist, but does not\"\n  end\n\n  def refute_file(file) do\n    refute File.regular?(file), \"Expected #{file} to not exist, but it does\"\n  end\n\n  def assert_file(file, match) do\n    cond do\n      is_list(match) ->\n        assert_file(file, &Enum.each(match, fn m -> assert &1 =~ m end))\n\n      is_binary(match) or is_struct(match, Regex) ->\n        assert_file(file, &assert(&1 =~ match))\n\n      is_function(match, 1) ->\n        assert_file(file)\n        match.(File.read!(file))\n\n      true ->\n        raise inspect({file, match})\n    end\n  end\n\n  def assert_tests_pass(app_path) do\n    mix_run!(~w(test), app_path)\n  end\n\n  def assert_passes_formatter_check(app_path) do\n    mix_run!(~w(format --check-formatted), app_path)\n  end\n\n  def assert_no_compilation_warnings(app_path) do\n    mix_run!([\"do\", \"clean,\", \"compile\", \"--warnings-as-errors\"], app_path)\n  end\n\n  def drop_test_database(app_path) when is_binary(app_path) do\n    mix_run!([\"ecto.drop\"], app_path, env: [{\"MIX_ENV\", \"test\"}])\n  end\n\n  def with_installer_tmp(name, opts \\\\ [], function)\n      when is_list(opts) and is_function(function, 1) do\n    autoremove? = Keyword.get(opts, :autoremove?, true)\n    path = Path.join([installer_tmp_path(), random_string(10), to_string(name)])\n\n    try do\n      File.rm_rf!(path)\n      File.mkdir_p!(path)\n      function.(path)\n    after\n      if autoremove?, do: File.rm_rf!(path)\n    end\n  end\n\n  defp installer_tmp_path do\n    Path.expand(\"../../../installer/tmp\", __DIR__)\n  end\n\n  def inject_before_final_end(code, code_to_inject)\n      when is_binary(code) and is_binary(code_to_inject) do\n    code\n    |> String.trim_trailing()\n    |> String.trim_trailing(\"end\")\n    |> Kernel.<>(code_to_inject)\n    |> Kernel.<>(\"end\\n\")\n  end\n\n  def modify_file(path, function) when is_binary(path) and is_function(function, 1) do\n    path\n    |> File.read!()\n    |> function.()\n    |> write_file!(path)\n  end\n\n  defp write_file!(content, path) do\n    File.write!(path, content)\n  end\n\n  defp get_app_root_path(tmp_dir, app_name, opts) do\n    app_root_dir =\n      if \"--umbrella\" in opts do\n        app_name <> \"_umbrella\"\n      else\n        app_name\n      end\n\n    Path.expand(app_root_dir, tmp_dir)\n  end\n\n  defp random_string(len) do\n    len |> :crypto.strong_rand_bytes() |> Base.url_encode64() |> binary_part(0, len)\n  end\nend\n"
  },
  {
    "path": "integration_test/test/test_helper.exs",
    "content": "# Copy _build/test to _build/dev so it only has to be compiled once\nFile.rm_rf!(Path.expand(\"../_build/dev\", __DIR__))\n\nFile.cp_r!(\n  Path.expand(\"../_build/test\", __DIR__),\n  Path.expand(\"../_build/dev\", __DIR__)\n)\n\nExUnit.configure(timeout: 180_000, exclude: [:database])\nExUnit.start()\n"
  },
  {
    "path": "integration_test/test.sh",
    "content": "#!/bin/sh -e\n\nmix local.rebar --force\nmix local.hex --force\n\n# Install Dependencies\napk add --no-progress --update git socat make gcc libc-dev cmake g++\n\n# Set up local proxies\nsocat TCP-LISTEN:5432,fork TCP-CONNECT:postgres:5432&\nsocat TCP-LISTEN:3306,fork TCP-CONNECT:mysql:3306&\nsocat TCP-LISTEN:1433,fork TCP-CONNECT:mssql:1433&\n\n# Run installer tests\necho \"Running installer tests\"\ncd installer\nmix deps.get\nmix test\n\necho \"Running integration tests\"\ncd ../integration_test\nmix deps.get\nmix test --include database\n"
  },
  {
    "path": "jest.config.js",
    "content": "/*\n * For a detailed explanation regarding each configuration property, visit:\n * https://jestjs.io/docs/configuration\n */\n\nmodule.exports = {\n  // Automatically clear mock calls and instances between every test\n  clearMocks: true,\n\n  // Indicates which provider should be used to instrument code for coverage\n  coverageProvider: \"v8\",\n\n  // The paths to modules that run some code to configure or set up the testing environment before each test\n  setupFiles: [\n    // \"<rootDir>/setupTests.js\"\n  ],\n\n  // The test environment that will be used for testing\n  testEnvironment: \"jest-environment-jsdom\",\n  \n  testEnvironmentOptions: {\n    url: \"https://example.com\"\n  },\n\n  // The regexp pattern or array of patterns that Jest uses to detect test files\n  testRegex: \"/assets/test/.*_test\\\\.js$\",\n}\n"
  },
  {
    "path": "lib/mix/phoenix/context.ex",
    "content": "defmodule Mix.Phoenix.Context do\n  @moduledoc false\n\n  alias Mix.Phoenix.{Context, Schema}\n\n  defstruct name: nil,\n            module: nil,\n            schema: nil,\n            alias: nil,\n            base_module: nil,\n            web_module: nil,\n            basename: nil,\n            file: nil,\n            test_file: nil,\n            test_fixtures_file: nil,\n            dir: nil,\n            generate?: true,\n            context_app: nil,\n            opts: [],\n            scope: nil\n\n  def valid?(context) do\n    context =~ ~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/\n  end\n\n  def new(context_name, opts) do\n    new(context_name, %Schema{}, opts)\n  end\n\n  def new(context_name, %Schema{} = schema, opts) do\n    ctx_app = opts[:context_app] || Mix.Phoenix.context_app()\n    base = Module.concat([Mix.Phoenix.context_base(ctx_app)])\n    module = Module.concat(base, context_name)\n    alias = Module.concat([module |> Module.split() |> List.last()])\n    basedir = Phoenix.Naming.underscore(context_name)\n    basename = Path.basename(basedir)\n    dir = Mix.Phoenix.context_lib_path(ctx_app, basedir)\n    file = dir <> \".ex\"\n    test_dir = Mix.Phoenix.context_test_path(ctx_app, basedir)\n    test_file = test_dir <> \"_test.exs\"\n    test_fixtures_dir = Mix.Phoenix.context_app_path(ctx_app, \"test/support/fixtures\")\n    test_fixtures_file = Path.join([test_fixtures_dir, basedir <> \"_fixtures.ex\"])\n    generate? = Keyword.get(opts, :context, true)\n\n    %Context{\n      name: context_name,\n      module: module,\n      schema: schema,\n      alias: alias,\n      base_module: base,\n      web_module: web_module(),\n      basename: basename,\n      file: file,\n      test_file: test_file,\n      test_fixtures_file: test_fixtures_file,\n      dir: dir,\n      generate?: generate?,\n      context_app: ctx_app,\n      opts: opts,\n      scope: schema.scope\n    }\n  end\n\n  def pre_existing?(%Context{file: file}), do: File.exists?(file)\n\n  def pre_existing_tests?(%Context{test_file: file}), do: File.exists?(file)\n\n  def pre_existing_test_fixtures?(%Context{test_fixtures_file: file}), do: File.exists?(file)\n\n  def function_count(%Context{file: file}) do\n    {_ast, count} =\n      file\n      |> File.read!()\n      |> Code.string_to_quoted!()\n      |> Macro.postwalk(0, fn\n        {:def, _, _} = node, count -> {node, count + 1}\n        {:defdelegate, _, _} = node, count -> {node, count + 1}\n        node, count -> {node, count}\n      end)\n\n    count\n  end\n\n  def file_count(%Context{dir: dir}) do\n    dir\n    |> Path.join(\"**/*.ex\")\n    |> Path.wildcard()\n    |> Enum.count()\n  end\n\n  defp web_module do\n    base = Mix.Phoenix.base()\n    cond do\n      Mix.Phoenix.context_app() != Mix.Phoenix.otp_app() ->\n        Module.concat([base])\n\n      String.ends_with?(base, \"Web\") ->\n        Module.concat([base])\n\n      true ->\n        Module.concat([\"#{base}Web\"])\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/phoenix/schema.ex",
    "content": "defmodule Mix.Phoenix.Schema do\n  @moduledoc false\n\n  alias Mix.Phoenix.Schema\n\n  defstruct module: nil,\n            repo: nil,\n            repo_alias: nil,\n            table: nil,\n            collection: nil,\n            embedded?: false,\n            generate?: true,\n            opts: [],\n            alias: nil,\n            file: nil,\n            attrs: [],\n            string_attr: nil,\n            plural: nil,\n            singular: nil,\n            uniques: [],\n            redacts: [],\n            assocs: [],\n            types: [],\n            indexes: [],\n            defaults: [],\n            human_singular: nil,\n            human_plural: nil,\n            binary_id: false,\n            migration_defaults: nil,\n            migration?: false,\n            params: %{},\n            optionals: [],\n            sample_id: nil,\n            web_path: nil,\n            web_namespace: nil,\n            context_app: nil,\n            route_helper: nil,\n            route_prefix: nil,\n            api_route_prefix: nil,\n            migration_module: nil,\n            fixture_unique_functions: [],\n            fixture_params: [],\n            prefix: nil,\n            timestamp_type: :naive_datetime,\n            scope: nil\n\n  @valid_types [\n    :integer,\n    :float,\n    :decimal,\n    :boolean,\n    :map,\n    :string,\n    :array,\n    :references,\n    :text,\n    :date,\n    :time,\n    :time_usec,\n    :naive_datetime,\n    :naive_datetime_usec,\n    :utc_datetime,\n    :utc_datetime_usec,\n    :uuid,\n    :binary,\n    :enum\n  ]\n\n  def valid_types, do: @valid_types\n\n  def valid?(schema) do\n    schema =~ ~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/\n  end\n\n  def new(schema_name, schema_plural, cli_attrs, opts) do\n    ctx_app = opts[:context_app] || Mix.Phoenix.context_app()\n    otp_app = Mix.Phoenix.otp_app()\n    opts = Keyword.merge(Application.get_env(otp_app, :generators, []), opts)\n    base = Mix.Phoenix.context_base(ctx_app)\n    basename = Phoenix.Naming.underscore(schema_name)\n    module = Module.concat([base, schema_name])\n    repo = opts[:repo] || Module.concat([base, \"Repo\"])\n    repo_alias = if String.ends_with?(Atom.to_string(repo), \".Repo\"), do: \"\", else: \", as: Repo\"\n    file = Mix.Phoenix.context_lib_path(ctx_app, basename <> \".ex\")\n    table = opts[:table] || schema_plural\n    scope = Mix.Phoenix.Scope.scope_from_opts(ctx_app, opts[:scope], opts[:no_scope])\n    {cli_attrs, uniques, redacts} = extract_attr_flags(cli_attrs)\n    {assocs, attrs} = partition_attrs_and_assocs(module, attrs(cli_attrs), scope)\n    types = types(attrs)\n    web_namespace = opts[:web] && Phoenix.Naming.camelize(opts[:web])\n    web_path = web_namespace && Phoenix.Naming.underscore(web_namespace)\n    api_prefix = Application.get_env(otp_app, :generators)[:api_prefix] || \"/api\"\n    embedded? = Keyword.get(opts, :embedded, false)\n    generate? = Keyword.get(opts, :schema, true)\n\n    singular =\n      module\n      |> Module.split()\n      |> List.last()\n      |> Phoenix.Naming.underscore()\n\n    collection = if schema_plural == singular, do: singular <> \"_collection\", else: schema_plural\n    string_attr = string_attr(types)\n    create_params = params(attrs, :create)\n\n    optionals = for {key, :map} <- types, do: key, into: []\n\n    default_params_key =\n      case Enum.at(create_params, 0) do\n        {key, _} -> key\n        nil -> :some_field\n      end\n\n    fixture_unique_functions = fixture_unique_functions(singular, uniques, attrs)\n\n    %Schema{\n      opts: opts,\n      migration?: Keyword.get(opts, :migration, true),\n      module: module,\n      repo: repo,\n      repo_alias: repo_alias,\n      table: table,\n      embedded?: embedded?,\n      alias: module |> Module.split() |> List.last() |> Module.concat(nil),\n      file: file,\n      attrs: attrs,\n      plural: schema_plural,\n      singular: singular,\n      collection: collection,\n      optionals: optionals,\n      assocs: assocs,\n      types: types,\n      defaults: schema_defaults(attrs),\n      uniques: uniques,\n      redacts: redacts,\n      indexes: indexes(table, assocs, uniques),\n      human_singular: Phoenix.Naming.humanize(singular),\n      human_plural: Phoenix.Naming.humanize(schema_plural),\n      binary_id: opts[:binary_id],\n      timestamp_type: opts[:timestamp_type] || :naive_datetime,\n      migration_defaults: migration_defaults(attrs),\n      string_attr: string_attr,\n      params: %{\n        create: create_params,\n        update: params(attrs, :update),\n        default_key: string_attr || default_params_key\n      },\n      web_namespace: web_namespace,\n      web_path: web_path,\n      route_helper: route_helper(web_path, singular),\n      route_prefix: route_prefix(web_path, schema_plural),\n      api_route_prefix: api_prefix,\n      sample_id: sample_id(opts),\n      context_app: ctx_app,\n      generate?: generate?,\n      migration_module: migration_module(),\n      fixture_unique_functions: Enum.sort(fixture_unique_functions),\n      fixture_params: fixture_params(attrs, fixture_unique_functions),\n      prefix: opts[:prefix],\n      scope: scope\n    }\n  end\n\n  @doc \"\"\"\n  Returns the string value of the default schema param.\n  \"\"\"\n  def default_param(%Schema{} = schema, action) do\n    schema.params\n    |> Map.fetch!(action)\n    |> Map.fetch!(schema.params.default_key)\n    |> to_string()\n  end\n\n  def extract_attr_flags(cli_attrs) do\n    {attrs, uniques, redacts} =\n      Enum.reduce(cli_attrs, {[], [], []}, fn attr, {attrs, uniques, redacts} ->\n        [attr_name | rest] = String.split(attr, \":\")\n        attr_name = String.to_atom(attr_name)\n        split_flags(Enum.reverse(rest), attr_name, attrs, uniques, redacts)\n      end)\n\n    {Enum.reverse(attrs), uniques, redacts}\n  end\n\n  defp split_flags([\"unique\" | rest], name, attrs, uniques, redacts),\n    do: split_flags(rest, name, attrs, [name | uniques], redacts)\n\n  defp split_flags([\"redact\" | rest], name, attrs, uniques, redacts),\n    do: split_flags(rest, name, attrs, uniques, [name | redacts])\n\n  defp split_flags(rest, name, attrs, uniques, redacts),\n    do: {[Enum.join([name | Enum.reverse(rest)], \":\") | attrs], uniques, redacts}\n\n  @doc \"\"\"\n  Parses the attrs as received by generators.\n  \"\"\"\n  def attrs(attrs) do\n    Enum.map(attrs, fn attr ->\n      attr\n      |> String.split(\":\", parts: 3)\n      |> list_to_attr()\n      |> validate_attr!()\n    end)\n  end\n\n  @doc \"\"\"\n  Generates some sample params based on the parsed attributes.\n  \"\"\"\n  def params(attrs, action \\\\ :create) when action in [:create, :update] do\n    Map.new(attrs, fn {k, t} -> {k, type_to_default(k, t, action)} end)\n  end\n\n  @doc \"\"\"\n  Converts the given value to map format when it's a date, time, datetime or naive_datetime.\n\n  Since `form.html.heex` generated by the live generator uses selects for dates and/or\n  times, fixtures must use map format for those fields in order to submit the live form.\n  \"\"\"\n  def live_form_value(%Date{} = date), do: Calendar.strftime(date, \"%Y-%m-%d\")\n\n  def live_form_value(%Time{} = time), do: Calendar.strftime(time, \"%H:%M\")\n\n  def live_form_value(%NaiveDateTime{} = naive) do\n    NaiveDateTime.to_iso8601(naive)\n  end\n\n  def live_form_value(%DateTime{} = naive) do\n    DateTime.to_iso8601(naive)\n  end\n\n  def live_form_value(value), do: value\n\n  @doc \"\"\"\n  Builds an invalid value for `@invalid_attrs` which is nil by default.\n\n  * In case the value is a list, this will return an empty array.\n  * In case the value is date, datetime, naive_datetime or time, this will return an invalid date.\n  * In case it is a boolean, we keep it as false\n  \"\"\"\n  def invalid_form_value(value) when is_list(value), do: []\n\n  def invalid_form_value(%{day: _day, month: _month, year: _year} = _date),\n    do: \"2022-00\"\n\n  def invalid_form_value(%{hour: _hour, minute: _minute}), do: %{hour: 14, minute: 00}\n  def invalid_form_value(true), do: false\n  def invalid_form_value(_value), do: nil\n\n  @doc \"\"\"\n  Generates an invalid error message according to the params present in the schema.\n  \"\"\"\n  def failed_render_change_message(_schema) do\n    \"can&#39;t be blank\"\n  end\n\n  def type_for_migration({:enum, _}), do: :string\n  def type_for_migration(other), do: other\n\n  def format_fields_for_schema(schema) do\n    Enum.map_join(schema.types, \"\\n\", fn {k, v} ->\n      \"    field #{inspect(k)}, #{type_and_opts_for_schema(v)}#{schema.defaults[k]}#{maybe_redact_field(k in schema.redacts)}\"\n    end)\n  end\n\n  @doc \"\"\"\n  Returns the required fields in the schema. Anything not in the `optionals` list\n  is considered required.\n  \"\"\"\n  def required_fields(schema) do\n    Enum.reject(schema.attrs, fn {key, _} -> key in schema.optionals end)\n  end\n\n  def type_and_opts_for_schema({:enum, opts}),\n    do: ~s|Ecto.Enum, values: #{inspect(Keyword.get(opts, :values))}|\n\n  def type_and_opts_for_schema(other), do: inspect(other)\n\n  def maybe_redact_field(true), do: \", redact: true\"\n  def maybe_redact_field(false), do: \"\"\n\n  @doc \"\"\"\n  Returns the string value for use in EEx templates.\n  \"\"\"\n  def value(schema, field, value) do\n    schema.types\n    |> Keyword.fetch!(field)\n    |> inspect_value(value)\n  end\n\n  defp inspect_value(:decimal, value), do: \"Decimal.new(\\\"#{value}\\\")\"\n  defp inspect_value(_type, value), do: inspect(value)\n\n  defp list_to_attr([key]), do: {String.to_atom(key), :string}\n  defp list_to_attr([key, value]), do: {String.to_atom(key), String.to_atom(value)}\n\n  defp list_to_attr([key, comp, value]) do\n    {String.to_atom(key), {String.to_atom(comp), String.to_atom(value)}}\n  end\n\n  @one_day_in_seconds 24 * 3600\n\n  defp type_to_default(key, t, :create) do\n    case t do\n      {:array, type} ->\n        build_array_values(type, :create)\n\n      {:enum, values} ->\n        build_enum_values(values, :create)\n\n      :integer ->\n        42\n\n      :float ->\n        120.5\n\n      :decimal ->\n        \"120.5\"\n\n      :boolean ->\n        true\n\n      :map ->\n        %{}\n\n      :text ->\n        \"some #{key}\"\n\n      :date ->\n        Date.add(Date.utc_today(), -1)\n\n      :time ->\n        ~T[14:00:00]\n\n      :time_usec ->\n        ~T[14:00:00.000000]\n\n      :uuid ->\n        \"7488a646-e31f-11e4-aace-600308960662\"\n\n      :utc_datetime ->\n        DateTime.add(\n          build_utc_datetime(),\n          -@one_day_in_seconds,\n          :second,\n          Calendar.UTCOnlyTimeZoneDatabase\n        )\n\n      :utc_datetime_usec ->\n        DateTime.add(\n          build_utc_datetime_usec(),\n          -@one_day_in_seconds,\n          :second,\n          Calendar.UTCOnlyTimeZoneDatabase\n        )\n\n      :naive_datetime ->\n        NaiveDateTime.add(build_utc_naive_datetime(), -@one_day_in_seconds)\n\n      :naive_datetime_usec ->\n        NaiveDateTime.add(build_utc_naive_datetime_usec(), -@one_day_in_seconds)\n\n      _ ->\n        \"some #{key}\"\n    end\n  end\n\n  defp type_to_default(key, t, :update) do\n    case t do\n      {:array, type} -> build_array_values(type, :update)\n      {:enum, values} -> build_enum_values(values, :update)\n      :integer -> 43\n      :float -> 456.7\n      :decimal -> \"456.7\"\n      :boolean -> false\n      :map -> %{}\n      :text -> \"some updated #{key}\"\n      :date -> Date.utc_today()\n      :time -> ~T[15:01:01]\n      :time_usec -> ~T[15:01:01.000000]\n      :uuid -> \"7488a646-e31f-11e4-aace-600308960668\"\n      :utc_datetime -> build_utc_datetime()\n      :utc_datetime_usec -> build_utc_datetime_usec()\n      :naive_datetime -> build_utc_naive_datetime()\n      :naive_datetime_usec -> build_utc_naive_datetime_usec()\n      _ -> \"some updated #{key}\"\n    end\n  end\n\n  defp build_array_values(:string, :create),\n    do: Enum.map([1, 2], &\"option#{&1}\")\n\n  defp build_array_values(:integer, :create),\n    do: [1, 2]\n\n  defp build_array_values(:string, :update),\n    do: [\"option1\"]\n\n  defp build_array_values(:integer, :update),\n    do: [1]\n\n  defp build_array_values(_, _),\n    do: []\n\n  defp build_enum_values(values, action) do\n    case {action, translate_enum_vals(values)} do\n      {:create, vals} -> hd(vals)\n      {:update, [val | []]} -> val\n      {:update, vals} -> vals |> tl() |> hd()\n    end\n  end\n\n  defp build_utc_datetime_usec,\n    do: %{DateTime.utc_now() | second: 0, microsecond: {0, 6}}\n\n  defp build_utc_datetime,\n    do: DateTime.truncate(build_utc_datetime_usec(), :second)\n\n  defp build_utc_naive_datetime_usec,\n    do: %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}}\n\n  defp build_utc_naive_datetime,\n    do: NaiveDateTime.truncate(build_utc_naive_datetime_usec(), :second)\n\n  @enum_missing_value_error \"\"\"\n  Enum type requires at least one value\n  For example:\n\n      mix phx.gen.schema Comment comments body:text status:enum:published:unpublished\n  \"\"\"\n\n  defp validate_attr!({name, :datetime}), do: {name, :naive_datetime}\n\n  defp validate_attr!({name, :array}) do\n    Mix.raise(\"\"\"\n    Phoenix generators expect the type of the array to be given to #{name}:array.\n    For example:\n\n        mix phx.gen.schema Post posts settings:array:string\n    \"\"\")\n  end\n\n  defp validate_attr!({_name, :enum}), do: Mix.raise(@enum_missing_value_error)\n  defp validate_attr!({_name, type} = attr) when type in @valid_types, do: attr\n  defp validate_attr!({_name, {type, _}} = attr) when type in @valid_types, do: attr\n\n  defp validate_attr!({_, type}) do\n    Mix.raise(\n      \"Unknown type `#{inspect(type)}` given to generator. \" <>\n        \"The supported types are: #{@valid_types |> Enum.sort() |> Enum.join(\", \")}\"\n    )\n  end\n\n  defp partition_attrs_and_assocs(schema_module, attrs, scope) do\n    {assocs, attrs} =\n      Enum.split_with(attrs, fn\n        {_, {:references, _}} ->\n          true\n\n        {key, :references} ->\n          Mix.raise(\"\"\"\n          Phoenix generators expect the table to be given to #{key}:references.\n          For example:\n\n              mix phx.gen.schema Comment comments body:text post_id:references:posts\n          \"\"\")\n\n        _ ->\n          false\n      end)\n\n    assocs =\n      Enum.map(assocs, fn {key_id, {:references, source}} ->\n        validate_scope_and_reference_conflict!(scope, key_id)\n\n        key = String.replace(Atom.to_string(key_id), \"_id\", \"\")\n        base = schema_module |> Module.split() |> Enum.drop(-1)\n        module = Module.concat(base ++ [Phoenix.Naming.camelize(key)])\n        {String.to_atom(key), key_id, inspect(module), source}\n      end)\n\n    {assocs, attrs}\n  end\n\n  defp validate_scope_and_reference_conflict!(\n         %Mix.Phoenix.Scope{schema_key: reference_key},\n         reference_key\n       ) do\n    Mix.raise(\"\"\"\n    Reference #{inspect(reference_key)} has the same name as the scope schema key, either skip the reference or pass it with the --no-scope flag.\n    \"\"\")\n  end\n\n  defp validate_scope_and_reference_conflict!(_scope, _source), do: :ok\n\n  defp schema_defaults(attrs) do\n    Enum.into(attrs, %{}, fn\n      {key, :boolean} -> {key, \", default: false\"}\n      {key, _} -> {key, \"\"}\n    end)\n  end\n\n  defp string_attr(types) do\n    Enum.find_value(types, fn\n      {key, :string} -> key\n      _ -> false\n    end)\n  end\n\n  defp types(attrs) do\n    Keyword.new(attrs, fn\n      {key, {:enum, vals}} -> {key, {:enum, values: translate_enum_vals(vals)}}\n      {key, {root, val}} -> {key, {root, schema_type(val)}}\n      {key, val} -> {key, schema_type(val)}\n    end)\n  end\n\n  def translate_enum_vals(vals) do\n    vals\n    |> Atom.to_string()\n    |> String.split(\":\")\n    |> Enum.map(&String.to_atom/1)\n  end\n\n  defp schema_type(:text), do: :string\n  defp schema_type(:uuid), do: Ecto.UUID\n\n  defp schema_type(val) do\n    if Code.ensure_loaded?(Ecto.Type) and not Ecto.Type.primitive?(val) do\n      Mix.raise(\"Unknown type `#{val}` given to generator\")\n    else\n      val\n    end\n  end\n\n  defp indexes(table, assocs, uniques) do\n    uniques = Enum.map(uniques, fn key -> {key, true} end)\n    assocs = Enum.map(assocs, fn {_, key, _, _} -> {key, false} end)\n\n    (uniques ++ assocs)\n    |> Enum.uniq_by(fn {key, _} -> key end)\n    |> Enum.map(fn\n      {key, false} -> \"create index(:#{table}, [:#{key}])\"\n      {key, true} -> \"create unique_index(:#{table}, [:#{key}])\"\n    end)\n  end\n\n  defp migration_defaults(attrs) do\n    Enum.into(attrs, %{}, fn\n      {key, :boolean} -> {key, \", default: false, null: false\"}\n      {key, _} -> {key, \"\"}\n    end)\n  end\n\n  defp sample_id(opts) do\n    if Keyword.get(opts, :binary_id, false) do\n      Keyword.get(opts, :sample_binary_id, \"11111111-1111-1111-1111-111111111111\")\n    else\n      -1\n    end\n  end\n\n  defp route_helper(web_path, singular) do\n    \"#{web_path}_#{singular}\"\n    |> String.trim_leading(\"_\")\n    |> String.replace(\"/\", \"_\")\n  end\n\n  defp route_prefix(web_path, plural) do\n    path = Path.join(for str <- [web_path, plural], do: to_string(str))\n    \"/\" <> String.trim_leading(path, \"/\")\n  end\n\n  defp migration_module do\n    case Application.get_env(:ecto_sql, :migration_module, Ecto.Migration) do\n      migration_module when is_atom(migration_module) -> migration_module\n      other -> Mix.raise(\"Expected :migration_module to be a module, got: #{inspect(other)}\")\n    end\n  end\n\n  defp fixture_unique_functions(singular, uniques, attrs) do\n    uniques\n    |> Enum.filter(&Keyword.has_key?(attrs, &1))\n    |> Enum.into(%{}, fn attr ->\n      function_name = \"unique_#{singular}_#{attr}\"\n\n      {function_def, needs_impl?} =\n        case Keyword.fetch!(attrs, attr) do\n          :integer ->\n            function_def =\n              \"\"\"\n                def #{function_name}, do: System.unique_integer([:positive])\n              \"\"\"\n\n            {function_def, false}\n\n          type when type in [:string, :text] ->\n            function_def =\n              \"\"\"\n                def #{function_name}, do: \"some #{attr}\\#{System.unique_integer([:positive])}\"\n              \"\"\"\n\n            {function_def, false}\n\n          _ ->\n            function_def =\n              \"\"\"\n                def #{function_name} do\n                  raise \"implement the logic to generate a unique #{singular} #{attr}\"\n                end\n              \"\"\"\n\n            {function_def, true}\n        end\n\n      {attr, {function_name, function_def, needs_impl?}}\n    end)\n  end\n\n  defp fixture_params(attrs, fixture_unique_functions) do\n    attrs\n    |> Enum.sort()\n    |> Enum.map(fn {attr, type} ->\n      case fixture_unique_functions do\n        %{^attr => {function_name, _function_def, _needs_impl?}} ->\n          {attr, \"#{function_name}()\"}\n\n        %{} ->\n          {attr, inspect(type_to_default(attr, type, :create))}\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "lib/mix/phoenix/scope.ex",
    "content": "defmodule Mix.Phoenix.Scope do\n  @moduledoc false\n\n  defstruct name: nil,\n            default: false,\n            module: nil,\n            alias: nil,\n            assign_key: nil,\n            access_path: nil,\n            route_prefix: nil,\n            route_access_path: nil,\n            schema_table: nil,\n            schema_key: nil,\n            schema_type: nil,\n            schema_migration_type: nil,\n            test_data_fixture: nil,\n            test_setup_helper: nil\n\n  @doc \"\"\"\n  Creates a new scope struct.\n  \"\"\"\n  def new!(name, opts) do\n    scope = struct!(__MODULE__, opts)\n    alias = Module.concat([scope.module |> Module.split() |> List.last()])\n\n    route_access_path =\n      case scope.route_access_path || Enum.drop(scope.access_path, -1) do\n        [] -> scope.access_path\n        rap -> rap\n      end\n\n    %{\n      scope\n      | name: name,\n        alias: alias,\n        route_access_path: route_access_path,\n        schema_migration_type: scope.schema_migration_type || scope.schema_type\n    }\n  end\n\n  @doc \"\"\"\n  Returns a `%{name: scope}` map of configured scopes.\n  \"\"\"\n  def scopes_from_config(otp_app) do\n    scopes = Application.get_env(otp_app, :scopes, [])\n\n    Map.new(scopes, fn {name, opts} -> {name, new!(name, opts)} end)\n  end\n\n  @doc \"\"\"\n  Returns the default scope.\n  \"\"\"\n  def default_scope(otp_app) do\n    case Enum.filter(scopes_from_config(otp_app), fn {_, scope} -> scope.default end) do\n      [{_name, scope}] ->\n        scope\n\n      [_ | _] = scopes ->\n        Mix.raise(\"\"\"\n        There can only be one default scope defined on your application, got:\n\n            * #{Enum.map(scopes, fn {name, _scope} -> name end) |> Enum.join(\"\\n    * \")}\n        \"\"\")\n\n      [] ->\n        nil\n    end\n  end\n\n  @doc \"\"\"\n  Returns the configured scope for the given --scope parameter.\n\n  Returns `nil` for `--no-scope` and raises if a specific scope is not configured.\n  \"\"\"\n  def scope_from_opts(_otp_app, bin, false) when is_binary(bin) do\n    Mix.raise(\"The --scope and --no-scope options must not be used together\")\n  end\n\n  def scope_from_opts(_otp_app, _name, true), do: nil\n\n  def scope_from_opts(otp_app, nil, _), do: default_scope(otp_app)\n\n  def scope_from_opts(otp_app, name, _) do\n    key = String.to_atom(name)\n    scopes = scopes_from_config(otp_app)\n\n    Map.get_lazy(scopes, key, fn ->\n      Mix.raise(\"\"\"\n      Scope :#{key} not configured!\n\n      Ensure that the scope :#{key} is configured in your application's config:\n\n          config :#{otp_app}, :scopes, [\n            #{key}: [\n              ...\n            ]\n          ]\n\n      Note that phx.gen.auth generates a default scope for you.\n      \"\"\")\n    end)\n  end\n\n  @doc \"\"\"\n  Generates a route prefix string with placeholders for the access path.\n\n  Takes a scope_key (what to use for accessing the scope) and a schema with scope information.\n  If the schema doesn't have a scope with route_prefix, returns an empty string.\n  Otherwise, it processes the route_prefix, replacing param segments with dynamic path elements.\n\n  ## Examples\n\n      route_prefix(\"socket.assigns.current_scope\", schema_with_scope)\n      # => \"/orgs/\\#{socket.assigns.current_scope.organization.slug}\"\n\n      route_prefix(\"@current_scope\", schema_with_scope)\n      # => \"/orgs/\\#{@current_scope.organization.slug}\"\n\n      route_prefix(\"scope\", schema_with_scope)\n      # => \"/orgs/\\#{scope.organization.slug}\"\n  \"\"\"\n  def route_prefix(\n        scope_key,\n        %{scope: %{route_prefix: route_prefix, route_access_path: route_access_path}} = _schema\n      )\n      when not is_nil(route_prefix) do\n    # Replace any path segment that starts with a colon with route_access_path from the scope\n    path_segments = String.split(route_prefix, \"/\", trim: true)\n    param_segments = Enum.filter(path_segments, &String.starts_with?(&1, \":\"))\n\n    if length(param_segments) > 1 do\n      Mix.raise(\n        \"The route_prefix option in scope configuration must contain only one parameter. Found: #{inspect(param_segments)}\"\n      )\n    end\n\n    path_with_placeholders =\n      path_segments\n      |> Enum.map(fn segment ->\n        if String.starts_with?(segment, \":\") do\n          # Extract parameter name without the colon\n          access_string = Enum.join(route_access_path, \".\")\n          \"\\#{#{scope_key}.#{access_string}}\"\n        else\n          segment\n        end\n      end)\n      |> Enum.join(\"/\")\n\n    \"/#{path_with_placeholders}\"\n  end\n\n  def route_prefix(_scope_key, _schema), do: \"\"\nend\n"
  },
  {
    "path": "lib/mix/phoenix.ex",
    "content": "defmodule Mix.Phoenix do\n  # Conveniences for Phoenix tasks.\n  @moduledoc false\n\n  @doc \"\"\"\n  Evals EEx files from source dir.\n\n  Files are evaluated against EEx according to\n  the given binding.\n  \"\"\"\n  def eval_from(apps, source_file_path, binding) do\n    sources = Enum.map(apps, &to_app_source(&1, source_file_path))\n\n    content =\n      Enum.find_value(sources, fn source ->\n        File.exists?(source) && File.read!(source)\n      end) || raise \"could not find #{source_file_path} in any of the sources\"\n\n    EEx.eval_string(content, binding)\n  end\n\n  @doc \"\"\"\n  Copies files from source dir to target dir\n  according to the given map.\n\n  Files are evaluated against EEx according to\n  the given binding.\n  \"\"\"\n  def copy_from(apps, source_dir, binding, mapping) when is_list(mapping) do\n    roots = Enum.map(apps, &to_app_source(&1, source_dir))\n\n    binding =\n      Keyword.merge(binding,\n        maybe_heex_attr_gettext: &maybe_heex_attr_gettext/2,\n        maybe_eex_gettext: &maybe_eex_gettext/2\n      )\n\n    for {format, source_file_path, target} <- mapping do\n      source =\n        Enum.find_value(roots, fn root ->\n          source = Path.join(root, source_file_path)\n          # for backwards compatibility, we also check for files with missing .eex extension\n          source_with_stripped_eex = String.replace_suffix(source, \".eex\", \"\")\n\n          cond do\n            File.exists?(source) -> source\n            File.exists?(source_with_stripped_eex) -> source_with_stripped_eex\n            true -> nil\n          end\n        end) || raise \"could not find #{source_file_path} in any of the sources\"\n\n      case format do\n        :text ->\n          Mix.Generator.create_file(target, File.read!(source))\n\n        :eex ->\n          Mix.Generator.create_file(target, EEx.eval_file(source, binding))\n\n        :new_eex ->\n          if File.exists?(target) do\n            :ok\n          else\n            Mix.Generator.create_file(target, EEx.eval_file(source, binding))\n          end\n      end\n    end\n  end\n\n  defp to_app_source(path, source_dir) when is_binary(path),\n    do: Path.join(path, source_dir)\n\n  defp to_app_source(app, source_dir) when is_atom(app),\n    do: Application.app_dir(app, source_dir)\n\n  @doc \"\"\"\n  Inflects path, scope, alias and more from the given name.\n\n  ## Examples\n\n      iex> Mix.Phoenix.inflect(\"user\")\n      [alias: \"User\",\n       human: \"User\",\n       base: \"Phoenix\",\n       web_module: \"PhoenixWeb\",\n       module: \"Phoenix.User\",\n       scoped: \"User\",\n       singular: \"user\",\n       path: \"user\"]\n\n      iex> Mix.Phoenix.inflect(\"Admin.User\")\n      [alias: \"User\",\n       human: \"User\",\n       base: \"Phoenix\",\n       web_module: \"PhoenixWeb\",\n       module: \"Phoenix.Admin.User\",\n       scoped: \"Admin.User\",\n       singular: \"user\",\n       path: \"admin/user\"]\n\n      iex> Mix.Phoenix.inflect(\"Admin.SuperUser\")\n      [alias: \"SuperUser\",\n       human: \"Super user\",\n       base: \"Phoenix\",\n       web_module: \"PhoenixWeb\",\n       module: \"Phoenix.Admin.SuperUser\",\n       scoped: \"Admin.SuperUser\",\n       singular: \"super_user\",\n       path: \"admin/super_user\"]\n\n  \"\"\"\n  def inflect(singular) do\n    base = Mix.Phoenix.base()\n    web_module = base |> web_module() |> inspect()\n    scoped = Phoenix.Naming.camelize(singular)\n    path = Phoenix.Naming.underscore(scoped)\n    singular = String.split(path, \"/\") |> List.last()\n    module = Module.concat(base, scoped) |> inspect\n    alias = String.split(module, \".\") |> List.last()\n    human = Phoenix.Naming.humanize(singular)\n\n    [\n      alias: alias,\n      human: human,\n      base: base,\n      web_module: web_module,\n      module: module,\n      scoped: scoped,\n      singular: singular,\n      path: path\n    ]\n  end\n\n  @doc \"\"\"\n  Checks the availability of a given module name.\n  \"\"\"\n  def check_module_name_availability!(name) do\n    name = Module.concat(Elixir, name)\n\n    if Code.ensure_loaded?(name) do\n      Mix.raise(\"Module name #{inspect(name)} is already taken, please choose another name\")\n    end\n  end\n\n  @doc \"\"\"\n  Returns the module base name based on the configuration value.\n\n      config :my_app\n        namespace: My.App\n\n  \"\"\"\n  def base do\n    app_base(otp_app())\n  end\n\n  @doc \"\"\"\n  Returns the context module base name based on the configuration value.\n\n      config :my_app\n        namespace: My.App\n\n  \"\"\"\n  def context_base(ctx_app) do\n    app_base(ctx_app)\n  end\n\n  defp app_base(app) do\n    case Application.get_env(app, :namespace, app) do\n      ^app -> app |> to_string() |> Phoenix.Naming.camelize()\n      mod -> mod |> inspect()\n    end\n  end\n\n  @doc \"\"\"\n  Returns the OTP app from the Mix project configuration.\n  \"\"\"\n  def otp_app do\n    Mix.Project.config() |> Keyword.fetch!(:app)\n  end\n\n  @doc \"\"\"\n  Returns all compiled modules in a project.\n  \"\"\"\n  def modules do\n    Mix.Project.compile_path()\n    |> Path.join(\"*.beam\")\n    |> Path.wildcard()\n    |> Enum.map(&beam_to_module/1)\n  end\n\n  defp beam_to_module(path) do\n    path |> Path.basename(\".beam\") |> String.to_atom()\n  end\n\n  @doc \"\"\"\n  The paths to look for template files for generators.\n\n  Defaults to checking the current app's `priv` directory,\n  and falls back to Phoenix's `priv` directory.\n  \"\"\"\n  def generator_paths do\n    [\".\", :phoenix]\n  end\n\n  @doc \"\"\"\n  Checks if the given `app_path` is inside an umbrella.\n  \"\"\"\n  def in_umbrella?(app_path) do\n    umbrella = Path.expand(Path.join([app_path, \"..\", \"..\"]))\n    mix_path = Path.join(umbrella, \"mix.exs\")\n    apps_path = Path.join(umbrella, \"apps\")\n    File.exists?(mix_path) && File.exists?(apps_path)\n  end\n\n  @doc \"\"\"\n  Returns the web prefix to be used in generated file specs.\n  \"\"\"\n  def web_path(ctx_app, rel_path \\\\ \"\") when is_atom(ctx_app) do\n    this_app = otp_app()\n\n    if ctx_app == this_app do\n      Path.join([\"lib\", \"#{this_app}_web\", rel_path])\n    else\n      Path.join([\"lib\", to_string(this_app), rel_path])\n    end\n  end\n\n  @doc \"\"\"\n  Returns the context app path prefix to be used in generated context files.\n  \"\"\"\n  def context_app_path(ctx_app, rel_path) when is_atom(ctx_app) do\n    this_app = otp_app()\n\n    if ctx_app == this_app do\n      rel_path\n    else\n      app_path =\n        case Application.get_env(this_app, :generators)[:context_app] do\n          {^ctx_app, path} -> Path.relative_to_cwd(path)\n          _ -> mix_app_path(ctx_app, this_app)\n        end\n\n      Path.join(app_path, rel_path)\n    end\n  end\n\n  @doc \"\"\"\n  Returns the context lib path to be used in generated context files.\n  \"\"\"\n  def context_lib_path(ctx_app, rel_path) when is_atom(ctx_app) do\n    context_app_path(ctx_app, Path.join([\"lib\", to_string(ctx_app), rel_path]))\n  end\n\n  @doc \"\"\"\n  Returns the context test path to be used in generated context files.\n  \"\"\"\n  def context_test_path(ctx_app, rel_path) when is_atom(ctx_app) do\n    context_app_path(ctx_app, Path.join([\"test\", to_string(ctx_app), rel_path]))\n  end\n\n  @doc \"\"\"\n  Returns the OTP context app.\n  \"\"\"\n  def context_app do\n    this_app = otp_app()\n\n    case fetch_context_app(this_app) do\n      {:ok, app} -> app\n      :error -> this_app\n    end\n  end\n\n  @doc \"\"\"\n  Returns the test prefix to be used in generated file specs.\n  \"\"\"\n  def web_test_path(ctx_app, rel_path \\\\ \"\") when is_atom(ctx_app) do\n    this_app = otp_app()\n\n    if ctx_app == this_app do\n      Path.join([\"test\", \"#{this_app}_web\", rel_path])\n    else\n      Path.join([\"test\", to_string(this_app), rel_path])\n    end\n  end\n\n  defp fetch_context_app(this_otp_app) do\n    case Application.get_env(this_otp_app, :generators)[:context_app] do\n      nil ->\n        :error\n\n      false ->\n        Mix.raise(\"\"\"\n        no context_app configured for current application #{this_otp_app}.\n\n        Add the context_app generators config in config.exs, or pass the\n        --context-app option explicitly to the generators. For example:\n\n        via config:\n\n            config :#{this_otp_app}, :generators,\n              context_app: :some_app\n\n        via cli option:\n\n            mix phx.gen.[task] --context-app some_app\n\n        Note: cli option only works when `context_app` is not set to `false`\n        in the config.\n        \"\"\")\n\n      {app, _path} ->\n        {:ok, app}\n\n      app ->\n        {:ok, app}\n    end\n  end\n\n  defp mix_app_path(app, this_otp_app) do\n    case Mix.Project.deps_paths() do\n      %{^app => path} ->\n        Path.relative_to_cwd(path)\n\n      deps ->\n        Mix.raise(\"\"\"\n        no directory for context_app #{inspect(app)} found in #{this_otp_app}'s deps.\n\n        Ensure you have listed #{inspect(app)} as an in_umbrella dependency in mix.exs:\n\n            def deps do\n              [\n                {:#{app}, in_umbrella: true},\n                ...\n              ]\n            end\n\n        Existing deps:\n\n            #{inspect(Map.keys(deps))}\n\n        \"\"\")\n    end\n  end\n\n  @doc \"\"\"\n  Prompts to continue if any files exist.\n  \"\"\"\n  def prompt_for_conflicts(generator_files) do\n    file_paths =\n      Enum.flat_map(generator_files, fn\n        {:new_eex, _, _path} -> []\n        {_kind, _, path} -> [path]\n      end)\n\n    case Enum.filter(file_paths, &File.exists?(&1)) do\n      [] ->\n        :ok\n\n      conflicts ->\n        Mix.shell().info(\"\"\"\n        The following files conflict with new files to be generated:\n\n        #{Enum.map_join(conflicts, \"\\n\", &\"  * #{&1}\")}\n\n        See the --web option to namespace similarly named resources\n        \"\"\")\n\n        unless Mix.shell().yes?(\"Proceed with interactive overwrite?\") do\n          System.halt()\n        end\n    end\n  end\n\n  @doc \"\"\"\n  Returns the web module prefix.\n  \"\"\"\n  def web_module(base) do\n    if base |> to_string() |> String.ends_with?(\"Web\") do\n      Module.concat([base])\n    else\n      Module.concat([\"#{base}Web\"])\n    end\n  end\n\n  def to_text(data) do\n    inspect(data, limit: :infinity, printable_limit: :infinity)\n  end\n\n  def prepend_newline(string) do\n    \"\\n\" <> string\n  end\n\n  @doc \"\"\"\n  Ensures user's LiveView is compatible with the current generators.\n  \"\"\"\n  def ensure_live_view_compat!(generator_mod) do\n    vsn = Application.spec(:phoenix_live_view)[:vsn]\n\n    # if lv is not installed, such as in phoenix's own test env, do not raise\n    if vsn && Version.compare(\"#{vsn}\", \"1.0.0-rc.7\") != :gt do\n      raise \"#{inspect(generator_mod)} requires :phoenix_live_view >= 1.0.0, got: #{vsn}\"\n    end\n  end\n\n  # In the context of a HEEx attribute value, transforms a given message into a\n  # dynamic `gettext` call or a fixed-value string attribute, depending on the\n  # `gettext?` parameter.\n  #\n  # ## Examples\n  #\n  #     iex> ~s|<tag attr=#{maybe_heex_attr_gettext(\"Hello\", true)} />|\n  #     ~S|<tag attr={gettext(\"Hello\")} />|\n  #\n  #     iex> ~s|<tag attr=#{maybe_heex_attr_gettext(\"Hello\", false)} />|\n  #     ~S|<tag attr=\"Hello\" />|\n  defp maybe_heex_attr_gettext(message, gettext?) do\n    if gettext? do\n      ~s|{gettext(#{inspect(message)})}|\n    else\n      inspect(message)\n    end\n  end\n\n  # In the context of an EEx template, transforms a given message into a dynamic\n  # `gettext` call or the message as is, depending on the `gettext?` parameter.\n  #\n  # ## Examples\n  #\n  #     iex> ~s|<tag>#{maybe_eex_gettext(\"Hello\", true)}</tag>|\n  #     ~S|<tag><%= gettext(\"Hello\") %></tag>|\n  #\n  #     iex> ~s|<tag>#{maybe_eex_gettext(\"Hello\", false)}</tag>|\n  #     ~S|<tag>Hello</tag>|\n  defp maybe_eex_gettext(message, gettext?) do\n    if gettext? do\n      ~s|<%= gettext(#{inspect(message)}) %>|\n    else\n      message\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/compile.phoenix.ex",
    "content": "defmodule Mix.Tasks.Compile.Phoenix do\n  use Mix.Task\n  @recursive true\n  @moduledoc false\n\n  @doc false\n  def run(_args) do\n    IO.warn(\"\"\"\n    the :phoenix compiler is no longer required in your mix.exs.\n\n    Please find the following line in your mix.exs and remove the :phoenix entry:\n\n        compilers: [..., :phoenix, ...] ++ Mix.compilers(),\n    \"\"\")\n\n    {:ok, _} = Application.ensure_all_started(:phoenix)\n\n    case touch() do\n      [] -> {:noop, []}\n      _ -> {:ok, []}\n    end\n  end\n\n  @doc false\n  def touch do\n    Mix.Phoenix.modules()\n    |> modules_for_recompilation\n    |> modules_to_file_paths\n    |> Stream.map(&touch_if_exists(&1))\n    |> Stream.filter(&(&1 == :ok))\n    |> Enum.to_list()\n  end\n\n  defp touch_if_exists(path) do\n    :file.change_time(path, :calendar.local_time())\n  end\n\n  defp modules_for_recompilation(modules) do\n    Stream.filter(modules, fn mod ->\n      Code.ensure_loaded?(mod) and phoenix_recompile?(mod)\n    end)\n  end\n\n  defp phoenix_recompile?(mod) do\n    function_exported?(mod, :__phoenix_recompile__?, 0) and mod.__phoenix_recompile__?()\n  end\n\n  defp modules_to_file_paths(modules) do\n    Stream.map(modules, fn mod -> mod.__info__(:compile)[:source] end)\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.digest.clean.ex",
    "content": "defmodule Mix.Tasks.Phx.Digest.Clean do\n  use Mix.Task\n  @default_output_path \"priv/static\"\n  @default_age 3600\n  @default_keep 2\n\n  @shortdoc \"Removes old versions of static assets.\"\n  @recursive true\n\n  @moduledoc \"\"\"\n  Removes old versions of compiled assets.\n\n  By default, it will keep the latest version and\n  2 previous versions as well as any digest created\n  in the last hour.\n\n      $ mix phx.digest.clean\n      $ mix phx.digest.clean -o /www/public\n      $ mix phx.digest.clean --age 600 --keep 3\n      $ mix phx.digest.clean --all\n\n  ## Options\n\n    * `-o, --output` - indicates the path to your compiled\n      assets directory. Defaults to `priv/static`\n\n    * `--age` - specifies a maximum age (in seconds) for assets.\n      Files older than age that are not in the last `--keep` versions\n      will be removed. Defaults to 3600 (1 hour)\n\n    * `--keep` - specifies how many previous versions of assets to keep.\n      Defaults to 2 previous versions\n\n    * `--all` - specifies that all compiled assets (including the manifest)\n      will be removed. Note this overrides the age and keep switches.\n\n    * `--no-compile` - do not run mix compile\n  \"\"\"\n\n  @switches [output: :string, age: :integer, keep: :integer, all: :boolean]\n\n  @doc false\n  def run(all_args) do\n    # Ensure all compressors are compiled.\n    if \"--no-compile\" not in all_args do\n      Mix.Task.run(\"compile\", all_args)\n    end\n\n    Mix.Task.reenable(\"phx.digest.clean\")\n\n    {:ok, _} = Application.ensure_all_started(:phoenix)\n\n    {opts, _, _} = OptionParser.parse(all_args, switches: @switches, aliases: [o: :output])\n    output_path = opts[:output] || @default_output_path\n    age = opts[:age] || @default_age\n    keep = opts[:keep] || @default_keep\n    all? = opts[:all] || false\n\n    result =\n      if all?,\n        do: Phoenix.Digester.clean_all(output_path),\n        else: Phoenix.Digester.clean(output_path, age, keep)\n\n    case result do\n      :ok ->\n        # We need to call build structure so everything we have cleaned from\n        # priv is removed from _build in case we have build_embedded set to\n        # true. In case it's not true, build structure is mostly a no-op, so we\n        # are fine.\n        Mix.Project.build_structure()\n        Mix.shell().info [:green, \"Clean complete for #{inspect output_path}\"]\n      {:error, :invalid_path} ->\n        Mix.shell().error \"The output path #{inspect output_path} does not exist\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.digest.ex",
    "content": "defmodule Mix.Tasks.Phx.Digest do\n  use Mix.Task\n  @default_input_path \"priv/static\"\n\n  @shortdoc \"Digests and compresses static files\"\n  @recursive true\n\n  @moduledoc \"\"\"\n  Digests and compresses static files.\n\n      $ mix phx.digest\n      $ mix phx.digest priv/static -o /www/public\n\n  The first argument is the path where the static files are located. The\n  `-o` option indicates the path that will be used to save the digested and\n  compressed files.\n\n  If no path is given, it will use `priv/static` as the input and output path.\n\n  The output folder will contain:\n\n    * the original file\n    * the file compressed with gzip\n    * a file containing the original file name and its digest\n    * a compressed file containing the file name and its digest\n    * a cache manifest file\n\n  Example of generated files:\n\n    * app.js\n    * app.js.gz\n    * app-eb0a5b9302e8d32828d8a73f137cc8f0.js\n    * app-eb0a5b9302e8d32828d8a73f137cc8f0.js.gz\n    * cache_manifest.json\n\n  You can use `mix phx.digest.clean` to prune stale versions of the assets.\n  If you want to remove all produced files, run `mix phx.digest.clean --all`.\n\n  ## vsn\n\n  It is possible to digest the stylesheet asset references without the query\n  string \"?vsn=d\" with the option `--no-vsn`.\n\n  ## Options\n\n    * `-o, --output` - indicates the path to your compiled\n      assets directory. Defaults to `priv/static`\n\n    * `--no-vsn` - do not add version query string to assets\n\n    * `--no-compile` - do not run mix compile\n  \"\"\"\n\n  @default_opts [vsn: true]\n  @switches [output: :string, vsn: :boolean]\n\n  @doc false\n  def run(all_args) do\n    # Ensure all compressors are compiled.\n    if \"--no-compile\" not in all_args do\n      Mix.Task.run(\"compile\", all_args)\n    end\n\n    Mix.Task.reenable(\"phx.digest\")\n\n    {:ok, _} = Application.ensure_all_started(:phoenix)\n\n    {opts, args, _} = OptionParser.parse(all_args, switches: @switches, aliases: [o: :output])\n    input_path = List.first(args) || @default_input_path\n    output_path = opts[:output] || input_path\n    with_vsn? = Keyword.merge(@default_opts, opts)[:vsn]\n\n    case Phoenix.Digester.compile(input_path, output_path, with_vsn?) do\n      :ok ->\n        # We need to call build structure so everything we have\n        # generated into priv is copied to _build in case we have\n        # build_embedded set to true. In case it's not true,\n        # build structure is mostly a no-op, so we are fine.\n        Mix.Project.build_structure()\n        Mix.shell().info [:green, \"Check your digested files at #{inspect output_path}\"]\n\n      {:error, :invalid_path} ->\n        # Do not exit with status code on purpose because\n        # in an umbrella not all apps are digestable.\n        Mix.shell().error \"The input path #{inspect input_path} does not exist\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.ex",
    "content": "defmodule Mix.Tasks.Phx do\n  use Mix.Task\n\n  @shortdoc \"Prints Phoenix help information\"\n\n  @moduledoc \"\"\"\n  Prints Phoenix tasks and their information.\n\n      $ mix phx\n\n  To print the Phoenix version, pass `-v` or `--version`, for example:\n\n      $ mix phx --version\n\n  \"\"\"\n\n  @version Mix.Project.config()[:version]\n\n  @impl true\n  @doc false\n  def run([version]) when version in ~w(-v --version) do\n    Mix.shell().info(\"Phoenix v#{@version}\")\n  end\n\n  def run(args) do\n    case args do\n      [] -> general()\n      _ -> Mix.raise \"Invalid arguments, expected: mix phx\"\n    end\n  end\n\n  defp general() do\n    Application.ensure_all_started(:phoenix)\n    Mix.shell().info \"Phoenix v#{Application.spec(:phoenix, :vsn)}\"\n    Mix.shell().info \"Peace of mind from prototype to production\"\n    Mix.shell().info \"\\n## Options\\n\"\n    Mix.shell().info \"-v, --version        # Prints Phoenix version\\n\"\n    Mix.Tasks.Help.run([\"--search\", \"phx.\"])\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.auth/hashing_library.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Auth.HashingLibrary do\n  @moduledoc false\n\n  defstruct [:name, :module, :mix_dependency, :test_config]\n\n  @type t :: %__MODULE__{\n          name: atom(),\n          module: module(),\n          mix_dependency: binary(),\n          test_config: binary()\n        }\n\n  def build(\"bcrypt\") do\n    lib = %__MODULE__{\n      name: :bcrypt,\n      module: Bcrypt,\n      mix_dependency: ~s|{:bcrypt_elixir, \"~> 3.0\"}|,\n      test_config: \"\"\"\n      config :bcrypt_elixir, :log_rounds, 1\n      \"\"\"\n    }\n\n    {:ok, lib}\n  end\n\n  def build(\"pbkdf2\") do\n    lib = %__MODULE__{\n      name: :pbkdf2,\n      module: Pbkdf2,\n      mix_dependency: ~s|{:pbkdf2_elixir, \"~> 2.0\"}|,\n      test_config: \"\"\"\n      config :pbkdf2_elixir, :rounds, 1\n      \"\"\"\n    }\n\n    {:ok, lib}\n  end\n\n  def build(\"argon2\") do\n    lib = %__MODULE__{\n      name: :argon2,\n      module: Argon2,\n      mix_dependency: ~s|{:argon2_elixir, \"~> 4.0\"}|,\n      test_config: \"\"\"\n      config :argon2_elixir, t_cost: 1, m_cost: 8\n      \"\"\"\n    }\n\n    {:ok, lib}\n  end\n\n  def build(other) do\n    {:error, {:unknown_library, other}}\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.auth/injector.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Auth.Injector do\n  @moduledoc false\n\n  alias Mix.Phoenix.Schema\n  alias Mix.Tasks.Phx.Gen.Auth.HashingLibrary\n\n  @type schema :: %Schema{}\n\n  @doc \"\"\"\n  Injects a dependency into the contents of mix.exs\n  \"\"\"\n  @spec mix_dependency_inject(String.t(), String.t()) ::\n          {:ok, String.t()} | :already_injected | {:error, :unable_to_inject}\n  def mix_dependency_inject(mixfile, dependency) do\n    with :ok <- ensure_not_already_injected(mixfile, dependency),\n         {:ok, new_mixfile} <- do_mix_dependency_inject(mixfile, dependency) do\n      {:ok, new_mixfile}\n    end\n  end\n\n  @spec do_mix_dependency_inject(String.t(), String.t()) ::\n          {:ok, String.t()} | {:error, :unable_to_inject}\n  defp do_mix_dependency_inject(mixfile, dependency) do\n    string_to_split_on = \"\"\"\n      defp deps do\n        [\n    \"\"\"\n\n    case split_with_self(mixfile, string_to_split_on) do\n      {beginning, splitter, rest} ->\n        new_mixfile =\n          IO.iodata_to_binary([beginning, splitter, \"      \", dependency, ?,, ?\\n, rest])\n\n        {:ok, new_mixfile}\n\n      _ ->\n        {:error, :unable_to_inject}\n    end\n  end\n\n  @doc \"\"\"\n  Injects configuration into `file`.\n  \"\"\"\n  def config_inject(file, code_to_inject) when is_binary(file) and is_binary(code_to_inject) do\n    inject_unless_contains(\n      file,\n      code_to_inject,\n      # Matches the entire line and captures the line ending. In the\n      # replace string:\n      #\n      # * the entire matching line is inserted with \\\\0,\n      # * the actual code is injected with &2,\n      # * and the appropriate newlines are injected using \\\\2.\n      &Regex.replace(~r/(use Mix\\.Config|import Config)(\\r\\n|\\n|$)/, &1, \"\\\\0\\\\2#{&2}\\\\2\",\n        global: false\n      )\n    )\n  end\n\n  @doc \"\"\"\n  Injects configuration for test environment into `file`.\n  \"\"\"\n  @spec test_config_inject(String.t(), HashingLibrary.t()) ::\n          {:ok, String.t()} | :already_injected | {:error, :unable_to_inject}\n  def test_config_inject(file, %HashingLibrary{} = hashing_library) when is_binary(file) do\n    code_to_inject =\n      hashing_library\n      |> test_config_code()\n      |> normalize_line_endings_to_file(file)\n\n    config_inject(file, code_to_inject)\n  end\n\n  @doc \"\"\"\n  Instructions to provide the user when `test_config_inject/2` fails.\n  \"\"\"\n  @spec test_config_help_text(String.t(), HashingLibrary.t()) :: String.t()\n  def test_config_help_text(file_path, %HashingLibrary{} = hashing_library) do\n    \"\"\"\n    Add the following to #{Path.relative_to_cwd(file_path)}:\n\n    #{hashing_library |> test_config_code() |> indent_spaces(4)}\n    \"\"\"\n  end\n\n  defp test_config_code(%HashingLibrary{test_config: test_config}) do\n    String.trim(\"\"\"\n    # Only in tests, remove the complexity from the password hashing algorithm\n    #{test_config}\n    \"\"\")\n  end\n\n  @router_plug_anchor_line \"plug :put_secure_browser_headers\"\n\n  @doc \"\"\"\n  Injects the fetch_current_scope_for_<schema> plug into router's browser pipeline\n  \"\"\"\n  @spec router_plug_inject(String.t(), binding :: keyword()) ::\n          {:ok, String.t()} | :already_injected | {:error, :unable_to_inject}\n  def router_plug_inject(file, binding) when is_binary(file) do\n    inject_unless_contains(\n      file,\n      router_plug_code(binding),\n      # Matches the entire line containing `anchor_line` and captures\n      # the whitespace before the anchor. In the replace string\n      #\n      # * the entire matching line is inserted with \\\\0,\n      # * the captured indent is inserted using \\\\1,\n      # * the actual code is injected with &2,\n      # * and the appropriate newline is injected using \\\\2\n      &Regex.replace(~r/^(\\s*)#{@router_plug_anchor_line}.*(\\r\\n|\\n|$)/Um, &1, \"\\\\0\\\\1#{&2}\\\\2\",\n        global: false\n      )\n    )\n  end\n\n  @doc \"\"\"\n  Instructions to provide the user when `inject_router_plug/2` fails.\n  \"\"\"\n  @spec router_plug_help_text(String.t(), binding :: keyword()) :: String.t()\n  def router_plug_help_text(file_path, binding) do\n    \"\"\"\n    Add the #{router_plug_name(binding)} plug to the :browser pipeline in #{Path.relative_to_cwd(file_path)}:\n\n        pipeline :browser do\n          ...\n          #{@router_plug_anchor_line}\n          #{router_plug_code(binding)}\n        end\n    \"\"\"\n  end\n\n  defp router_plug_code(binding) do\n    \"plug \" <> router_plug_name(binding)\n  end\n\n  defp router_plug_name(binding) do\n    \":fetch_#{binding[:scope_config].scope.assign_key}_for_#{binding[:schema].singular}\"\n  end\n\n  @doc \"\"\"\n  Injects a menu in the application layout\n  \"\"\"\n  def app_layout_menu_inject(binding, template_str) do\n    with {:error, :unable_to_inject} <-\n           app_layout_menu_inject_at_end_of_nav_tag(binding, template_str),\n         {:error, :unable_to_inject} <-\n           app_layout_menu_inject_after_opening_body_tag(binding, template_str) do\n      {:error, :unable_to_inject}\n    end\n  end\n\n  @doc \"\"\"\n  Instructions to provide the user when `app_layout_menu_inject/2` fails.\n  \"\"\"\n  def app_layout_menu_help_text(file_path, binding) do\n    {_dup_check, code} = app_layout_menu_code_to_inject(binding)\n\n    \"\"\"\n    Add the following #{binding[:schema].singular} menu items to your #{Path.relative_to_cwd(file_path)} layout file:\n\n    #{code}\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Menu code to inject into the application layout template.\n  \"\"\"\n  def app_layout_menu_code_to_inject(binding, padding \\\\ 4, newline \\\\ \"\\n\") do\n    schema = binding[:schema]\n    scope_config = binding[:scope_config]\n    already_injected_str = \"#{schema.route_prefix}/log-in\"\n\n    template = \"\"\"\n    <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\n      <%= if @#{scope_config.scope.assign_key} do %>\n        <li>\n          {@#{scope_config.scope.assign_key}.#{schema.singular}.email}\n        </li>\n        <li>\n          <.link href={~p\"#{schema.route_prefix}/settings\"}>Settings</.link>\n        </li>\n        <li>\n          <.link href={~p\"#{schema.route_prefix}/log-out\"} method=\"delete\">Log out</.link>\n        </li>\n      <% else %>\n        <li>\n          <.link href={~p\"#{schema.route_prefix}/register\"}>Register</.link>\n        </li>\n        <li>\n          <.link href={~p\"#{schema.route_prefix}/log-in\"}>Log in</.link>\n        </li>\n      <% end %>\n    </ul>\\\n    \"\"\"\n\n    {already_injected_str, indent_spaces(template, padding, newline)}\n  end\n\n  defp formatting_info(template, tag) do\n    {padding, newline} =\n      case Regex.run(~r/<?(([\\r\\n]{1})\\s*)#{tag}/m, template, global: false) do\n        [_, pre, \"\\n\"] -> {String.trim_leading(pre, \"\\n\") <> \"  \", \"\\n\"}\n        [_, \"\\r\\n\" <> pre, \"\\r\"] -> {String.trim_leading(pre, \"\\r\\n\") <> \"  \", \"\\r\\n\"}\n        _ -> {\"\", \"\\n\"}\n      end\n\n    {String.length(padding), newline}\n  end\n\n  defp app_layout_menu_inject_at_end_of_nav_tag(binding, file) do\n    {padding, newline} = formatting_info(file, \"<\\/nav>\")\n    {dup_check, code} = app_layout_menu_code_to_inject(binding, padding, newline)\n\n    inject_unless_contains(\n      file,\n      dup_check,\n      code,\n      &Regex.replace(~r/(\\s*)<\\/nav>/m, &1, \"#{newline}#{&2}\\\\0\", global: false)\n    )\n  end\n\n  defp app_layout_menu_inject_after_opening_body_tag(binding, file) do\n    anchor_line = \"<body\"\n    {padding, newline} = formatting_info(file, anchor_line)\n    {dup_check, code} = app_layout_menu_code_to_inject(binding, padding, newline)\n\n    inject_unless_contains(\n      file,\n      dup_check,\n      code,\n      # Matches the entire line containing `anchor_line` and captures\n      # the whitespace before the anchor. In the replace string, the\n      # entire matching line is inserted with \\\\0, then a newline then\n      # the indent that was captured using \\\\1. &2 is the code to\n      # inject.\n      &Regex.replace(~r/^(\\s*)#{anchor_line}.*(\\r\\n|\\n|$)/Um, &1, \"\\\\0#{&2}\\\\2\", global: false)\n    )\n  end\n\n  @doc \"\"\"\n  Injects code unless the existing code already contains `code_to_inject`\n  \"\"\"\n  def inject_unless_contains(code, dup_check, inject_fn) do\n    inject_unless_contains(code, dup_check, dup_check, inject_fn)\n  end\n\n  def inject_unless_contains(code, dup_check, code_to_inject, inject_fn)\n      when is_binary(code) and is_binary(code_to_inject) and is_binary(dup_check) and\n             is_function(inject_fn, 2) do\n    with :ok <- ensure_not_already_injected(code, dup_check) do\n      new_code = inject_fn.(code, code_to_inject)\n\n      if code != new_code do\n        {:ok, new_code}\n      else\n        {:error, :unable_to_inject}\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Injects snippet before the final end in a file\n  \"\"\"\n  @spec inject_before_final_end(String.t(), String.t()) :: {:ok, String.t()} | :already_injected\n  def inject_before_final_end(code, code_to_inject)\n      when is_binary(code) and is_binary(code_to_inject) do\n    if String.contains?(code, code_to_inject) do\n      :already_injected\n    else\n      new_code =\n        code\n        |> String.trim_trailing()\n        |> String.trim_trailing(\"end\")\n        |> Kernel.<>(code_to_inject)\n        |> Kernel.<>(\"end\\n\")\n\n      {:ok, new_code}\n    end\n  end\n\n  @spec ensure_not_already_injected(String.t(), String.t()) :: :ok | :already_injected\n  defp ensure_not_already_injected(file, inject) do\n    if String.contains?(file, inject) do\n      :already_injected\n    else\n      :ok\n    end\n  end\n\n  @spec split_with_self(String.t(), String.t()) :: {String.t(), String.t(), String.t()} | :error\n  defp split_with_self(contents, text) do\n    case :binary.split(contents, text) do\n      [left, right] -> {left, text, right}\n      [_] -> :error\n    end\n  end\n\n  @spec normalize_line_endings_to_file(String.t(), String.t()) :: String.t()\n  defp normalize_line_endings_to_file(code, file) do\n    String.replace(code, \"\\n\", get_line_ending(file))\n  end\n\n  @spec get_line_ending(String.t()) :: String.t()\n  defp get_line_ending(file) do\n    case Regex.run(~r/\\r\\n|\\n|$/, file) do\n      [line_ending] -> line_ending\n      [] -> \"\\n\"\n    end\n  end\n\n  defp indent_spaces(string, number_of_spaces, newline \\\\ \"\\n\")\n       when is_binary(string) and is_integer(number_of_spaces) do\n    indent = String.duplicate(\" \", number_of_spaces)\n\n    string\n    |> String.split(\"\\n\")\n    |> Enum.map_join(newline, &(indent <> &1))\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.auth/migration.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Auth.Migration do\n  @moduledoc false\n\n  defstruct [:ecto_adapter, :extensions, :column_definitions]\n\n  def build(ecto_adapter) when is_atom(ecto_adapter) do\n    %__MODULE__{\n      ecto_adapter: ecto_adapter,\n      extensions: extensions(ecto_adapter),\n      column_definitions: column_definitions(ecto_adapter)\n    }\n  end\n\n  defp extensions(Ecto.Adapters.Postgres) do\n    [\"execute \\\"CREATE EXTENSION IF NOT EXISTS citext\\\", \\\"\\\"\"]\n  end\n\n  defp extensions(_), do: []\n\n  defp column_definitions(ecto_adapter) do\n    for field <- ~w(email token)a,\n        into: %{},\n        do: {field, column_definition(field, ecto_adapter)}\n  end\n\n  defp column_definition(:email, Ecto.Adapters.Postgres), do: \"add :email, :citext, null: false\"\n  defp column_definition(:email, Ecto.Adapters.SQLite3), do: \"add :email, :string, null: false, collate: :nocase\"\n  defp column_definition(:email, _), do: \"add :email, :string, null: false, size: 160\"\n\n  defp column_definition(:token, Ecto.Adapters.Postgres), do: \"add :token, :binary, null: false\"\n\n  defp column_definition(:token, _), do: \"add :token, :binary, null: false, size: 32\"\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.auth.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Auth do\n  @shortdoc \"Generates authentication logic for a resource\"\n\n  @moduledoc \"\"\"\n  Generates authentication logic and related views for a resource.\n\n  ```console\n  $ mix phx.gen.auth Accounts User users\n  ```\n\n  The first argument is the context module followed by the schema module\n  and its plural name (used as the schema table name). The example above\n  will generate an `Accounts` context module with two schemas inside:\n  `User` and `UserToken`. You may name the context and schema according\n  to your preferences. For example:\n\n  ```console\n  $ mix phx.gen.auth Identity Client clients\n  ```\n\n  Will generate an `Identity` context with `Client` and `ClientToken` inside.\n  Additional information and security considerations are detailed in the\n  [`mix phx.gen.auth` guide](mix_phx_gen_auth.html).\n\n  > #### A note on scopes {: .info}\n  >\n  > `mix phx.gen.auth` creates a scope named after the schema by default.\n  > You can read more about scopes in the [Scopes guide](scopes.html).\n\n  ## LiveView vs conventional Controllers & Views\n\n  Authentication views can either be generated to use LiveView by passing\n  the `--live` option, or they can use conventional Phoenix\n  Controllers & Views by passing `--no-live`.\n\n  If neither of these options are provided, a prompt will be displayed.\n\n  Using the `--live` option is advised if you plan on using LiveView\n  elsewhere in your application. The user experience when navigating between\n  LiveViews can be tightly controlled, allowing you to let your users navigate\n  to authentication views without necessarily triggering a new HTTP request\n  each time (which would result in a full page load).\n\n  ## Mixing magic link and password registration\n\n  `mix phx.gen.auth` generates email based authentication, which assumes the user who\n  owns the email address has control over the account. Therefore, it is extremely\n  important to void all access tokens once the user confirms their account for the first\n  time, and we do so by revoking all tokens upon confirmation.\n\n  However, if you allow users to create an account with password, you must also\n  require them to be logged in by the time of confirmation, otherwise you may be\n  vulnerable to credential pre-stuffing, as the following attack is possible:\n\n  1. An attacker registers a new account with the email address of their target, anticipating\n     that the target creates an account at a later point in time.\n  2. The attacker sets a password when registering.\n  3. The target registers an account and sees that their email address is already in use.\n  4. The target logs in by magic link, but does not change the existing password.\n  5. The attacker maintains access using the password they previously set.\n\n  This is why the default implementation raises whenever a user tries to log in for the first\n  time by magic link and there is a password set. If you add registration with email and\n  password, then you must require the user to be logged in to confirm their account.\n  If they don't have a password (because it was set by the attacker), then they can set one\n  via a \"Forgot your password?\"-like workflow.\n\n  ## Password hashing\n\n  The password hashing mechanism defaults to `bcrypt` for\n  Unix systems and `pbkdf2` for Windows systems. Both\n  systems use the [Comeonin interface](https://hexdocs.pm/comeonin/).\n\n  The password hashing mechanism can be overridden with the\n  `--hashing-lib` option. The following values are supported:\n\n    * `bcrypt` - [bcrypt_elixir](https://hex.pm/packages/bcrypt_elixir)\n    * `pbkdf2` - [pbkdf2_elixir](https://hex.pm/packages/pbkdf2_elixir)\n    * `argon2` - [argon2_elixir](https://hex.pm/packages/argon2_elixir)\n\n  We recommend developers to consider using `argon2`, which\n  is the most robust of all 3. The downside is that `argon2`\n  is quite CPU and memory intensive, and you will need more\n  powerful instances to run your applications on.\n\n  For more information about choosing these libraries, see the\n  [Comeonin project](https://github.com/riverrun/comeonin).\n\n  ## Multiple invocations\n\n  You can invoke this generator multiple times. This is typically useful\n  if you have distinct resources that go through distinct authentication\n  workflows:\n\n      $ mix phx.gen.auth Store User users\n      $ mix phx.gen.auth Backoffice Admin admins\n\n  Note that when invoking `phx.gen.auth` multiple times, it will also generate\n  multiple [scopes](guides/authn_authz/scopes.md). Typically, only one scope is needed,\n  thus you will probably want to customize the generated code afterwards. Also, it\n  is expected that the generated code is not fully free of conflicts. One example is the\n  browser pipeline, which will try to assign both scopes as `:current_scope` by default.\n  You can customize the generated assign key with the `--assign-key` option.\n\n  ## Binary ids\n\n  The `--binary-id` option causes the generated migration to use\n  `binary_id` for its primary key and foreign keys.\n\n  ## Default options\n\n  This generator uses default options provided in the `:generators`\n  configuration of your application. These are the defaults:\n\n      config :your_app, :generators,\n        binary_id: false,\n        sample_binary_id: \"11111111-1111-1111-1111-111111111111\"\n\n  You can override those options per invocation by providing corresponding\n  switches, e.g. `--no-binary-id` to use normal ids despite the default\n  configuration.\n\n  ## Custom table names\n\n  By default, the table name for the migration and schema will be\n  the plural name provided for the resource. To customize this value,\n  a `--table` option may be provided. For example:\n\n      $ mix phx.gen.auth Accounts User users --table accounts_users\n\n  This will cause the generated tables to be named `\"accounts_users\"` and `\"accounts_users_tokens\"`.\n\n  ## Custom scope name\n\n  By default, the scope name is the same as the schema name. You can customize the scope name by passing the `--scope` option. For example:\n\n  ```console\n  $ mix phx.gen.auth Accounts User users --scope app_user\n  ```\n\n  This will generate a scope named `app_user` instead of `user`. You can read more about scopes in the [Scopes guide](scopes.html).\n\n  Additionally, the scope's assign key can be customized by passing the `--assign-key` option. For example:\n\n  ```console\n  $ mix phx.gen.auth Accounts User users --assign-key current_user_scope\n  ```\n\n  This is useful when you want to run `mix phx.gen.auth` multiple times in the same project, but note that\n  often it might make more sense to reuse the same scope with additional fields instead of separate scopes.\n  \"\"\"\n\n  use Mix.Task\n\n  alias Mix.Phoenix.{Context, Schema}\n  alias Mix.Tasks.Phx.Gen\n  alias Mix.Tasks.Phx.Gen.Auth.{HashingLibrary, Injector, Migration}\n\n  @switches [\n    web: :string,\n    binary_id: :boolean,\n    hashing_lib: :string,\n    table: :string,\n    merge_with_existing_context: :boolean,\n    prefix: :string,\n    live: :boolean,\n    compile: :boolean,\n    scope: :string,\n    assign_key: :string,\n    agents_md: :boolean\n  ]\n\n  @doc false\n  def run(args, test_opts \\\\ []) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.auth must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    Mix.Phoenix.ensure_live_view_compat!(__MODULE__)\n\n    {opts, parsed} = OptionParser.parse!(args, strict: @switches)\n    validate_args!(parsed)\n    hashing_library = build_hashing_library!(opts)\n\n    context_args =\n      OptionParser.to_argv(Keyword.drop(opts, [:scope, :assign_key, :agents_md]),\n        switches: @switches\n      ) ++\n        parsed\n\n    {context, schema} = Gen.Context.build(context_args ++ [\"--no-scope\"], help_module: __MODULE__)\n\n    context = put_live_option(context)\n    Gen.Context.prompt_for_code_injection(context)\n\n    if \"--no-compile\" not in args do\n      # Needed so we can get the ecto adapter and ensure other\n      # libraries are loaded.\n      Mix.Task.run(\"compile\")\n      validate_required_dependencies!()\n    end\n\n    ecto_adapter =\n      Keyword.get_lazy(\n        test_opts,\n        :ecto_adapter,\n        fn -> get_ecto_adapter!(schema) end\n      )\n\n    migration = Migration.build(ecto_adapter)\n\n    binding = [\n      context: context,\n      schema: schema,\n      migration: migration,\n      hashing_library: hashing_library,\n      web_app_name: web_app_name(context),\n      web_namespace: context.web_module,\n      endpoint_module: Module.concat([context.web_module, Endpoint]),\n      auth_module:\n        Module.concat([context.web_module, schema.web_namespace, \"#{inspect(schema.alias)}Auth\"]),\n      router_scope: router_scope(context),\n      web_path_prefix: web_path_prefix(schema),\n      test_case_options: test_case_options(ecto_adapter),\n      live?: Keyword.fetch!(context.opts, :live),\n      datetime_module: datetime_module(schema),\n      datetime_now: datetime_now(schema),\n      scope_config:\n        scope_config(context, opts[:scope], Keyword.get(opts, :assign_key, \"current_scope\")),\n      agents_md: Keyword.get(opts, :agents_md, true)\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(binding)\n\n    context\n    |> copy_new_files(binding, paths)\n    |> inject_conn_case_helpers(paths, binding)\n    |> inject_hashing_config(hashing_library)\n    |> maybe_inject_scope_config(binding)\n    |> maybe_inject_mix_dependency(hashing_library)\n    |> inject_routes(paths, binding)\n    |> maybe_inject_router_import(binding)\n    |> maybe_inject_router_plug(binding)\n    |> maybe_inject_app_layout_menu(binding)\n    |> maybe_inject_agents_md(paths, binding)\n    |> Gen.Notifier.maybe_print_mailer_installation_instructions()\n    |> print_shell_instructions()\n  end\n\n  defp web_app_name(%Context{} = context) do\n    context.web_module\n    |> inspect()\n    |> Phoenix.Naming.underscore()\n  end\n\n  defp validate_args!([_, _, _]), do: :ok\n\n  defp validate_args!(_) do\n    raise_with_help(\"Invalid arguments\")\n  end\n\n  defp validate_required_dependencies! do\n    unless Code.ensure_loaded?(Ecto.Adapters.SQL) do\n      raise_with_help(\"mix phx.gen.auth requires ecto_sql\", :phx_generator_args)\n    end\n\n    if generated_with_no_html?() do\n      raise_with_help(\"mix phx.gen.auth requires phoenix_html\", :phx_generator_args)\n    end\n\n    if generated_with_no_assets_or_esbuild?() do\n      Mix.shell().yes?(\"\"\"\n      Warning: did not find phoenix_html in your app.js.\n\n      phx.gen.auth expects the phoenix_html JavaScript to be available in your application for\n      the generated logout link to work.\n      This is not the case for applications generated with `--no-assets` or `--no-esbuild`.\n\n      To make the logout link work, you'll need to manually add the phoenix_html JavaScript to your application.\n      It is available at the \"priv/static/phoenix_html\" path of the phoenix_html application.\n\n      Alternatively, you can refactor the logout link to submit a `<form>` with method \"delete\" instead.\n\n      Continue?\\\n      \"\"\") || System.halt()\n    end\n  end\n\n  defp generated_with_no_html? do\n    Mix.Project.config()\n    |> Keyword.get(:deps, [])\n    |> Enum.any?(fn\n      {:phoenix_html, _} -> true\n      {:phoenix_html, _, _} -> true\n      _ -> false\n    end)\n    |> Kernel.not()\n  end\n\n  defp generated_with_no_assets_or_esbuild? do\n    not Code.ensure_loaded?(Phoenix.HTML) or\n      case File.read(\"assets/js/app.js\") do\n        {:ok, content} -> content =~ \"priv/static/phoenix_html.js\"\n        {:error, _} -> true\n      end\n  end\n\n  defp build_hashing_library!(opts) do\n    opts\n    |> Keyword.get_lazy(:hashing_lib, &default_hashing_library_option/0)\n    |> HashingLibrary.build()\n    |> case do\n      {:ok, hashing_library} ->\n        hashing_library\n\n      {:error, {:unknown_library, unknown_library}} ->\n        raise_with_help(\n          \"Unknown value for --hashing-lib #{inspect(unknown_library)}\",\n          :hashing_lib\n        )\n    end\n  end\n\n  defp default_hashing_library_option do\n    case :os.type() do\n      {:unix, _} -> \"bcrypt\"\n      {:win32, _} -> \"pbkdf2\"\n    end\n  end\n\n  defp scope_config(context, requested_scope, assign_key) do\n    existing_scopes = Mix.Phoenix.Scope.scopes_from_config(context.context_app)\n\n    {_, default_scope} =\n      Enum.find(existing_scopes, {nil, nil}, fn {_, scope} -> scope.default end)\n\n    key = String.to_atom(requested_scope || find_scope_name(context, existing_scopes))\n\n    {create_new?, scope, config_string} =\n      if Map.has_key?(existing_scopes, key) do\n        {false, existing_scopes[key], nil}\n      else\n        {true, new_scope(context, key, default_scope, assign_key),\n         scope_config_string(context, key, default_scope, assign_key)}\n      end\n\n    %{\n      scopes: existing_scopes,\n      default_scope: default_scope,\n      create_new?: create_new?,\n      scope: scope,\n      config_string: config_string\n    }\n  end\n\n  defp find_scope_name(context, existing_scopes) do\n    cond do\n      # user\n      is_new_scope?(existing_scopes, context.schema.singular) ->\n        context.schema.singular\n\n      # accounts_user\n      is_new_scope?(existing_scopes, \"#{context.basename}_#{context.schema.singular}\") ->\n        \"#{context.basename}_#{context.schema.singular}\"\n\n      # my_app_accounts_user\n      is_new_scope?(\n        existing_scopes,\n        \"#{context.context_app}_#{context.basename}_#{context.schema.singular}\"\n      ) ->\n        \"#{context.context_app}_#{context.basename}_#{context.schema.singular}\"\n\n      true ->\n        Mix.raise(\"\"\"\n        Could not generate a scope name for #{context.schema.singular}! These scopes already exist:\n\n            * #{Enum.map(existing_scopes, fn {name, _scope} -> name end) |> Enum.join(\"\\n    * \")}\n\n        You can customize the scope name by passing the --scope option.\n        \"\"\")\n    end\n  end\n\n  defp is_new_scope?(existing_scopes, bin_key) do\n    key = String.to_atom(bin_key)\n    not Map.has_key?(existing_scopes, key)\n  end\n\n  defp new_scope(context, key, default_scope, assign_key) do\n    Mix.Phoenix.Scope.new!(key, %{\n      default: !default_scope,\n      module: Module.concat([context.module, \"Scope\"]),\n      assign_key: String.to_atom(assign_key),\n      access_path: [\n        String.to_atom(context.schema.singular),\n        context.schema.opts[:primary_key] || :id\n      ],\n      schema_key:\n        String.to_atom(\"#{context.schema.singular}_#{context.schema.opts[:primary_key] || :id}\"),\n      schema_type: if(context.schema.binary_id, do: :binary_id, else: :id),\n      schema_table: context.schema.table,\n      test_data_fixture: Module.concat([context.module, \"Fixtures\"]),\n      test_setup_helper: :\"register_and_log_in_#{context.schema.singular}\"\n    })\n  end\n\n  defp scope_config_string(context, key, default_scope, assign_key) do\n    \"\"\"\n    config :#{context.context_app}, :scopes,\n      #{key}: [\n        default: #{if default_scope, do: false, else: true},\n        module: #{inspect(context.module)}.Scope,\n        assign_key: :#{assign_key},\n        access_path: [:#{context.schema.singular}, :#{context.schema.opts[:primary_key] || :id}],\n        schema_key: :#{context.schema.singular}_#{context.schema.opts[:primary_key] || :id},\n        schema_type: :#{if(context.schema.binary_id, do: :binary_id, else: :id)},\n        schema_table: :#{context.schema.table},\n        test_data_fixture: #{inspect(context.module)}Fixtures,\n        test_setup_helper: :register_and_log_in_#{context.schema.singular}\n      ]\\\n    \"\"\"\n  end\n\n  defp prompt_for_conflicts(binding) do\n    prompt_for_scope_conflicts(binding)\n\n    binding\n    |> files_to_be_generated()\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  defp prompt_for_scope_conflicts(binding) do\n    schema = binding[:schema]\n    %{scope: scope, default_scope: default_scope, create_new?: new?} = binding[:scope_config]\n\n    cond do\n      # this can only happen if --scope is used and the user explicitly asked for the scope name\n      scope && not new? ->\n        Mix.shell().yes?(\"\"\"\n        The scope #{scope.name} is already configured.\n\n        phx.gen.auth expects the configured scope module #{inspect(scope.module)} to include\n        a `for_#{schema.singular}/1` function that returns a `%#{inspect(schema.module)}{}` struct:\n\n            def for_#{schema.singular}(nil), do: %__MODULE__{user: nil}\n\n            def for_#{schema.singular}(%<%= inspect schema.alias %>{} = #{schema.singular}) do\n              %__MODULE__{#{schema.singular}: #{schema.singular}}\n            end\n\n        Please ensure that your scope module includes such code.\n\n        Do you want to proceed with the generation?\\\n        \"\"\") || System.halt()\n\n      default_scope ->\n        Mix.shell().yes?(\"\"\"\n        Your application configuration already contains a default scope: #{inspect(default_scope.name)}.\n\n        phx.gen.auth will create a new #{scope.name} scope.\n\n        Note that if you run `phx.gen.live` multiple times, the generated assign key for\n        the generated scopes can conflict with each other. You can pass `--assign-key` to customize\n        the assign key for the generated scope.\n\n        Do you want to proceed with the generation?\\\n        \"\"\") || System.halt()\n\n      true ->\n        :ok\n    end\n  end\n\n  defp files_to_be_generated(binding) do\n    schema = binding[:schema]\n    context = binding[:context]\n    context_app = context.context_app\n    scope_config = binding[:scope_config]\n\n    singular = schema.singular\n    web_pre = Mix.Phoenix.web_path(context_app)\n    web_test_pre = Mix.Phoenix.web_test_path(context_app)\n    migrations_pre = Mix.Phoenix.context_app_path(context_app, \"priv/repo/migrations\")\n    web_path = to_string(schema.web_path)\n    controller_pre = Path.join([web_pre, \"controllers\", web_path])\n\n    default_files =\n      [\n        \"migration.ex.eex\": [migrations_pre, \"#{timestamp()}_create_#{schema.table}_auth_tables.exs\"],\n        \"notifier.ex.eex\": [context.dir, \"#{singular}_notifier.ex\"],\n        \"schema.ex.eex\": [context.dir, \"#{singular}.ex\"],\n        \"schema_token.ex.eex\": [context.dir, \"#{singular}_token.ex\"],\n        \"auth.ex.eex\": [web_pre, web_path, \"#{singular}_auth.ex\"],\n        \"auth_test.exs.eex\": [web_test_pre, web_path, \"#{singular}_auth_test.exs\"],\n        \"session_controller.ex.eex\": [controller_pre, \"#{singular}_session_controller.ex\"],\n        \"session_controller_test.exs.eex\": [\n          web_test_pre,\n          \"controllers\",\n          web_path,\n          \"#{singular}_session_controller_test.exs\"\n        ]\n      ] ++\n        if scope_config.create_new? do\n          [\"scope.ex.eex\": [context.dir, \"scope.ex\"]]\n        else\n          []\n        end\n\n    case Keyword.fetch(context.opts, :live) do\n      {:ok, true} ->\n        live_files = [\n          \"registration_live.ex.eex\": [\n            web_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"registration.ex\"\n          ],\n          \"registration_live_test.exs.eex\": [\n            web_test_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"registration_test.exs\"\n          ],\n          \"login_live.ex.eex\": [web_pre, \"live\", web_path, \"#{singular}_live\", \"login.ex\"],\n          \"login_live_test.exs.eex\": [\n            web_test_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"login_test.exs\"\n          ],\n          \"settings_live.ex.eex\": [web_pre, \"live\", web_path, \"#{singular}_live\", \"settings.ex\"],\n          \"settings_live_test.exs.eex\": [\n            web_test_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"settings_test.exs\"\n          ],\n          \"confirmation_live.ex.eex\": [\n            web_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"confirmation.ex\"\n          ],\n          \"confirmation_live_test.exs.eex\": [\n            web_test_pre,\n            \"live\",\n            web_path,\n            \"#{singular}_live\",\n            \"confirmation_test.exs\"\n          ]\n        ]\n\n        remap_files(default_files ++ live_files)\n\n      _ ->\n        non_live_files = [\n          \"registration_new.html.heex.eex\": [\n            controller_pre,\n            \"#{singular}_registration_html\",\n            \"new.html.heex\"\n          ],\n          \"registration_controller.ex.eex\": [controller_pre, \"#{singular}_registration_controller.ex\"],\n          \"registration_controller_test.exs.eex\": [\n            web_test_pre,\n            \"controllers\",\n            web_path,\n            \"#{singular}_registration_controller_test.exs\"\n          ],\n          \"registration_html.ex.eex\": [controller_pre, \"#{singular}_registration_html.ex\"],\n          \"session_html.ex.eex\": [controller_pre, \"#{singular}_session_html.ex\"],\n          \"session_new.html.heex.eex\": [controller_pre, \"#{singular}_session_html\", \"new.html.heex\"],\n          \"session_confirm.html.heex.eex\": [\n            controller_pre,\n            \"#{singular}_session_html\",\n            \"confirm.html.heex\"\n          ],\n          \"settings_html.ex.eex\": [web_pre, \"controllers\", web_path, \"#{singular}_settings_html.ex\"],\n          \"settings_controller.ex.eex\": [controller_pre, \"#{singular}_settings_controller.ex\"],\n          \"settings_edit.html.heex.eex\": [\n            controller_pre,\n            \"#{singular}_settings_html\",\n            \"edit.html.heex\"\n          ],\n          \"settings_controller_test.exs.eex\": [\n            web_test_pre,\n            \"controllers\",\n            web_path,\n            \"#{singular}_settings_controller_test.exs\"\n          ]\n        ]\n\n        remap_files(default_files ++ non_live_files)\n    end\n  end\n\n  defp remap_files(files) do\n    for {source, dest} <- files, do: {:eex, to_string(source), Path.join(dest)}\n  end\n\n  defp copy_new_files(%Context{} = context, binding, paths) do\n    files = files_to_be_generated(binding)\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.auth\", binding, files)\n    inject_context_functions(context, paths, binding)\n    inject_tests(context, paths, binding)\n    inject_context_test_fixtures(context, paths, binding)\n\n    context\n  end\n\n  defp inject_context_functions(%Context{file: file} = context, paths, binding) do\n    Gen.Context.ensure_context_file_exists(context, paths, binding)\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.auth/context_functions.ex.eex\", binding)\n    |> prepend_newline()\n    |> inject_before_final_end(file)\n  end\n\n  defp inject_tests(%Context{test_file: test_file} = context, paths, binding) do\n    Gen.Context.ensure_test_file_exists(context, paths, binding)\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.auth/test_cases.exs.eex\", binding)\n    |> prepend_newline()\n    |> inject_before_final_end(test_file)\n  end\n\n  defp inject_context_test_fixtures(\n         %Context{test_fixtures_file: test_fixtures_file} = context,\n         paths,\n         binding\n       ) do\n    Gen.Context.ensure_test_fixtures_file_exists(context, paths, binding)\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.auth/context_fixtures_functions.ex.eex\", binding)\n    |> prepend_newline()\n    |> inject_before_final_end(test_fixtures_file)\n  end\n\n  defp inject_conn_case_helpers(%Context{} = context, paths, binding) do\n    test_file = \"test/support/conn_case.ex\"\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.auth/conn_case.exs.eex\", binding)\n    |> inject_before_final_end(test_file)\n\n    context\n  end\n\n  defp inject_routes(%Context{context_app: ctx_app} = context, paths, binding) do\n    web_prefix = Mix.Phoenix.web_path(ctx_app)\n    file_path = Path.join(web_prefix, \"router.ex\")\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.auth/routes.ex.eex\", binding)\n    |> inject_before_final_end(file_path)\n\n    context\n  end\n\n  defp maybe_inject_mix_dependency(%Context{context_app: ctx_app} = context, %HashingLibrary{\n         mix_dependency: mix_dependency\n       }) do\n    file_path = Mix.Phoenix.context_app_path(ctx_app, \"mix.exs\")\n\n    file = File.read!(file_path)\n\n    case Injector.mix_dependency_inject(file, mix_dependency) do\n      {:ok, new_file} ->\n        print_injecting(file_path)\n        File.write!(file_path, new_file)\n\n      :already_injected ->\n        :ok\n\n      {:error, :unable_to_inject} ->\n        Mix.shell().info(\"\"\"\n\n        Add your #{mix_dependency} dependency to #{file_path}:\n\n            defp deps do\n              [\n                #{mix_dependency},\n                ...\n              ]\n            end\n        \"\"\")\n    end\n\n    context\n  end\n\n  defp maybe_inject_router_import(%Context{context_app: ctx_app} = context, binding) do\n    web_prefix = Mix.Phoenix.web_path(ctx_app)\n    file_path = Path.join(web_prefix, \"router.ex\")\n    auth_module = Keyword.fetch!(binding, :auth_module)\n    inject = \"import #{inspect(auth_module)}\"\n    use_line = \"use #{inspect(context.web_module)}, :router\"\n\n    help_text = \"\"\"\n    Add your #{inspect(auth_module)} import to #{Path.relative_to_cwd(file_path)}:\n\n        defmodule #{inspect(context.web_module)}.Router do\n          #{use_line}\n\n          # Import authentication plugs\n          #{inject}\n\n          ...\n        end\n    \"\"\"\n\n    with {:ok, file} <- read_file(file_path),\n         {:ok, new_file} <-\n           Injector.inject_unless_contains(\n             file,\n             inject,\n             &String.replace(&1, use_line, \"#{use_line}\\n\\n  #{&2}\")\n           ) do\n      print_injecting(file_path, \" - imports\")\n      File.write!(file_path, new_file)\n    else\n      :already_injected ->\n        :ok\n\n      {:error, :unable_to_inject} ->\n        Mix.shell().info(\"\"\"\n\n        #{help_text}\n        \"\"\")\n\n      {:error, {:file_read_error, _}} ->\n        print_injecting(file_path)\n        print_unable_to_read_file_error(file_path, help_text)\n    end\n\n    context\n  end\n\n  defp maybe_inject_router_plug(%Context{context_app: ctx_app} = context, binding) do\n    web_prefix = Mix.Phoenix.web_path(ctx_app)\n    file_path = Path.join(web_prefix, \"router.ex\")\n    help_text = Injector.router_plug_help_text(file_path, binding)\n\n    with {:ok, file} <- read_file(file_path),\n         {:ok, new_file} <- Injector.router_plug_inject(file, binding) do\n      print_injecting(file_path, \" - plug\")\n      File.write!(file_path, new_file)\n    else\n      :already_injected ->\n        :ok\n\n      {:error, :unable_to_inject} ->\n        Mix.shell().info(\"\"\"\n\n        #{help_text}\n        \"\"\")\n\n      {:error, {:file_read_error, _}} ->\n        print_injecting(file_path)\n        print_unable_to_read_file_error(file_path, help_text)\n    end\n\n    context\n  end\n\n  defp maybe_inject_app_layout_menu(%Context{} = context, binding) do\n    if file_path = get_layout_html_path(context) do\n      case Injector.app_layout_menu_inject(binding, File.read!(file_path)) do\n        {:ok, new_content} ->\n          print_injecting(file_path)\n          File.write!(file_path, new_content)\n\n        :already_injected ->\n          :ok\n\n        {:error, :unable_to_inject} ->\n          Mix.shell().info(\"\"\"\n\n          #{Injector.app_layout_menu_help_text(file_path, binding)}\n          \"\"\")\n      end\n    else\n      {_dup, inject} = Injector.app_layout_menu_code_to_inject(binding)\n\n      missing =\n        context\n        |> potential_layout_file_paths()\n        |> Enum.map_join(\"\\n\", &\"  * #{&1}\")\n\n      Mix.shell().error(\"\"\"\n\n      Unable to find the root layout file to inject user menu items.\n\n      Missing files:\n\n      #{missing}\n\n      Please ensure this phoenix app was not generated with\n      --no-html. If you have changed the name of your root\n      layout file, please add the following code to it where you'd\n      like the #{binding[:schema].singular} menu items to be rendered.\n\n      #{inject}\n      \"\"\")\n    end\n\n    context\n  end\n\n  defp get_layout_html_path(%Context{} = context) do\n    context\n    |> potential_layout_file_paths()\n    |> Enum.find(&File.exists?/1)\n  end\n\n  defp potential_layout_file_paths(%Context{context_app: ctx_app}) do\n    web_prefix = Mix.Phoenix.web_path(ctx_app)\n\n    for file_name <- ~w(root.html.heex) do\n      Path.join([web_prefix, \"components\", \"layouts\", file_name])\n    end\n  end\n\n  defp inject_hashing_config(context, %HashingLibrary{} = hashing_library) do\n    file_path =\n      if Mix.Phoenix.in_umbrella?(File.cwd!()) do\n        Path.expand(\"../../\")\n      else\n        File.cwd!()\n      end\n      |> Path.join(\"config/test.exs\")\n\n    file =\n      case read_file(file_path) do\n        {:ok, file} -> file\n        {:error, {:file_read_error, _}} -> \"import Config\\n\"\n      end\n\n    case Injector.test_config_inject(file, hashing_library) do\n      {:ok, new_file} ->\n        print_injecting(file_path)\n        File.write!(file_path, new_file)\n\n      :already_injected ->\n        :ok\n\n      {:error, :unable_to_inject} ->\n        help_text = Injector.test_config_help_text(file_path, hashing_library)\n\n        Mix.shell().info(\"\"\"\n\n        #{help_text}\n        \"\"\")\n    end\n\n    context\n  end\n\n  defp maybe_inject_scope_config(%Context{} = context, binding) do\n    if binding[:scope_config].create_new? do\n      inject_scope_config(context, binding)\n    else\n      context\n    end\n  end\n\n  defp inject_scope_config(%Context{} = context, binding) do\n    scope_config = binding[:scope_config].config_string\n\n    file_path =\n      if Mix.Phoenix.in_umbrella?(File.cwd!()) do\n        Path.expand(\"../../\")\n      else\n        File.cwd!()\n      end\n      |> Path.join(\"config/config.exs\")\n\n    file =\n      case read_file(file_path) do\n        {:ok, file} -> file\n        {:error, {:file_read_error, _}} -> \"import Config\\n\"\n      end\n\n    case Injector.config_inject(file, scope_config) do\n      {:ok, new_file} ->\n        print_injecting(file_path)\n        File.write!(file_path, new_file)\n\n      :already_injected ->\n        :ok\n\n      {:error, :unable_to_inject} ->\n        Mix.shell().info(\"\"\"\n        Add the following to #{Path.relative_to_cwd(file_path)}:\n\n        #{scope_config}\n        \"\"\")\n    end\n\n    context\n  end\n\n  defp maybe_inject_agents_md(%Context{} = context, paths, binding) do\n    if binding[:agents_md] do\n      # we add our own comment marker (not related to usage_rules)\n      # to check if phx.gen.auth already ran as we only want to inject once\n      # even if other options were used\n      auth_content =\n        \"\"\"\n        <!-- phoenix-gen-auth-start -->\n        #{Mix.Phoenix.eval_from(paths, \"priv/templates/phx.gen.auth/AGENTS.md.eex\", binding)}\n        <!-- phoenix-gen-auth-end -->\n        \"\"\"\n\n      file_path =\n        if Mix.Phoenix.in_umbrella?(File.cwd!()) do\n          Path.expand(\"../../\")\n        else\n          File.cwd!()\n        end\n        |> Path.join(\"AGENTS.md\")\n\n      with true <- File.exists?(file_path),\n           content = File.read!(file_path),\n           false <- content =~ \"<!-- phoenix-gen-auth-start -->\" do\n        print_injecting(file_path)\n        # inject before usage rules\n        case String.split(content, \"<!-- usage-rules-start -->\", parts: 2) do\n          [pre, post] ->\n            File.write!(file_path, [\n              pre,\n              String.trim_trailing(auth_content),\n              \"\\n\\n\",\n              \"<!-- usage-rules-start -->\",\n              post\n            ])\n\n          _ ->\n            # just append\n            File.write!(file_path, content <> \"\\n\\n\" <> String.trim_trailing(auth_content))\n        end\n      end\n    end\n\n    context\n  end\n\n  defp print_shell_instructions(%Context{} = context) do\n    Mix.shell().info(\"\"\"\n\n    Please re-fetch your dependencies with the following command:\n\n        $ mix deps.get\n\n    Remember to update your repository by running migrations:\n\n        $ mix ecto.migrate\n\n    Once you are ready, visit \"/#{context.schema.plural}/register\"\n    to create your account and then access \"/dev/mailbox\" to\n    see the account confirmation email.\n    \"\"\")\n\n    context\n  end\n\n  defp router_scope(%Context{schema: schema} = context) do\n    prefix = Module.concat(context.web_module, schema.web_namespace)\n\n    if schema.web_namespace do\n      ~s|\"/#{schema.web_path}\", #{inspect(prefix)}|\n    else\n      ~s|\"/\", #{inspect(context.web_module)}|\n    end\n  end\n\n  defp web_path_prefix(%Schema{web_path: nil}), do: \"\"\n  defp web_path_prefix(%Schema{web_path: web_path}), do: \"/\" <> web_path\n\n  defp inject_before_final_end(content_to_inject, file_path) do\n    with {:ok, file} <- read_file(file_path),\n         {:ok, new_file} <- Injector.inject_before_final_end(file, content_to_inject) do\n      print_injecting(file_path)\n      File.write!(file_path, new_file)\n    else\n      :already_injected ->\n        :ok\n\n      {:error, {:file_read_error, _}} ->\n        print_injecting(file_path)\n\n        print_unable_to_read_file_error(\n          file_path,\n          \"\"\"\n\n          Please add the following to the end of your equivalent\n          #{Path.relative_to_cwd(file_path)} module:\n\n          #{indent_spaces(content_to_inject, 2)}\n          \"\"\"\n        )\n    end\n  end\n\n  defp read_file(file_path) do\n    case File.read(file_path) do\n      {:ok, file} -> {:ok, file}\n      {:error, reason} -> {:error, {:file_read_error, reason}}\n    end\n  end\n\n  defp indent_spaces(string, number_of_spaces)\n       when is_binary(string) and is_integer(number_of_spaces) do\n    indent = String.duplicate(\" \", number_of_spaces)\n\n    string\n    |> String.split(\"\\n\")\n    |> Enum.map_join(\"\\n\", &(indent <> &1))\n  end\n\n  defp timestamp do\n    {{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()\n    \"#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}\"\n  end\n\n  defp pad(i) when i < 10, do: <<?0, ?0 + i>>\n  defp pad(i), do: to_string(i)\n\n  defp prepend_newline(string) when is_binary(string), do: \"\\n\" <> string\n\n  defp get_ecto_adapter!(%Schema{repo: repo}) do\n    if Code.ensure_loaded?(repo) do\n      repo.__adapter__()\n    else\n      Mix.raise(\"Unable to find #{inspect(repo)}\")\n    end\n  end\n\n  defp print_injecting(file_path, suffix \\\\ []) do\n    Mix.shell().info([:green, \"* injecting \", :reset, Path.relative_to_cwd(file_path), suffix])\n  end\n\n  defp print_unable_to_read_file_error(file_path, help_text) do\n    Mix.shell().error(\n      \"\"\"\n\n      Unable to read file #{Path.relative_to_cwd(file_path)}.\n\n      #{help_text}\n      \"\"\"\n      |> indent_spaces(2)\n    )\n  end\n\n  @doc false\n  def raise_with_help(msg) do\n    raise_with_help(msg, :general)\n  end\n\n  defp raise_with_help(msg, :general) do\n    Mix.raise(\"\"\"\n    #{msg}\n\n    mix phx.gen.auth expects a context module name, followed by\n    the schema module and its plural name (used as the schema\n    table name).\n\n    For example:\n\n        mix phx.gen.auth Accounts User users\n\n    The context serves as the API boundary for the given resource.\n    Multiple resources may belong to a context and a resource may be\n    split over distinct contexts (such as Accounts.User and Payments.User).\n    \"\"\")\n  end\n\n  defp raise_with_help(msg, :phx_generator_args) do\n    Mix.raise(\"\"\"\n    #{msg}\n\n    mix phx.gen.auth must be installed into a Phoenix 1.5 app that\n    contains ecto and html templates.\n\n        mix phx.new my_app\n        mix phx.new my_app --umbrella\n        mix phx.new my_app --database mysql\n\n    Apps generated with --no-ecto or --no-html are not supported.\n    \"\"\")\n  end\n\n  defp raise_with_help(msg, :hashing_lib) do\n    Mix.raise(\"\"\"\n    #{msg}\n\n    mix phx.gen.auth supports the following values for --hashing-lib\n\n      * bcrypt\n      * pbkdf2\n      * argon2\n\n    Visit https://github.com/riverrun/comeonin for more information\n    on choosing a library.\n    \"\"\")\n  end\n\n  defp test_case_options(Ecto.Adapters.Postgres), do: \", async: true\"\n  defp test_case_options(adapter) when is_atom(adapter), do: \"\"\n\n  defp datetime_module(%{timestamp_type: :naive_datetime}), do: NaiveDateTime\n  defp datetime_module(%{timestamp_type: :utc_datetime}), do: DateTime\n  defp datetime_module(%{timestamp_type: :utc_datetime_usec}), do: DateTime\n\n  defp datetime_now(%{timestamp_type: :naive_datetime}), do: \"NaiveDateTime.utc_now(:second)\"\n  defp datetime_now(%{timestamp_type: :utc_datetime}), do: \"DateTime.utc_now(:second)\"\n  defp datetime_now(%{timestamp_type: :utc_datetime_usec}), do: \"DateTime.utc_now()\"\n\n  defp put_live_option(schema) do\n    opts =\n      case Keyword.fetch(schema.opts, :live) do\n        {:ok, _live?} ->\n          schema.opts\n\n        _ ->\n          Mix.shell().info(\"\"\"\n          An authentication system can be created in two different ways:\n          - Using Phoenix.LiveView (default)\n          - Using Phoenix.Controller only\\\n          \"\"\")\n\n          if Mix.shell().yes?(\"Do you want to create a LiveView based authentication system?\") do\n            Keyword.put_new(schema.opts, :live, true)\n          else\n            Keyword.put_new(schema.opts, :live, false)\n          end\n      end\n\n    Map.put(schema, :opts, opts)\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.cert.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Cert do\n  @shortdoc \"Generates a self-signed certificate for HTTPS testing\"\n\n  @default_path \"priv/cert/selfsigned\"\n  @default_name \"Self-signed test certificate\"\n  @default_hostnames [\"localhost\"]\n\n  @warning \"\"\"\n  WARNING: only use the generated certificate for testing in a closed network\n  environment, such as running a development server on `localhost`.\n  For production, staging, or testing servers on the public internet, obtain a\n  proper certificate, for example from [Let's Encrypt](https://letsencrypt.org).\n  \"\"\"\n\n  @moduledoc \"\"\"\n  Generates a self-signed certificate for HTTPS testing.\n\n      $ mix phx.gen.cert\n      $ mix phx.gen.cert my-app.localhost my-app.internal.example.com\n\n  Creates a private key and a self-signed certificate in PEM format. These\n  files can be referenced in the `certfile` and `keyfile` parameters of an\n  HTTPS Endpoint.\n\n  #{@warning}\n\n  ## Arguments\n\n  The list of hostnames, if none are specified, defaults to:\n\n    * #{Enum.join(@default_hostnames, \"\\n  * \")}\n\n  Other (optional) arguments:\n\n    * `--output` (`-o`): the path and base filename for the certificate and\n      key (default: #{@default_path})\n    * `--name` (`-n`): the Common Name value in certificate's subject\n      (default: \"#{@default_name}\")\n\n  Requires OTP 21.3 or later.\n  \"\"\"\n\n  use Mix.Task\n  import Mix.Generator\n\n  @doc false\n  def run(all_args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.cert must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    {opts, args} =\n      OptionParser.parse!(\n        all_args,\n        aliases: [n: :name, o: :output],\n        strict: [name: :string, output: :string]\n      )\n\n    path = opts[:output] || @default_path\n    name = opts[:name] || @default_name\n\n    hostnames =\n      case args do\n        [] -> @default_hostnames\n        list -> list\n      end\n\n    {certificate, private_key} = certificate_and_key(2048, name, hostnames)\n\n    keyfile = path <> \"_key.pem\"\n    certfile = path <> \".pem\"\n\n    create_file(\n      keyfile,\n      :public_key.pem_encode([:public_key.pem_entry_encode(:RSAPrivateKey, private_key)])\n    )\n\n    create_file(\n      certfile,\n      :public_key.pem_encode([{:Certificate, certificate, :not_encrypted}])\n    )\n\n    print_shell_instructions(keyfile, certfile)\n  end\n\n  @doc false\n  def certificate_and_key(key_size, name, hostnames) do\n    private_key =\n      case generate_rsa_key(key_size, 65537) do\n        {:ok, key} ->\n          key\n\n        {:error, :not_supported} ->\n          Mix.raise(\"\"\"\n          Failed to generate an RSA key pair.\n\n          This Mix task requires Erlang/OTP 20 or later. Please upgrade to a\n          newer version, or use another tool, such as OpenSSL, to generate a\n          certificate.\n          \"\"\")\n      end\n\n    public_key = extract_public_key(private_key)\n\n    certificate =\n      public_key\n      |> new_cert(name, hostnames)\n      |> :public_key.pkix_sign(private_key)\n\n    {certificate, private_key}\n  end\n\n  defp print_shell_instructions(keyfile, certfile) do\n    app = Mix.Phoenix.otp_app()\n    base = Mix.Phoenix.base()\n\n    Mix.shell().info(\"\"\"\n\n    If you have not already done so, please update your HTTPS Endpoint\n    configuration in config/dev.exs:\n\n      config #{inspect(app)}, #{inspect(Mix.Phoenix.web_module(base))}.Endpoint,\n        ...,\n        https: [\n          # Change to `ip: {0, 0, 0, 0}` to allow access from other machines\n          ip: {127, 0, 0, 1},\n          port: 4001,\n          cipher_suite: :strong,\n          certfile: \"#{certfile}\",\n          keyfile: \"#{keyfile}\"\n        ],\n        ...\n\n    #{@warning}\n    \"\"\")\n  end\n\n  require Record\n\n  # RSA key pairs\n\n  Record.defrecordp(\n    :rsa_private_key,\n    :RSAPrivateKey,\n    Record.extract(:RSAPrivateKey, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :rsa_public_key,\n    :RSAPublicKey,\n    Record.extract(:RSAPublicKey, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  defp generate_rsa_key(keysize, e) do\n    private_key = :public_key.generate_key({:rsa, keysize, e})\n    {:ok, private_key}\n  rescue\n    FunctionClauseError ->\n      {:error, :not_supported}\n  end\n\n  defp extract_public_key(rsa_private_key(modulus: m, publicExponent: e)) do\n    rsa_public_key(modulus: m, publicExponent: e)\n  end\n\n  # Certificates\n\n  Record.defrecordp(\n    :otp_tbs_certificate,\n    :OTPTBSCertificate,\n    Record.extract(:OTPTBSCertificate, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :signature_algorithm,\n    :SignatureAlgorithm,\n    Record.extract(:SignatureAlgorithm, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :validity,\n    :Validity,\n    Record.extract(:Validity, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :otp_subject_public_key_info,\n    :OTPSubjectPublicKeyInfo,\n    Record.extract(:OTPSubjectPublicKeyInfo, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :public_key_algorithm,\n    :PublicKeyAlgorithm,\n    Record.extract(:PublicKeyAlgorithm, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :extension,\n    :Extension,\n    Record.extract(:Extension, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :basic_constraints,\n    :BasicConstraints,\n    Record.extract(:BasicConstraints, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  Record.defrecordp(\n    :attr,\n    :AttributeTypeAndValue,\n    Record.extract(:AttributeTypeAndValue, from_lib: \"public_key/include/OTP-PUB-KEY.hrl\")\n  )\n\n  # OID values\n  @rsaEncryption {1, 2, 840, 113_549, 1, 1, 1}\n  @sha256WithRSAEncryption {1, 2, 840, 113_549, 1, 1, 11}\n\n  @basicConstraints {2, 5, 29, 19}\n  @keyUsage {2, 5, 29, 15}\n  @extendedKeyUsage {2, 5, 29, 37}\n  @subjectKeyIdentifier {2, 5, 29, 14}\n  @subjectAlternativeName {2, 5, 29, 17}\n\n  @organizationName {2, 5, 4, 10}\n  @commonName {2, 5, 4, 3}\n\n  @serverAuth {1, 3, 6, 1, 5, 5, 7, 3, 1}\n  @clientAuth {1, 3, 6, 1, 5, 5, 7, 3, 2}\n\n  defp new_cert(public_key, common_name, hostnames) do\n    <<serial::unsigned-64>> = :crypto.strong_rand_bytes(8)\n\n    today = Date.utc_today()\n\n    not_before =\n      today\n      |> Date.to_iso8601(:basic)\n      |> String.slice(2, 6)\n\n    not_after =\n      today\n      |> Date.add(365)\n      |> Date.to_iso8601(:basic)\n      |> String.slice(2, 6)\n\n    otp_tbs_certificate(\n      version: :v3,\n      serialNumber: serial,\n      signature: signature_algorithm(algorithm: @sha256WithRSAEncryption),\n      issuer: rdn(common_name),\n      validity:\n        validity(\n          notBefore: {:utcTime, ~c\"#{not_before}000000Z\"},\n          notAfter: {:utcTime, ~c\"#{not_after}000000Z\"}\n        ),\n      subject: rdn(common_name),\n      subjectPublicKeyInfo:\n        otp_subject_public_key_info(\n          algorithm: public_key_algorithm(algorithm: @rsaEncryption),\n          subjectPublicKey: public_key\n        ),\n      extensions: extensions(public_key, hostnames)\n    )\n  end\n\n  defp rdn(common_name) do\n    {:rdnSequence,\n     [\n       [attr(type: @organizationName, value: {:utf8String, \"Phoenix Framework\"})],\n       [attr(type: @commonName, value: {:utf8String, common_name})]\n     ]}\n  end\n\n  defp extensions(public_key, hostnames) do\n    [\n      extension(\n        extnID: @basicConstraints,\n        critical: true,\n        extnValue: basic_constraints(cA: false)\n      ),\n      extension(\n        extnID: @keyUsage,\n        critical: true,\n        extnValue: [:digitalSignature, :keyEncipherment]\n      ),\n      extension(\n        extnID: @extendedKeyUsage,\n        critical: false,\n        extnValue: [@serverAuth, @clientAuth]\n      ),\n      extension(\n        extnID: @subjectKeyIdentifier,\n        critical: false,\n        extnValue: key_identifier(public_key)\n      ),\n      extension(\n        extnID: @subjectAlternativeName,\n        critical: false,\n        extnValue: Enum.map(hostnames, &{:dNSName, String.to_charlist(&1)})\n      )\n    ]\n  end\n\n  defp key_identifier(public_key) do\n    :crypto.hash(:sha, :public_key.der_encode(:RSAPublicKey, public_key))\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.channel.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Channel do\n  @shortdoc \"Generates a Phoenix channel\"\n\n  @moduledoc \"\"\"\n  Generates a Phoenix channel.\n\n      $ mix phx.gen.channel Room\n\n  Accepts the module name for the channel\n\n  The generated files will contain:\n\n  For a regular application:\n\n    * a channel in `lib/my_app_web/channels`\n    * a channel test in `test/my_app_web/channels`\n\n  For an umbrella application:\n\n    * a channel in `apps/my_app_web/lib/app_name_web/channels`\n    * a channel test in `apps/my_app_web/test/my_app_web/channels`\n\n  \"\"\"\n  use Mix.Task\n  alias Mix.Tasks.Phx.Gen\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.channel must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    [channel_name] = validate_args!(args)\n    context_app = Mix.Phoenix.context_app()\n    web_prefix = Mix.Phoenix.web_path(context_app)\n    web_test_prefix = Mix.Phoenix.web_test_path(context_app)\n    binding = Mix.Phoenix.inflect(channel_name)\n    binding = Keyword.put(binding, :module, \"#{binding[:web_module]}.#{binding[:scoped]}\")\n\n    Mix.Phoenix.check_module_name_availability!(binding[:module] <> \"Channel\")\n\n    test_path = Path.join(web_test_prefix, \"channels/#{binding[:path]}_channel_test.exs\")\n    case_path = Path.join(Path.dirname(web_test_prefix), \"support/channel_case.ex\")\n\n    maybe_case =\n      if File.exists?(case_path) do\n        []\n      else\n        [{:eex, \"channel_case.ex.eex\", case_path}]\n      end\n\n    Mix.Phoenix.copy_from(\n      paths(),\n      \"priv/templates/phx.gen.channel\",\n      binding,\n      [\n        {:eex, \"channel.ex.eex\", Path.join(web_prefix, \"channels/#{binding[:path]}_channel.ex\")},\n        {:eex, \"channel_test.exs.eex\", test_path}\n      ] ++ maybe_case\n    )\n\n    user_socket_path = Mix.Phoenix.web_path(context_app, \"channels/user_socket.ex\")\n\n    if File.exists?(user_socket_path) do\n      Mix.shell().info(\"\"\"\n\n      Add the channel to your `#{user_socket_path}` handler, for example:\n\n          channel \"#{binding[:singular]}:lobby\", #{binding[:module]}Channel\n      \"\"\")\n    else\n      Mix.shell().info(\"\"\"\n\n      The default socket handler - #{binding[:web_module]}.UserSocket - was not found.\n      \"\"\")\n\n      if Mix.shell().yes?(\"Do you want to create it?\") do\n        Gen.Socket.run(~w(User --from-channel #{channel_name}))\n      else\n        Mix.shell().info(\"\"\"\n\n        To create it, please run the mix task:\n\n            mix phx.gen.socket User\n\n        Then add the channel to the newly created file, at `#{user_socket_path}`:\n\n            channel \"#{binding[:singular]}:lobby\", #{binding[:module]}Channel\n        \"\"\")\n      end\n    end\n  end\n\n  @spec raise_with_help() :: no_return()\n  defp raise_with_help do\n    Mix.raise(\"\"\"\n    mix phx.gen.channel expects just the module name, following capitalization:\n\n        mix phx.gen.channel Room\n\n    \"\"\")\n  end\n\n  defp validate_args!(args) do\n    unless length(args) == 1 and args |> hd() |> valid_name?() do\n      raise_with_help()\n    end\n\n    args\n  end\n\n  defp valid_name?(name) do\n    name =~ ~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/\n  end\n\n  defp paths do\n    [\".\", :phoenix]\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.context.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Context do\n  @shortdoc \"Generates a context with functions around an Ecto schema\"\n\n  @moduledoc \"\"\"\n  Generates a context with functions around an Ecto schema.\n\n  ```console\n  $ mix phx.gen.context Accounts User users name:string age:integer\n  ```\n\n  The first argument is the context module followed by the schema module\n  and its plural name (used as the schema table name).\n\n  The context is an Elixir module that serves as an API boundary for\n  the given resource. A context often holds many related resources.\n  Therefore, if the context already exists, it will be augmented with\n  functions for the given resource.\n\n  > Note: A resource may also be split\n  > over distinct contexts (such as Accounts.User and Payments.User).\n\n  The schema is responsible for mapping the database fields into an\n  Elixir struct.\n\n  Overall, this generator will add the following files to `lib/your_app`:\n\n    * a context module in `accounts.ex`, serving as the API boundary\n    * a schema in `accounts/user.ex`, with a `users` table\n\n  A migration file for the repository and test files for the context\n  will also be generated.\n\n  The generated migration can be skipped with `--no-migration`.\n\n  ## Scopes\n\n  If your application configures its own default [scope](scopes.md), then this generator\n  will automatically make sure all of your context operations are correctly scoped.\n  You can pass the `--no-scope` flag to disable the scoping.\n\n  ## Generating without a schema\n\n  In some cases, you may wish to bootstrap the context module and\n  tests, but leave internal implementation of the context and schema\n  to yourself. Use the `--no-schema` flags to accomplish this.\n\n  ## `--table`\n\n  By default, the table name for the migration and schema will be\n  the plural name provided for the resource. To customize this value,\n  a `--table` option may be provided. For example:\n\n      $ mix phx.gen.context Accounts User users --table cms_users\n\n  ## `--binary-id`\n\n  Generated migration can use `binary_id` for schema's primary key\n  and its references with option `--binary-id`.\n\n  ## Default options\n\n  This generator uses default options provided in the `:generators`\n  configuration of your application. These are the defaults:\n\n      config :your_app, :generators,\n        migration: true,\n        binary_id: false,\n        timestamp_type: :naive_datetime,\n        sample_binary_id: \"11111111-1111-1111-1111-111111111111\"\n\n  You can override those options per invocation by providing corresponding\n  switches, e.g. `--no-binary-id` to use normal ids despite the default\n  configuration or `--migration` to force generation of the migration.\n\n  Read the documentation for `phx.gen.schema` for more information on\n  attributes.\n\n  ## Skipping prompts\n\n  This generator will prompt you if there is an existing context with the same\n  name, in order to provide more instructions on how to correctly use phoenix contexts.\n  You can skip this prompt and automatically merge the new schema access functions and tests into the\n  existing context using `--merge-with-existing-context`. To prevent changes to\n  the existing context and exit the generator, use `--no-merge-with-existing-context`.\n  \"\"\"\n\n  use Mix.Task\n\n  alias Mix.Phoenix.{Context, Schema}\n  alias Mix.Tasks.Phx.Gen\n\n  @switches [\n    binary_id: :boolean,\n    table: :string,\n    web: :string,\n    schema: :boolean,\n    context: :boolean,\n    context_app: :string,\n    merge_with_existing_context: :boolean,\n    prefix: :string,\n    live: :boolean,\n    compile: :boolean,\n    primary_key: :string,\n    migration: :boolean,\n    scope: :string,\n    no_scope: :boolean\n  ]\n\n  @default_opts [schema: true, context: true]\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.context must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    {context, schema} = build(args)\n\n    binding = [\n      context: context,\n      schema: schema,\n      scope: context.scope,\n      primary_key: schema.opts[:primary_key] || :id\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(context)\n    prompt_for_code_injection(context)\n\n    context\n    |> copy_new_files(paths, binding)\n    |> print_shell_instructions()\n  end\n\n  defp prompt_for_conflicts(context) do\n    context\n    |> files_to_be_generated()\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  @doc false\n  def build(args, opts \\\\ []) do\n    help = Keyword.get(opts, :help_module, __MODULE__)\n    optional = Keyword.get(opts, :name_optional, false)\n\n    {opts, parsed, _} = parse_opts(args)\n\n    {context_name, schema_name, plural, schema_args} =\n      validate_args!(parsed, optional, help)\n\n    schema_module = inspect(Module.concat(context_name, schema_name))\n    schema = Gen.Schema.build([schema_module, plural | schema_args], opts, help)\n    context = Context.new(context_name, schema, opts)\n    {context, schema}\n  end\n\n  defp parse_opts(args) do\n    {opts, parsed, invalid} = OptionParser.parse(args, switches: @switches)\n\n    merged_opts =\n      @default_opts\n      |> Keyword.merge(opts)\n      |> put_context_app(opts[:context_app])\n\n    {merged_opts, parsed, invalid}\n  end\n\n  defp put_context_app(opts, nil), do: opts\n\n  defp put_context_app(opts, string) do\n    Keyword.put(opts, :context_app, String.to_atom(string))\n  end\n\n  @doc false\n  def files_to_be_generated(%Context{schema: schema}) do\n    if schema.generate? do\n      Gen.Schema.files_to_be_generated(schema)\n    else\n      []\n    end\n  end\n\n  @doc false\n  def copy_new_files(%Context{schema: schema} = context, paths, binding) do\n    if schema.generate?, do: Gen.Schema.copy_new_files(schema, paths, binding)\n    inject_schema_access(context, paths, binding)\n    inject_tests(context, paths, binding)\n    inject_test_fixture(context, paths, binding)\n\n    context\n  end\n\n  @doc false\n  def ensure_context_file_exists(%Context{file: file} = context, paths, binding) do\n    unless Context.pre_existing?(context) do\n      Mix.Generator.create_file(\n        file,\n        Mix.Phoenix.eval_from(paths, \"priv/templates/phx.gen.context/context.ex.eex\", binding)\n      )\n    end\n  end\n\n  defp inject_schema_access(%Context{file: file} = context, paths, binding) do\n    ensure_context_file_exists(context, paths, binding)\n\n    paths\n    |> Mix.Phoenix.eval_from(\n      \"priv/templates/phx.gen.context/#{schema_access_template(context)}\",\n      binding\n    )\n    |> inject_eex_before_final_end(file, binding)\n  end\n\n  defp write_file(content, file) do\n    File.write!(file, content)\n  end\n\n  @doc false\n  def ensure_test_file_exists(%Context{test_file: test_file} = context, paths, binding) do\n    unless Context.pre_existing_tests?(context) do\n      Mix.Generator.create_file(\n        test_file,\n        Mix.Phoenix.eval_from(paths, \"priv/templates/phx.gen.context/context_test.exs.eex\", binding)\n      )\n    end\n  end\n\n  defp inject_tests(%Context{test_file: test_file} = context, paths, binding) do\n    ensure_test_file_exists(context, paths, binding)\n\n    file =\n      if context.schema.scope do\n        \"test_cases_scope.exs.eex\"\n      else\n        \"test_cases.exs.eex\"\n      end\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.context/#{file}\", binding)\n    |> inject_eex_before_final_end(test_file, binding)\n  end\n\n  @doc false\n  def ensure_test_fixtures_file_exists(\n        %Context{test_fixtures_file: test_fixtures_file} = context,\n        paths,\n        binding\n      ) do\n    unless Context.pre_existing_test_fixtures?(context) do\n      Mix.Generator.create_file(\n        test_fixtures_file,\n        Mix.Phoenix.eval_from(paths, \"priv/templates/phx.gen.context/fixtures_module.ex.eex\", binding)\n      )\n    end\n  end\n\n  defp inject_test_fixture(\n         %Context{test_fixtures_file: test_fixtures_file} = context,\n         paths,\n         binding\n       ) do\n    ensure_test_fixtures_file_exists(context, paths, binding)\n\n    paths\n    |> Mix.Phoenix.eval_from(\"priv/templates/phx.gen.context/fixtures.ex.eex\", binding)\n    |> Mix.Phoenix.prepend_newline()\n    |> inject_eex_before_final_end(test_fixtures_file, binding)\n\n    maybe_print_unimplemented_fixture_functions(context)\n  end\n\n  defp maybe_print_unimplemented_fixture_functions(%Context{} = context) do\n    fixture_functions_needing_implementations =\n      Enum.flat_map(\n        context.schema.fixture_unique_functions,\n        fn\n          {_field, {_function_name, function_def, true}} -> [function_def]\n          {_field, {_function_name, _function_def, false}} -> []\n        end\n      )\n\n    if Enum.any?(fixture_functions_needing_implementations) do\n      Mix.shell().info(\"\"\"\n\n      Some of the generated database columns are unique. Please provide\n      unique implementations for the following fixture function(s) in\n      #{context.test_fixtures_file}:\n\n      #{fixture_functions_needing_implementations |> Enum.map_join(&indent(&1, 2)) |> String.trim_trailing()}\n      \"\"\")\n    end\n  end\n\n  defp indent(string, spaces) do\n    indent_string = String.duplicate(\" \", spaces)\n\n    string\n    |> String.split(\"\\n\")\n    |> Enum.map_join(fn line ->\n      if String.trim(line) == \"\" do\n        \"\\n\"\n      else\n        indent_string <> line <> \"\\n\"\n      end\n    end)\n  end\n\n  defp inject_eex_before_final_end(content_to_inject, file_path, binding) do\n    file = File.read!(file_path)\n\n    if String.contains?(file, content_to_inject) do\n      :ok\n    else\n      Mix.shell().info([:green, \"* injecting \", :reset, Path.relative_to_cwd(file_path)])\n\n      file\n      |> String.trim_trailing()\n      |> String.trim_trailing(\"end\")\n      |> EEx.eval_string(binding)\n      |> Kernel.<>(content_to_inject)\n      |> Kernel.<>(\"end\\n\")\n      |> write_file(file_path)\n    end\n  end\n\n  @doc false\n  def print_shell_instructions(%Context{schema: schema}) do\n    if schema.generate? do\n      Gen.Schema.print_shell_instructions(schema)\n    else\n      :ok\n    end\n  end\n\n  defp schema_access_template(%Context{schema: schema}) do\n    cond do\n      schema.generate? && schema.scope ->\n        \"schema_access_scope.ex.eex\"\n\n      schema.generate? ->\n        \"schema_access.ex.eex\"\n\n      schema.scope ->\n        \"access_no_schema_scope.ex.eex\"\n\n      true ->\n        \"access_no_schema.ex.eex\"\n    end\n  end\n\n  defp validate_args!(\n         [maybe_context_name, schema_name_or_plural, plural_or_first_attr | schema_args],\n         optional,\n         help\n       ) do\n    has_context? =\n      case schema_name_or_plural do\n        <<char, _rest::binary>> when char in ?A..?Z -> true\n        _ -> not optional\n      end\n\n    {context, schema, plural, schema_args} =\n      if has_context? do\n        {maybe_context_name, schema_name_or_plural, plural_or_first_attr, schema_args}\n      else\n        # mix phx.gen.live User users name:string\n        # we generate the context from the plural \"users\" -> Users\n        context = Phoenix.Naming.camelize(schema_name_or_plural)\n\n        if context == maybe_context_name do\n          # if someone did\n          # mix phx.gen.live Users users name\n          Mix.raise(\"\"\"\n          The given schema #{maybe_context_name} is equal to the camelized version of\n          the table plural #{schema_name_or_plural}, but the schema is expected to be singular.\n\n          Please pass an explicit context option like:\n\n              mix phx.gen.live #{context} #{maybe_context_name} #{schema_name_or_plural}\n\n          if this is what you want.\n          \"\"\")\n        end\n\n        {context, maybe_context_name, schema_name_or_plural, [plural_or_first_attr | schema_args]}\n      end\n\n    cond do\n      not Context.valid?(context) ->\n        help.raise_with_help(\n          \"Expected the context, #{inspect(context)}, to be a valid module name\"\n        )\n\n      not Schema.valid?(schema) ->\n        help.raise_with_help(\"Expected the schema, #{inspect(schema)}, to be a valid module name\")\n\n      context == schema ->\n        help.raise_with_help(\"The context and schema should have different names\")\n\n      context == Mix.Phoenix.base() ->\n        help.raise_with_help(\n          \"Cannot generate context #{context} because it has the same name as the application\"\n        )\n\n      schema == Mix.Phoenix.base() ->\n        help.raise_with_help(\n          \"Cannot generate schema #{schema} because it has the same name as the application\"\n        )\n\n      true ->\n        {context, schema, plural, schema_args}\n    end\n  end\n\n  defp validate_args!(_, _, help) do\n    help.raise_with_help(\"Invalid arguments\")\n  end\n\n  @doc false\n  def raise_with_help(msg) do\n    Mix.raise(\"\"\"\n    #{msg}\n\n    mix phx.gen.html, phx.gen.json, phx.gen.live, and phx.gen.context\n    expect a context module name, followed by singular and plural names\n    of the generated resource, ending with any number of attributes.\n    For example:\n\n        mix phx.gen.html [Accounts] User users name:string\n        mix phx.gen.json [Accounts] User users name:string\n        mix phx.gen.live [Accounts] User users name:string\n        mix phx.gen.context Accounts User users name:string\n\n    The context serves as the API boundary for the given resource.\n    It is optional except for phx.gen.context.\n    Multiple resources may belong to a context and a resource may be\n    split over distinct contexts (such as Accounts.User and Payments.User).\n    \"\"\")\n  end\n\n  @doc false\n  def prompt_for_code_injection(%Context{generate?: false}), do: :ok\n\n  def prompt_for_code_injection(%Context{} = context) do\n    if Context.pre_existing?(context) && !merge_with_existing_context?(context) do\n      System.halt()\n    end\n  end\n\n  defp merge_with_existing_context?(%Context{} = context) do\n    Keyword.get_lazy(context.opts, :merge_with_existing_context, fn ->\n      function_count = Context.function_count(context)\n      file_count = Context.file_count(context)\n\n      Mix.shell().info(\"\"\"\n      You are generating into an existing context.\n\n      The #{inspect(context.module)} context currently has #{singularize(function_count, \"functions\")} and \\\n      #{singularize(file_count, \"files\")} in its directory.\n\n        * It's OK to have multiple resources in the same context as \\\n      long as they are closely related. But if a context grows too \\\n      large, consider breaking it apart\n\n        * If they are not closely related, another context probably works better\n\n      The fact that two entities are related in the database does not mean they belong \\\n      to the same context.\n\n      If you are not sure, prefer creating a new context over adding to the existing one.\n      \"\"\")\n\n      Mix.shell().yes?(\"Would you like to proceed?\")\n    end)\n  end\n\n  defp singularize(1, plural), do: \"1 \" <> String.trim_trailing(plural, \"s\")\n  defp singularize(amount, plural), do: \"#{amount} #{plural}\"\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.embedded.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Embedded do\n  @shortdoc \"Generates an embedded Ecto schema file\"\n\n  @moduledoc \"\"\"\n  Generates an embedded Ecto schema for casting/validating data outside the DB.\n\n  ```console\n  $ mix phx.gen.embedded Blog.Post title:string views:integer\n  ```\n\n  The first argument is the schema module followed by the schema attributes.\n\n  The generated schema above will contain:\n\n    * an embedded schema file in `lib/my_app/blog/post.ex`\n\n  ## Attributes\n\n  The resource fields are given using `name:type` syntax\n  where type are the types supported by Ecto. Omitting\n  the type makes it default to `:string`:\n\n  ```console\n  $ mix phx.gen.embedded Blog.Post title views:integer\n  ```\n\n  The following types are supported:\n\n  #{for attr <- Mix.Phoenix.Schema.valid_types(), do: \"  * `#{inspect attr}`\\n\"}\n    * `:datetime` - An alias for `:naive_datetime`\n  \"\"\"\n  use Mix.Task\n\n  alias Mix.Phoenix.Schema\n\n  @switches [binary_id: :boolean, web: :string]\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise \"mix phx.gen.embedded must be invoked from within your *_web application root directory\"\n    end\n\n    schema = build(args)\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(schema)\n\n    copy_new_files(schema, paths, schema: schema)\n  end\n\n  @doc false\n  def build(args) do\n    {schema_opts, parsed, _} = OptionParser.parse(args, switches: @switches)\n    [schema_name | attrs] = validate_args!(parsed)\n    opts =\n      schema_opts\n      |> Keyword.put(:embedded, true)\n      |> Keyword.put(:migration, false)\n\n    schema = Schema.new(schema_name, nil, attrs, opts)\n\n    schema\n  end\n\n  @doc false\n  def validate_args!([schema | _] = args) do\n    if Schema.valid?(schema) do\n      args\n    else\n      raise_with_help \"Expected the schema argument, #{inspect schema}, to be a valid module name\"\n    end\n  end\n  def validate_args!(_) do\n    raise_with_help \"Invalid arguments\"\n  end\n\n  @doc false\n  @spec raise_with_help(String.t) :: no_return()\n  def raise_with_help(msg) do\n    Mix.raise \"\"\"\n    #{msg}\n\n    mix phx.gen.embedded expects a module name followed by\n    any number of attributes:\n\n        mix phx.gen.embedded Blog.Post title:string\n    \"\"\"\n  end\n\n\n  defp prompt_for_conflicts(schema) do\n    schema\n    |> files_to_be_generated()\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  @doc false\n  def files_to_be_generated(%Schema{} = schema) do\n    [{:eex, \"embedded_schema.ex.eex\", schema.file}]\n  end\n\n  @doc false\n  def copy_new_files(%Schema{} = schema, paths, binding) do\n    files = files_to_be_generated(schema)\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.embedded\", binding, files)\n\n    schema\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen do\n  use Mix.Task\n\n  @shortdoc \"Lists all available Phoenix generators\"\n\n  @moduledoc \"\"\"\n  Lists all available Phoenix generators.\n\n  ## CRUD related generators\n\n  The table below shows a summary of the contents created by the CRUD generators:\n\n  | Task | Schema | Migration | Context | Controller | View | LiveView |\n  |:------------------ |:-:|:-:|:-:|:-:|:-:|:-:|\n  | `phx.gen.embedded` | x |   |   |   |   |   |\n  | `phx.gen.schema`   | x | x |   |   |   |   |\n  | `phx.gen.context`  | x | x | x |   |   |   |\n  | `phx.gen.live`     | x | x | x |   |   | x |\n  | `phx.gen.json`     | x | x | x | x | x |   |\n  | `phx.gen.html`     | x | x | x | x | x |   |\n\n  ## Customizing generators\n  \n  You can override the default templates used by generators.\n  \n  For example, to customize `phx.gen.live`, you can copy and edit the generator templates\n  to your own project's priv folder:\n  \n  First, create the directory for your custom `phx.gen.live` templates:\n\n  ```console\n  $ mkdir -p priv/templates/phx.gen.live\n  ```\n\n  Next, copy the default phx.gen.live generator templates into your project so you can customize them:\n\n  ```console\n  $ cp -r deps/phoenix/priv/templates/phx.gen.live/* priv/templates/phx.gen.live/\n  ```\n\n  Phoenix generators will look for templates in your project's `priv/templates` directory first.\n  If a matching template is found, it will be used instead of the default.\n\n  Note generator templates may change between minor or even patch Phoenix releases,\n  so custom templates may require updates after upgrading. Use this mechanism at your\n  own risk.\n  \"\"\"\n\n  def run(_args) do\n    Mix.Task.run(\"help\", [\"--search\", \"phx.gen.\"])\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.html.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Html do\n  @shortdoc \"Generates context and controller for an HTML resource\"\n\n  @moduledoc \"\"\"\n  Generates controller with view, templates, schema and context for an HTML resource.\n\n  The format is:\n\n  ```console\n  $ mix phx.gen.html [<context>] <schema> <table> <attr:type> [<attr:type>...]\n  ```\n\n  For example:\n\n  ```console\n  $ mix phx.gen.html User users name:string age:integer\n  ```\n\n  Will generate a `User` schema for the `users` table within the `Users` context,\n  with the attributes `name` (as a string) and `age` (as an integer).\n\n  You can also explicitly pass the context name as argument, whenever the context\n  is well defined:\n\n  ```console\n  $ mix phx.gen.html Accounts User users name:string age:integer\n  ```\n\n  The first argument is the context module (`Accounts`) followed by\n  the schema module (`User`), table name (`users`), and attributes.\n\n  The context is an Elixir module that serves as an API boundary for\n  the given resource. A context often holds many related resources.\n  Therefore, if the context already exists, it will be augmented with\n  functions for the given resource.\n\n  The schema is responsible for mapping the database fields into an\n  Elixir struct. It is followed by a list of attributes with their\n  respective names and types. See `mix phx.gen.schema` for more\n  information on attributes.\n\n  Overall, this generator will add the following files to `lib/`:\n\n    * a controller in `lib/my_app_web/controllers/user_controller.ex`\n    * default CRUD HTML templates in `lib/my_app_web/controllers/user_html`\n    * an HTML view collocated with the controller in `lib/my_app_web/controllers/user_html.ex`\n    * a schema in `lib/my_app/accounts/user.ex`, with an `users` table\n    * a context module in `lib/my_app/accounts.ex` for the accounts API\n\n  Additionally, this generator creates the following files:\n\n    * a migration for the schema in `priv/repo/migrations`\n    * a controller test module in `test/my_app/controllers/user_controller_test.exs`\n    * a context test module in `test/my_app/accounts_test.exs`\n    * a context test helper module in `test/support/fixtures/accounts_fixtures.ex`\n\n  If the context already exists, this generator injects functions for the given resource into\n  the context, context test, and context test helper modules.\n\n  ## Scopes\n\n  If your application configures its own default [scope](scopes.md), then this generator\n  will automatically make sure all of your context operations are correctly scoped.\n  You can pass the `--no-scope` flag to disable the scoping.\n\n  ## Umbrella app configuration\n\n  By default, Phoenix injects both web and domain specific functionality into the same\n  application. When using umbrella applications, those concerns are typically broken\n  into two separate apps, your context application - let's call it `my_app` - and its web\n  layer, which Phoenix assumes to be `my_app_web`.\n\n  You can teach Phoenix to use this style via the `:context_app` configuration option\n  in your `my_app_umbrella/config/config.exs`:\n\n      config :my_app_web,\n        ecto_repos: [Stuff.Repo],\n        generators: [context_app: :my_app]\n\n  Alternatively, the `--context-app` option may be supplied to the generator:\n\n  ```console\n  $ mix phx.gen.html Accounts User users --context-app my_app\n  ```\n\n  ## Web namespace\n\n  By default, the controller and HTML views are not namespaced but you can add\n  a namespace by passing the `--web` flag with a module name, for example:\n\n  ```console\n  $ mix phx.gen.html Accounts User users --web Accounts\n  ```\n\n  Which would generate a `lib/app_web/controllers/accounts/user_controller.ex` and\n  `lib/app_web/controllers/accounts/user_html.ex`.\n\n  ## Customizing the context, schema, tables and migrations\n\n  In some cases, you may wish to bootstrap HTML templates, controllers,\n  and controller tests, but leave internal implementation of the context\n  or schema to yourself. You can use the `--no-context` and `--no-schema`\n  flags for file generation control. Note `--no-context` implies `--no-schema`:\n\n  ```console\n  $ mix phx.gen.live Accounts User users --no-context name:string\n  ```\n\n  In the cases above, tests are still generated, but they will all fail.\n\n  You can also change the table name or configure the migrations to\n  use binary ids for primary keys, see `mix phx.gen.schema` for more\n  information.\n  \"\"\"\n  use Mix.Task\n\n  alias Mix.Phoenix.{Context, Schema, Scope}\n  alias Mix.Tasks.Phx.Gen\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.html must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    Mix.Phoenix.ensure_live_view_compat!(__MODULE__)\n\n    {context, schema} = Gen.Context.build(args, name_optional: true)\n\n    if schema.attrs == [] do\n      Mix.raise(\"\"\"\n      No attributes provided. The phx.gen.html generator requires at least one attribute. For example:\n\n        mix phx.gen.html Accounts User users name:string\n\n      \"\"\")\n    end\n\n    Gen.Context.prompt_for_code_injection(context)\n\n    {conn_scope, context_scope_prefix} =\n      if schema.scope do\n        base = \"conn.assigns.#{schema.scope.assign_key}\"\n        {base, \"#{base}, \"}\n      else\n        {\"\", \"\"}\n      end\n\n    binding = [\n      context: context,\n      schema: schema,\n      primary_key: schema.opts[:primary_key] || :id,\n      scope: schema.scope,\n      inputs: inputs(schema),\n      conn_scope: conn_scope,\n      context_scope_prefix: context_scope_prefix,\n      scope_conn_route_prefix: Scope.route_prefix(conn_scope, schema),\n      scope_param_route_prefix: Scope.route_prefix(\"scope\", schema),\n      scope_assign_route_prefix: scope_assign_route_prefix(schema),\n      test_context_scope:\n        if(schema.scope && schema.scope.route_prefix, do: \", scope: scope\", else: \"\")\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(context)\n\n    context\n    |> copy_new_files(paths, binding)\n    |> print_shell_instructions()\n  end\n\n  defp prompt_for_conflicts(context) do\n    context\n    |> files_to_be_generated()\n    |> Kernel.++(context_files(context))\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  defp context_files(%Context{generate?: true} = context) do\n    Gen.Context.files_to_be_generated(context)\n  end\n\n  defp context_files(%Context{generate?: false}) do\n    []\n  end\n\n  @doc false\n  def files_to_be_generated(%Context{schema: schema, context_app: context_app}) do\n    singular = schema.singular\n    web_prefix = Mix.Phoenix.web_path(context_app)\n    test_prefix = Mix.Phoenix.web_test_path(context_app)\n    web_path = to_string(schema.web_path)\n    controller_pre = Path.join([web_prefix, \"controllers\", web_path])\n    test_pre = Path.join([test_prefix, \"controllers\", web_path])\n\n    [\n      {:eex, \"controller.ex.eex\", Path.join([controller_pre, \"#{singular}_controller.ex\"])},\n      {:eex, \"edit.html.heex.eex\", Path.join([controller_pre, \"#{singular}_html\", \"edit.html.heex\"])},\n      {:eex, \"index.html.heex.eex\",\n       Path.join([controller_pre, \"#{singular}_html\", \"index.html.heex\"])},\n      {:eex, \"new.html.heex.eex\", Path.join([controller_pre, \"#{singular}_html\", \"new.html.heex\"])},\n      {:eex, \"show.html.heex.eex\", Path.join([controller_pre, \"#{singular}_html\", \"show.html.heex\"])},\n      {:eex, \"resource_form.html.heex.eex\",\n       Path.join([controller_pre, \"#{singular}_html\", \"#{singular}_form.html.heex\"])},\n      {:eex, \"html.ex.eex\", Path.join([controller_pre, \"#{singular}_html.ex\"])},\n      {:eex, \"controller_test.exs.eex\", Path.join([test_pre, \"#{singular}_controller_test.exs\"])}\n    ]\n  end\n\n  @doc false\n  def copy_new_files(%Context{} = context, paths, binding) do\n    files = files_to_be_generated(context)\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.html\", binding, files)\n    if context.generate?, do: Gen.Context.copy_new_files(context, paths, binding)\n    context\n  end\n\n  @doc false\n  def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do\n    resource_path =\n      if schema.scope && schema.scope.route_prefix do\n        \"#{schema.scope.route_prefix}/#{schema.plural}\"\n      else\n        \"/#{schema.plural}\"\n      end\n\n    if schema.web_namespace do\n      Mix.shell().info(\"\"\"\n\n      Add the resource to your #{schema.web_namespace} :browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:\n\n          scope \"/#{schema.web_path}\", #{inspect(Module.concat(context.web_module, schema.web_namespace))} do\n            pipe_through :browser\n            ...\n            resources \"#{resource_path}\", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: \"#{schema.opts[:primary_key]}\"]}\n          end\n      \"\"\")\n    else\n      Mix.shell().info(\"\"\"\n\n      Add the resource to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:\n\n          resources \"#{resource_path}\", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: \"#{schema.opts[:primary_key]}\"]}\n      \"\"\")\n    end\n\n    if schema.scope do\n      Mix.shell().info(\n        \"Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign.\"\n      )\n    end\n\n    if context.generate?, do: Gen.Context.print_shell_instructions(context)\n  end\n\n  @doc false\n  def inputs(%Schema{} = schema) do\n    schema.attrs\n    |> Enum.reject(fn {_key, type} -> type == :map end)\n    |> Enum.map(fn\n      {key, :integer} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" />)\n\n      {key, :float} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" step=\"any\" />)\n\n      {key, :decimal} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" step=\"any\" />)\n\n      {key, :boolean} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"checkbox\" label=\"#{label(key)}\" />)\n\n      {key, :text} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"textarea\" label=\"#{label(key)}\" />)\n\n      {key, :date} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"date\" label=\"#{label(key)}\" />)\n\n      {key, :time} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"time\" label=\"#{label(key)}\" />)\n\n      {key, :utc_datetime} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"datetime-local\" label=\"#{label(key)}\" />)\n\n      {key, :naive_datetime} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"datetime-local\" label=\"#{label(key)}\" />)\n\n      {key, {:array, _} = type} ->\n        ~s\"\"\"\n        <.input\n          field={f[#{inspect(key)}]}\n          type=\"select\"\n          multiple\n          label=\"#{label(key)}\"\n          options={#{inspect(default_options(type))}}\n        />\n        \"\"\"\n\n      {key, {:enum, _}} ->\n        ~s\"\"\"\n        <.input\n          field={f[#{inspect(key)}]}\n          type=\"select\"\n          label=\"#{label(key)}\"\n          prompt=\"Choose a value\"\n          options={Ecto.Enum.values(#{inspect(schema.module)}, #{inspect(key)})}\n        />\n        \"\"\"\n\n      {key, _} ->\n        ~s(<.input field={f[#{inspect(key)}]} type=\"text\" label=\"#{label(key)}\" />)\n    end)\n  end\n\n  defp default_options({:array, :string}),\n    do: Enum.map([1, 2], &{\"Option #{&1}\", \"option#{&1}\"})\n\n  defp default_options({:array, :integer}),\n    do: Enum.map([1, 2], &{\"#{&1}\", &1})\n\n  defp default_options({:array, _}), do: []\n\n  defp label(key), do: Phoenix.Naming.humanize(to_string(key))\n\n  defp scope_assign_route_prefix(\n         %{scope: %{route_prefix: route_prefix, assign_key: assign_key}} = schema\n       )\n       when not is_nil(route_prefix) do\n    Scope.route_prefix(\"@#{assign_key}\", schema)\n  end\n\n  defp scope_assign_route_prefix(_), do: \"\"\n\n  @doc false\n  def indent_inputs(inputs, column_padding) do\n    columns = String.duplicate(\" \", column_padding)\n\n    inputs\n    |> Enum.map(fn input ->\n      lines = input |> String.split(\"\\n\") |> Enum.reject(&(&1 == \"\"))\n\n      case lines do\n        [] ->\n          []\n\n        [line] ->\n          [columns, line]\n\n        [first_line | rest] ->\n          rest = Enum.map_join(rest, \"\\n\", &(columns <> &1))\n          [columns, first_line, \"\\n\", rest]\n      end\n    end)\n    |> Enum.intersperse(\"\\n\")\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.json.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Json do\n  @shortdoc \"Generates context and controller for a JSON resource\"\n\n  @moduledoc \"\"\"\n  Generates controller, JSON view, and context for a JSON resource.\n\n  The format is:\n\n  ```console\n  $ mix phx.gen.json [<context>] <schema> <table> <attr:type> [<attr:type>...]\n  ```\n\n  For example:\n\n  ```console\n  $ mix phx.gen.json User users name:string age:integer\n  ```\n\n  Will generate a `User` schema for the `users` table within the `Users` context,\n  with the attributes `name` (as a string) and `age` (as an integer).\n\n  You can also explicitly pass the context name as argument, whenever the context\n  is well defined:\n\n  ```console\n  $ mix phx.gen.json Accounts User users name:string age:integer\n  ```\n\n  The first argument is the context module (`Accounts`) followed by\n  the schema module (`User`), table name (`users`), and attributes.\n\n  The context is an Elixir module that serves as an API boundary for\n  the given resource. A context often holds many related resources.\n  Therefore, if the context already exists, it will be augmented with\n  functions for the given resource.\n\n  The schema is responsible for mapping the database fields into an\n  Elixir struct. It is followed by a list of attributes with their\n  respective names and types. See `mix phx.gen.schema` for more\n  information on attributes.\n\n  Overall, this generator will add the following files to `lib/`:\n\n    * a context module in `lib/app/accounts.ex` for the accounts API\n    * a schema in `lib/app/accounts/user.ex`, with an `users` table\n    * a controller in `lib/app_web/controllers/user_controller.ex`\n    * a JSON view collocated with the controller in `lib/app_web/controllers/user_json.ex`\n\n  A migration file for the repository and test files for the context and\n  controller features will also be generated.\n\n  ## API Prefix\n\n  By default, the prefix \"/api\" will be generated for API route paths.\n  This can be customized via the `:api_prefix` generators configuration:\n\n      config :your_app, :generators,\n        api_prefix: \"/api/v1\"\n\n  ## Scopes\n\n  If your application configures its own default [scope](scopes.md), then this generator\n  will automatically make sure all of your context operations are correctly scoped.\n  You can pass the `--no-scope` flag to disable the scoping.\n\n  ## Umbrella app configuration\n\n  By default, Phoenix injects both web and domain specific functionality into the same\n  application. When using umbrella applications, those concerns are typically broken\n  into two separate apps, your context application - let's call it `my_app` - and its web\n  layer, which Phoenix assumes to be `my_app_web`.\n\n  You can teach Phoenix to use this style via the `:context_app` configuration option\n  in your `my_app_umbrella/config/config.exs`:\n\n      config :my_app_web,\n        ecto_repos: [Stuff.Repo],\n        generators: [context_app: :my_app]\n\n  Alternatively, the `--context-app` option may be supplied to the generator:\n\n  ```console\n  $ mix phx.gen.html Accounts User users --context-app my_app\n  ```\n\n  ## Web namespace\n\n  By default, the controller and HTML views are not namespaced but you can add\n  a namespace by passing the `--web` flag with a module name, for example:\n\n  ```console\n  $ mix phx.gen.json Accounts User users --web Accounts\n  ```\n\n  Which would generate a `lib/app_web/controllers/accounts/user_controller.ex` and\n  `lib/app_web/controllers/accounts/user_json.ex`.\n\n  ## Customizing the context, schema, tables and migrations\n\n  In some cases, you may wish to bootstrap JSON views, controllers,\n  and controller tests, but leave internal implementation of the context\n  or schema to yourself. You can use the `--no-context` and `--no-schema`\n  flags for file generation control. Note `--no-context` implies `--no-schema`:\n\n  ```console\n  $ mix phx.gen.live Accounts User users --no-context name:string\n  ```\n\n  In the cases above, tests are still generated, but they will all fail.\n\n  You can also change the table name or configure the migrations to\n  use binary ids for primary keys, see `mix phx.gen.schema` for more\n  information.\n  \"\"\"\n\n  use Mix.Task\n\n  alias Mix.Phoenix.{Context, Scope}\n  alias Mix.Tasks.Phx.Gen\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.json must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    {context, schema} = Gen.Context.build(args, name_optional: true)\n\n    if schema.attrs == [] do\n      Mix.raise(\"\"\"\n      No attributes provided. The phx.gen.json generator requires at least one attribute. For example:\n\n        mix phx.gen.json Accounts User users name:string\n\n      \"\"\")\n    end\n\n    Gen.Context.prompt_for_code_injection(context)\n\n    {conn_scope, context_scope_prefix} =\n      if schema.scope do\n        base = \"conn.assigns.#{schema.scope.assign_key}\"\n        {base, \"#{base}, \"}\n      else\n        {\"\", \"\"}\n      end\n\n    binding = [\n      context: context,\n      schema: schema,\n      scope: schema.scope,\n      core_components?: Code.ensure_loaded?(Module.concat(context.web_module, \"CoreComponents\")),\n      gettext?: Code.ensure_loaded?(Module.concat(context.web_module, \"Gettext\")),\n      primary_key: schema.opts[:primary_key] || :id,\n      conn_scope: conn_scope,\n      context_scope_prefix: context_scope_prefix,\n      scope_conn_route_prefix: Scope.route_prefix(conn_scope, schema),\n      scope_param_route_prefix: Scope.route_prefix(\"scope\", schema),\n      test_context_scope:\n        if(schema.scope && schema.scope.route_prefix, do: \", scope: scope\", else: \"\")\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(context)\n\n    context\n    |> copy_new_files(paths, binding)\n    |> print_shell_instructions()\n  end\n\n  defp prompt_for_conflicts(context) do\n    context\n    |> files_to_be_generated()\n    |> Kernel.++(context_files(context))\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  defp context_files(%Context{generate?: true} = context) do\n    Gen.Context.files_to_be_generated(context)\n  end\n\n  defp context_files(%Context{generate?: false}) do\n    []\n  end\n\n  @doc false\n  def files_to_be_generated(%Context{schema: schema, context_app: context_app}) do\n    singular = schema.singular\n    web = Mix.Phoenix.web_path(context_app)\n    test_prefix = Mix.Phoenix.web_test_path(context_app)\n    web_path = to_string(schema.web_path)\n    controller_pre = Path.join([web, \"controllers\", web_path])\n    test_pre = Path.join([test_prefix, \"controllers\", web_path])\n\n    [\n      {:eex, \"controller.ex.eex\", Path.join([controller_pre, \"#{singular}_controller.ex\"])},\n      {:eex, \"json.ex.eex\", Path.join([controller_pre, \"#{singular}_json.ex\"])},\n      {:new_eex, \"changeset_json.ex.eex\", Path.join([web, \"controllers/changeset_json.ex\"])},\n      {:eex, \"controller_test.exs.eex\", Path.join([test_pre, \"#{singular}_controller_test.exs\"])},\n      {:new_eex, \"fallback_controller.ex.eex\", Path.join([web, \"controllers/fallback_controller.ex\"])}\n    ]\n  end\n\n  @doc false\n  def copy_new_files(%Context{} = context, paths, binding) do\n    files = files_to_be_generated(context)\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.json\", binding, files)\n    if context.generate?, do: Gen.Context.copy_new_files(context, paths, binding)\n\n    context\n  end\n\n  @doc false\n  def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do\n    resource_path =\n      if schema.scope && schema.scope.route_prefix do\n        \"#{schema.scope.route_prefix}/#{schema.plural}\"\n      else\n        \"/#{schema.plural}\"\n      end\n\n    if schema.web_namespace do\n      Mix.shell().info(\"\"\"\n\n      Add the resource to your #{schema.web_namespace} :api scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:\n\n          scope \"/#{schema.web_path}\", #{inspect(Module.concat(context.web_module, schema.web_namespace))} do\n            pipe_through :api\n            ...\n            resources \"#{resource_path}\", #{inspect(schema.alias)}Controller#{if schema.opts[:primary_key], do: ~s[, param: \"#{schema.opts[:primary_key]}\"]}\n          end\n      \"\"\")\n    else\n      Mix.shell().info(\"\"\"\n\n      Add the resource to the \"#{Application.get_env(ctx_app, :generators)[:api_prefix] || \"/api\"}\" scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:\n\n          resources \"#{resource_path}\", #{inspect(schema.alias)}Controller, except: [:new, :edit]#{if schema.opts[:primary_key], do: ~s[, param: \"#{schema.opts[:primary_key]}\"]}\n      \"\"\")\n    end\n\n    if schema.scope do\n      Mix.shell().info(\n        \"Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign.\"\n      )\n    end\n\n    if context.generate?, do: Gen.Context.print_shell_instructions(context)\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.live.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Live do\n  @shortdoc \"Generates LiveView, templates, and context for a resource\"\n\n  @moduledoc \"\"\"\n  Generates LiveView, templates, and context for a resource.\n\n  The format is:\n\n  ```console\n  $ mix phx.gen.live [<context>] <schema> <table> <attr:type> [<attr:type>...]\n  ```\n\n  For example:\n\n  ```console\n  $ mix phx.gen.live User users name:string age:integer\n  ```\n\n  Will generate a `User` schema for the `users` table within the `Users` context,\n  with the attributes `name` (as a string) and `age` (as an integer).\n\n  You can also explicitly pass the context name as argument, whenever the context\n  is well defined:\n\n  ```console\n  $ mix phx.gen.live Accounts User users name:string age:integer\n  ```\n\n  The first argument is the context module (`Accounts`) followed by\n  the schema module (`User`), table name (`users`), and attributes.\n\n  The context is an Elixir module that serves as an API boundary for\n  the given resource. A context often holds many related resources.\n  Therefore, if the context already exists, it will be augmented with\n  functions for the given resource.\n\n  The schema is responsible for mapping the database fields into an\n  Elixir struct. It is followed by a list of attributes with their\n  respective names and types. See `mix phx.gen.schema` for more\n  information on attributes.\n\n  Overall, this generator will add the following files to `lib/`:\n\n    * a context module in `lib/app/accounts.ex` for the accounts API\n    * a schema in `lib/app/accounts/user.ex`, with a `users` table\n    * a LiveView in `lib/app_web/live/user_live/show.ex`\n    * a LiveView in `lib/app_web/live/user_live/index.ex`\n    * a LiveView in `lib/app_web/live/user_live/form.ex`\n    * a components module in `lib/app_web/components/core_components.ex`\n      if none exists\n\n  After file generation is complete, there will be output regarding required\n  updates to the `lib/app_web/router.ex` file.\n\n      Add the live routes to your browser scope in lib/app_web/router.ex:\n\n        live \"/users\", UserLive.Index, :index\n        live \"/users/new\", UserLive.Form, :new\n        live \"/users/:id\", UserLive.Show, :show\n        live \"/users/:id/edit\", UserLive.Form, :edit\n\n  ## Scopes\n\n  If your application configures its own default [scope](scopes.md), then this generator\n  will automatically make sure all of your context operations are correctly scoped.\n  You can pass the `--no-scope` flag to disable the scoping.\n\n  ## Umbrella app configuration\n\n  By default, Phoenix injects both web and domain specific functionality into the same\n  application. When using umbrella applications, those concerns are typically broken\n  into two separate apps, your context application - let's call it `my_app` - and its web\n  layer, which Phoenix assumes to be `my_app_web`.\n\n  You can teach Phoenix to use this style via the `:context_app` configuration option\n  in your `my_app_umbrella/config/config.exs`:\n\n      config :my_app_web,\n        ecto_repos: [Stuff.Repo],\n        generators: [context_app: :my_app]\n\n  Alternatively, the `--context-app` option may be supplied to the generator:\n\n  ```console\n  $ mix phx.gen.html Accounts User users --context-app my_app\n  ```\n\n  ## Web namespace\n\n  By default, the LiveView modules are defined within a folder named\n  after the schema, such as `lib/app_web/live/user_live`. You can add\n  additional namespaces by passing the `--web` flag with a module name,\n  for example:\n\n  ```console\n  $ mix phx.gen.live Accounts User users --web Accounts name:string\n  ```\n\n  Which would generate the LiveViews in `lib/app_web/live/accounts/user_live/`,\n  namespaced `AppWeb.Accounts.UserLive` instead of `AppWeb.UserLive`.\n\n  ## Customizing the context, schema, tables and migrations\n\n  In some cases, you may wish to bootstrap HTML templates, LiveViews,\n  and tests, but leave internal implementation of the context or schema\n  to yourself. You can use the `--no-context` and `--no-schema` flags\n  flags for file generation control. Note `--no-context` implies `--no-schema`:\n\n  ```console\n  $ mix phx.gen.live Accounts User users --no-context name:string\n  ```\n\n  In the cases above, tests are still generated, but they will all fail.\n\n  You can also change the table name or configure the migrations to\n  use binary ids for primary keys, see `mix help phx.gen.schema` for more\n  information.\n  \"\"\"\n  use Mix.Task\n\n  alias Mix.Phoenix.{Context, Schema, Scope}\n  alias Mix.Tasks.Phx.Gen\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.live must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    Mix.Phoenix.ensure_live_view_compat!(__MODULE__)\n\n    {context, schema} = Gen.Context.build(args, name_optional: true)\n    validate_context!(context)\n\n    if schema.attrs == [] do\n      Mix.raise(\"\"\"\n      No attributes provided. The phx.gen.live generator requires at least one attribute. For example:\n\n        mix phx.gen.live Accounts User users name:string\n\n      \"\"\")\n    end\n\n    Gen.Context.prompt_for_code_injection(context)\n\n    {socket_scope, context_scope_prefix, assign_scope, assign_scope_prefix} =\n      if schema.scope do\n        base_socket = \"socket.assigns.#{schema.scope.assign_key}\"\n        base_assign = \"@#{schema.scope.assign_key}\"\n        {base_socket, \"#{base_socket}, \", base_assign, \"#{base_assign}, \"}\n      else\n        {\"\", \"\", \"\", \"\"}\n      end\n\n    binding = [\n      context: context,\n      schema: schema,\n      primary_key: schema.opts[:primary_key] || :id,\n      scope: schema.scope,\n      inputs: inputs(schema),\n      socket_scope: socket_scope,\n      context_scope_prefix: context_scope_prefix,\n      assign_scope: assign_scope,\n      assign_scope_prefix: assign_scope_prefix,\n      scope_param_route_prefix: Scope.route_prefix(\"scope\", schema),\n      scope_param: scope_param(schema),\n      scope_param_prefix: scope_param_prefix(schema),\n      scope_socket_route_prefix: Scope.route_prefix(socket_scope, schema),\n      scope_assign_route_prefix: scope_assign_route_prefix(schema),\n      test_context_scope:\n        if(schema.scope && schema.scope.route_prefix, do: \", scope: scope\", else: \"\")\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(context)\n\n    context\n    |> copy_new_files(binding, paths)\n    |> maybe_inject_imports()\n    |> print_shell_instructions()\n  end\n\n  defp validate_context!(context) do\n    cond do\n      context.schema.singular == \"form\" ->\n        Gen.Context.raise_with_help(\n          \"cannot use form as the schema name because it conflicts with the LiveView assigns!\"\n        )\n\n      true ->\n        :ok\n    end\n  end\n\n  defp prompt_for_conflicts(context) do\n    context\n    |> files_to_be_generated()\n    |> Kernel.++(context_files(context))\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  defp context_files(%Context{generate?: true} = context) do\n    Gen.Context.files_to_be_generated(context)\n  end\n\n  defp context_files(%Context{generate?: false}) do\n    []\n  end\n\n  defp files_to_be_generated(%Context{schema: schema, context_app: context_app}) do\n    web_prefix = Mix.Phoenix.web_path(context_app)\n    test_prefix = Mix.Phoenix.web_test_path(context_app)\n    web_path = to_string(schema.web_path)\n    live_subdir = \"#{schema.singular}_live\"\n    web_live = Path.join([web_prefix, \"live\", web_path, live_subdir])\n    test_live = Path.join([test_prefix, \"live\", web_path])\n\n    [\n      {:eex, \"show.ex.eex\", Path.join(web_live, \"show.ex\")},\n      {:eex, \"index.ex.eex\", Path.join(web_live, \"index.ex\")},\n      {:eex, \"form.ex.eex\", Path.join(web_live, \"form.ex\")},\n      {:eex, \"live_test.exs.eex\", Path.join(test_live, \"#{schema.singular}_live_test.exs\")},\n      {:new_eex, \"core_components.ex.eex\",\n       Path.join([web_prefix, \"components\", \"core_components.ex\"])}\n    ]\n  end\n\n  defp copy_new_files(%Context{} = context, binding, paths) do\n    files = files_to_be_generated(context)\n\n    binding =\n      Keyword.merge(binding,\n        assigns: %{\n          web_namespace: inspect(context.web_module),\n          gettext: true,\n          live: true,\n          # the core components are also generated in phx.new, so we check for\n          # esbuild (@javascript) - here we just assume that it's there\n          javascript: true\n        }\n      )\n\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.live\", binding, files)\n    if context.generate?, do: Gen.Context.copy_new_files(context, paths, binding)\n\n    context\n  end\n\n  defp maybe_inject_imports(%Context{context_app: ctx_app} = context) do\n    web_prefix = Mix.Phoenix.web_path(ctx_app)\n    [lib_prefix, web_dir] = Path.split(web_prefix)\n    file_path = Path.join(lib_prefix, \"#{web_dir}.ex\")\n    file = File.read!(file_path)\n    inject = \"import #{inspect(context.web_module)}.CoreComponents\"\n\n    if String.contains?(file, inject) do\n      :ok\n    else\n      do_inject_imports(context, file, file_path, inject)\n    end\n\n    context\n  end\n\n  defp do_inject_imports(context, file, file_path, inject) do\n    Mix.shell().info([:green, \"* injecting \", :reset, Path.relative_to_cwd(file_path)])\n\n    new_file =\n      String.replace(\n        file,\n        \"use Phoenix.Component\",\n        \"use Phoenix.Component\\n      #{inject}\"\n      )\n\n    if file != new_file do\n      File.write!(file_path, new_file)\n    else\n      Mix.shell().info(\"\"\"\n\n      Could not find use Phoenix.Component in #{file_path}.\n\n      This typically happens because your application was not generated\n      with the --live flag:\n\n          mix phx.new my_app --live\n\n      Please make sure LiveView is installed and that #{inspect(context.web_module)}\n      defines both `live_view/0` and `live_component/0` functions,\n      and that both functions import #{inspect(context.web_module)}.CoreComponents.\n      \"\"\")\n    end\n  end\n\n  @doc false\n  def print_shell_instructions(%Context{schema: schema, context_app: ctx_app} = context) do\n    prefix = Module.concat(context.web_module, schema.web_namespace)\n    web_path = Mix.Phoenix.web_path(ctx_app)\n\n    if schema.web_namespace do\n      Mix.shell().info(\"\"\"\n\n      Add the live routes to your #{schema.web_namespace} :browser scope in #{web_path}/router.ex:\n\n          scope \"/#{schema.web_path}\", #{inspect(prefix)} do\n            pipe_through :browser\n            ...\n\n      #{for line <- live_route_instructions(schema), do: \"      #{line}\"}\n          end\n      \"\"\")\n    else\n      Mix.shell().info(\"\"\"\n\n      Add the live routes to your browser scope in #{Mix.Phoenix.web_path(ctx_app)}/router.ex:\n\n      #{for line <- live_route_instructions(schema), do: \"    #{line}\"}\n      \"\"\")\n    end\n\n    if schema.scope do\n      Mix.shell().info(\n        \"Ensure the routes are defined in a block that sets the `#{inspect(context.scope.assign_key)}` assign.\"\n      )\n    end\n\n    if context.generate?, do: Gen.Context.print_shell_instructions(context)\n    maybe_print_upgrade_info()\n  end\n\n  defp maybe_print_upgrade_info do\n    unless Code.ensure_loaded?(Phoenix.LiveView.JS) do\n      Mix.shell().info(\"\"\"\n\n      You must update :phoenix_live_view to v0.18 or later and\n      :phoenix_live_dashboard to v0.7 or later to use the features\n      in this generator.\n      \"\"\")\n    end\n  end\n\n  defp live_route_instructions(schema) do\n    route_base =\n      if schema.scope && schema.scope.route_prefix do\n        scope_prefix = schema.scope.route_prefix\n        \"#{scope_prefix}/#{schema.plural}\"\n      else\n        \"/#{schema.plural}\"\n      end\n\n    [\n      ~s|live \"#{route_base}\", #{inspect(schema.alias)}Live.Index, :index\\n|,\n      ~s|live \"#{route_base}/new\", #{inspect(schema.alias)}Live.Form, :new\\n|,\n      ~s|live \"#{route_base}/:#{schema.opts[:primary_key] || :id}\", #{inspect(schema.alias)}Live.Show, :show\\n|,\n      ~s|live \"#{route_base}/:#{schema.opts[:primary_key] || :id}/edit\", #{inspect(schema.alias)}Live.Form, :edit|\n    ]\n  end\n\n  @doc false\n  def inputs(%Schema{} = schema) do\n    schema.attrs\n    |> Enum.reject(fn {_key, type} -> type == :map end)\n    |> Enum.map(fn\n      {_, {:references, _}} ->\n        nil\n\n      {key, :integer} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" />)\n\n      {key, :float} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" step=\"any\" />)\n\n      {key, :decimal} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"number\" label=\"#{label(key)}\" step=\"any\" />)\n\n      {key, :boolean} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"checkbox\" label=\"#{label(key)}\" />)\n\n      {key, :text} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"textarea\" label=\"#{label(key)}\" />)\n\n      {key, :date} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"date\" label=\"#{label(key)}\" />)\n\n      {key, :time} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"time\" label=\"#{label(key)}\" />)\n\n      {key, :utc_datetime} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"datetime-local\" label=\"#{label(key)}\" />)\n\n      {key, :naive_datetime} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"datetime-local\" label=\"#{label(key)}\" />)\n\n      {key, {:array, _} = type} ->\n        ~s\"\"\"\n        <.input\n          field={@form[#{inspect(key)}]}\n          type=\"select\"\n          multiple\n          label=\"#{label(key)}\"\n          options={#{inspect(default_options(type))}}\n        />\n        \"\"\"\n\n      {key, {:enum, _}} ->\n        ~s\"\"\"\n        <.input\n          field={@form[#{inspect(key)}]}\n          type=\"select\"\n          label=\"#{label(key)}\"\n          prompt=\"Choose a value\"\n          options={Ecto.Enum.values(#{inspect(schema.module)}, #{inspect(key)})}\n        />\n        \"\"\"\n\n      {key, _} ->\n        ~s(<.input field={@form[#{inspect(key)}]} type=\"text\" label=\"#{label(key)}\" />)\n    end)\n  end\n\n  defp default_options({:array, :string}),\n    do: Enum.map([1, 2], &{\"Option #{&1}\", \"option#{&1}\"})\n\n  defp default_options({:array, :integer}),\n    do: Enum.map([1, 2], &{\"#{&1}\", &1})\n\n  defp default_options({:array, _}), do: []\n\n  defp label(key), do: Phoenix.Naming.humanize(to_string(key))\n\n  defp scope_param(%{scope: nil}), do: \"\"\n\n  defp scope_param(%{scope: %{route_prefix: route_prefix}}) when not is_nil(route_prefix),\n    do: \"scope\"\n\n  defp scope_param(_), do: \"_scope\"\n\n  defp scope_param_prefix(schema) do\n    param = scope_param(schema)\n    if param != \"\", do: \"#{param}, \", else: \"\"\n  end\n\n  defp scope_assign_route_prefix(\n         %{scope: %{route_prefix: route_prefix, assign_key: assign_key}} = schema\n       )\n       when not is_nil(route_prefix) do\n    Scope.route_prefix(\"@#{assign_key}\", schema)\n  end\n\n  defp scope_assign_route_prefix(_), do: \"\"\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.notifier.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Notifier do\n  @shortdoc \"Generates a notifier that delivers emails by default\"\n\n  @moduledoc \"\"\"\n  Generates a notifier that delivers emails by default.\n\n      $ mix phx.gen.notifier Accounts User welcome_user reset_password confirmation_instructions\n\n  This task expects a context module name, followed by a\n  notifier name and one or more message names. Messages\n  are the functions that will be created prefixed by \"deliver\",\n  so the message name should be \"snake_case\" without punctuation.\n\n  Additionally a context app can be specified with the flag\n  `--context-app`, which is useful if the notifier is being\n  generated in a different app under an umbrella.\n\n      $ mix phx.gen.notifier Accounts User welcome_user --context-app marketing\n\n  The app \"marketing\" must exist before the command is executed.\n  \"\"\"\n\n  use Mix.Task\n\n  @switches [\n    context: :boolean,\n    context_app: :string,\n    prefix: :string\n  ]\n\n  @default_opts [context: true]\n\n  alias Mix.Phoenix.Context\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.notifier must be invoked from within your *_web application root directory\"\n      )\n    end\n\n    {context, notifier_module, messages} = build(args)\n\n    inflections = Mix.Phoenix.inflect(notifier_module)\n\n    binding = [\n      context: context,\n      inflections: inflections,\n      notifier_messages: messages\n    ]\n\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(context)\n\n    if \"--no-compile\" not in args do\n      Mix.Task.run(\"compile\")\n    end\n\n    context\n    |> copy_new_files(binding, paths)\n    |> maybe_print_mailer_installation_instructions()\n  end\n\n  @doc false\n  def build(args, help \\\\ __MODULE__) do\n    {opts, parsed, _} = parse_opts(args)\n\n    [context_name, notifier_name | notifier_messages] = validate_args!(parsed, help)\n\n    notifier_module = inspect(Module.concat(context_name, \"#{notifier_name}Notifier\"))\n    context = Context.new(notifier_module, opts)\n\n    {context, notifier_module, notifier_messages}\n  end\n\n  defp parse_opts(args) do\n    {opts, parsed, invalid} = OptionParser.parse(args, switches: @switches)\n\n    merged_opts =\n      @default_opts\n      |> Keyword.merge(opts)\n      |> put_context_app(opts[:context_app])\n\n    {merged_opts, parsed, invalid}\n  end\n\n  defp put_context_app(opts, nil), do: opts\n\n  defp put_context_app(opts, string) do\n    Keyword.put(opts, :context_app, String.to_atom(string))\n  end\n\n  defp validate_args!([context, notifier | messages] = args, help) do\n    cond do\n      not Context.valid?(context) ->\n        help.raise_with_help(\n          \"Expected the context, #{inspect(context)}, to be a valid module name\"\n        )\n\n      not valid_notifier?(notifier) ->\n        help.raise_with_help(\n          \"Expected the notifier, #{inspect(notifier)}, to be a valid module name\"\n        )\n\n      context == Mix.Phoenix.base() ->\n        help.raise_with_help(\n          \"Cannot generate context #{context} because it has the same name as the application\"\n        )\n\n      notifier == Mix.Phoenix.base() ->\n        help.raise_with_help(\n          \"Cannot generate notifier #{notifier} because it has the same name as the application\"\n        )\n\n      Enum.any?(messages, &(!valid_message?(&1))) ->\n        help.raise_with_help(\n          \"Cannot generate notifier #{inspect(notifier)} because one of the messages is invalid: #{Enum.map_join(messages, \", \", &inspect/1)}\"\n        )\n\n      true ->\n        args\n    end\n  end\n\n  defp validate_args!(_, help) do\n    help.raise_with_help(\"Invalid arguments\")\n  end\n\n  defp valid_notifier?(notifier) do\n    notifier =~ ~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/\n  end\n\n  defp valid_message?(message_name) do\n    message_name =~ ~r/^[a-z]+(\\_[a-z0-9]+)*$/\n  end\n\n  @doc false\n  @spec raise_with_help(String.t()) :: no_return()\n  def raise_with_help(msg) do\n    Mix.raise(\"\"\"\n    #{msg}\n\n    mix phx.gen.notifier expects a context module name, followed by a\n    notifier name and one or more message names. Messages are the\n    functions that will be created prefixed by \"deliver\", so the message\n    name should be \"snake_case\" without punctuation.\n    For example:\n\n        mix phx.gen.notifier Accounts User welcome reset_password\n\n    In this example the notifier will be called `UserNotifier` inside\n    the Accounts context. The functions `deliver_welcome/1` and\n    `reset_password/1` will be created inside this notifier.\n    \"\"\")\n  end\n\n  defp copy_new_files(%Context{} = context, binding, paths) do\n    files = files_to_be_generated(context)\n\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.notifier\", binding, files)\n\n    context\n  end\n\n  defp files_to_be_generated(%Context{} = context) do\n    [\n      {:eex, \"notifier.ex.eex\", context.file},\n      {:eex, \"notifier_test.exs.eex\", context.test_file}\n    ]\n  end\n\n  defp prompt_for_conflicts(context) do\n    context\n    |> files_to_be_generated()\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  @doc false\n  @spec maybe_print_mailer_installation_instructions(%Context{}) :: %Context{}\n  def maybe_print_mailer_installation_instructions(%Context{} = context) do\n    mailer_module = Module.concat([context.base_module, \"Mailer\"])\n\n    unless Code.ensure_loaded?(mailer_module) do\n      Mix.shell().info(\"\"\"\n      Unable to find the \"#{inspect(mailer_module)}\" module defined.\n\n      A mailer module like the following is expected to be defined\n      in your application in order to send emails.\n\n          defmodule #{inspect(mailer_module)} do\n            use Swoosh.Mailer, otp_app: #{inspect(context.context_app)}\n          end\n\n      It is also necessary to add \"swoosh\" as a dependency in your\n      \"mix.exs\" file:\n\n          def deps do\n            [{:swoosh, \"~> 1.4\"}]\n          end\n\n      Finally, an adapter needs to be set in your configuration:\n\n          import Config\n          config #{inspect(context.context_app)}, #{inspect(mailer_module)}, adapter: Swoosh.Adapters.Local\n\n      Check https://hexdocs.pm/swoosh for more details.\n      \"\"\")\n    end\n\n    context\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.presence.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Presence do\n  @shortdoc \"Generates a Presence tracker\"\n\n  @moduledoc \"\"\"\n  Generates a Presence tracker.\n\n      $ mix phx.gen.presence\n      $ mix phx.gen.presence MyPresence\n\n  The argument, which defaults to `Presence`, defines the module name of the\n  Presence tracker.\n\n  Generates a new file, `lib/my_app_web/channels/my_presence.ex`, where\n  `my_presence` is the snake-cased version of the provided module name.\n  \"\"\"\n  use Mix.Task\n\n  @doc false\n  def run([]) do\n    run([\"Presence\"])\n  end\n\n  def run([alias_name]) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.presence must be invoked from within your *_web application's root directory\"\n      )\n    end\n\n    context_app = Mix.Phoenix.context_app()\n    otp_app = Mix.Phoenix.otp_app()\n    web_prefix = Mix.Phoenix.web_path(context_app)\n    inflections = Mix.Phoenix.inflect(alias_name)\n\n    inflections =\n      Keyword.put(inflections, :module, \"#{inflections[:web_module]}.#{inflections[:scoped]}\")\n\n    binding =\n      inflections ++\n        [\n          otp_app: otp_app,\n          pubsub_server: Module.concat(Mix.Phoenix.context_base(context_app), \"PubSub\")\n        ]\n\n    files = [\n      {:eex, \"presence.ex.eex\", Path.join(web_prefix, \"channels/#{binding[:path]}.ex\")}\n    ]\n\n    Mix.Phoenix.copy_from(paths(), \"priv/templates/phx.gen.presence\", binding, files)\n\n    Mix.shell().info(\"\"\"\n\n    Add your new module to your supervision tree,\n    in lib/#{otp_app}/application.ex:\n\n        children = [\n          ...\n          #{binding[:module]}\n        ]\n\n    You're all set! See the Phoenix.Presence docs for more details:\n    https://hexdocs.pm/phoenix/Phoenix.Presence.html\n    \"\"\")\n  end\n\n  defp paths do\n    [\".\", :phoenix]\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.release.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Release do\n  @shortdoc \"Generates release files and optional Dockerfile for release-based deployments\"\n\n  @moduledoc \"\"\"\n  Generates release files and optional Dockerfile for release-based deployments.\n\n  The following release files are created:\n\n    * `lib/app_name/release.ex` - A release module containing tasks for running\n      migrations inside a release\n\n    * `rel/overlays/bin/migrate` - A migrate script for conveniently invoking\n      the release system migrations\n\n    * `rel/overlays/bin/server` - A server script for conveniently invoking\n      the release system with environment variables to start the phoenix web server\n\n  Note, the `rel/overlays` directory is copied into the release build by default when\n  running `mix release`.\n\n  To skip generating the migration-related files, use the `--no-ecto` flag. To\n  force these migration-related files to be generated, use the `--ecto` flag.\n\n  ## Docker\n\n  When the `--docker` flag is passed, the following docker files are generated:\n\n    * `Dockerfile` - The Dockerfile for use in any standard docker deployment\n\n    * `.dockerignore` - A docker ignore file with standard elixir defaults\n\n  By default, the build uses whatever base image matches your development system’s\n  active versions at generation time. To override those defaults, specify:\n\n  * `otp` — the OTP version to use\n\n  * `elixir` — the Elixir version to use\n\n  For extended release configuration, the `mix release.init` task can be used\n  in addition to this task. See the `Mix.Release` docs for more details.\n\n  If you are using third party JS package managers like `npm` or `yarn`, you will\n  need to update the generated Dockerfile with an extra step to fetch those packages.\n  This might look like this:\n\n  ```dockerfile\n  ...\n  ARG RUNNER_IMAGE=\"debian:...\"\n\n  FROM node:20 as node\n  COPY assets assets\n  RUN cd assets && npm install\n\n  FROM ${BUILDER_IMAGE} as builder\n\n  ...\n\n  COPY assets assets\n  COPY --from=node assets/node_modules assets/node_modules\n  ...\n  ```\n\n  If you are using esbuild through Node.js or other JavaScript build tools, the approach\n  above can also be modified to invoke those in the node stage, for example:\n\n  ```dockerfile\n  FROM node:20 as node\n  COPY assets assets\n  RUN cd assets && npm install && node build.js --deploy\n  ```\n\n  Note that you may need to adjust the `assets.deploy` task to not invoke Node.js again.\n  \"\"\"\n\n  use Mix.Task\n\n  require Logger\n\n  @doc false\n  def run(args) do\n    opts = parse_args(args)\n\n    if Mix.Project.umbrella?() do\n      Mix.raise(\"\"\"\n      mix phx.gen.release is not supported in umbrella applications.\n\n      Run this task in your web application instead.\n      \"\"\")\n    end\n\n    app = Mix.Phoenix.otp_app()\n    app_namespace = Mix.Phoenix.base()\n    web_namespace = app_namespace |> Mix.Phoenix.web_module() |> inspect()\n\n    binding = [\n      app_namespace: app_namespace,\n      otp_app: app,\n      assets_dir_exists?: File.dir?(\"assets\")\n    ]\n\n    Mix.Phoenix.copy_from(paths(), \"priv/templates/phx.gen.release\", binding, [\n      {:eex, \"rel/server.sh.eex\", \"rel/overlays/bin/server\"},\n      {:eex, \"rel/server.bat.eex\", \"rel/overlays/bin/server.bat\"}\n    ])\n\n    if opts.ecto do\n      Mix.Phoenix.copy_from(paths(), \"priv/templates/phx.gen.release\", binding, [\n        {:eex, \"rel/migrate.sh.eex\", \"rel/overlays/bin/migrate\"},\n        {:eex, \"rel/migrate.bat.eex\", \"rel/overlays/bin/migrate.bat\"},\n        {:eex, \"release.ex.eex\", Mix.Phoenix.context_lib_path(app, \"release.ex\")}\n      ])\n    end\n\n    if opts.docker do\n      gen_docker(binding, opts)\n    end\n\n    File.chmod!(\"rel/overlays/bin/server\", 0o755)\n    File.chmod!(\"rel/overlays/bin/server.bat\", 0o755)\n\n    if opts.ecto do\n      File.chmod!(\"rel/overlays/bin/migrate\", 0o755)\n      File.chmod!(\"rel/overlays/bin/migrate.bat\", 0o755)\n    end\n\n    Mix.shell().info(\"\"\"\n\n    Your application is ready to be deployed in a release!\n\n    See https://hexdocs.pm/mix/Mix.Tasks.Release.html for more information about Elixir releases.\n    #{if opts.docker, do: docker_instructions()}\n    Here are some useful release commands you can run in any release environment:\n\n        # To build a release\n        mix release\n\n        # To start your system with the Phoenix server running\n        _build/dev/rel/#{app}/bin/server\n    #{if opts.ecto, do: ecto_instructions(app)}\n    Once the release is running you can connect to it remotely:\n\n        _build/dev/rel/#{app}/bin/#{app} remote\n\n    To list all commands:\n\n        _build/dev/rel/#{app}/bin/#{app}\n    \"\"\")\n\n    if opts.ecto and opts.socket_db_adaptor_installed do\n      post_install_instructions(\"config/runtime.exs\", ~r/ECTO_IPV6/, \"\"\"\n      [warn] Conditional IPv6 support missing from runtime configuration.\n\n      Add the following to your config/runtime.exs:\n\n          maybe_ipv6 = if System.get_env(\"ECTO_IPV6\") in ~w(true 1), do: [:inet6], else: []\n\n          config :#{app}, #{app_namespace}.Repo,\n            ...,\n            socket_options: maybe_ipv6\n      \"\"\")\n    end\n\n    post_install_instructions(\"config/runtime.exs\", ~r/PHX_SERVER/, \"\"\"\n    [warn] Conditional server startup is missing from runtime configuration.\n\n    Add the following to the top of your config/runtime.exs:\n\n        if System.get_env(\"PHX_SERVER\") do\n          config :#{app}, #{web_namespace}.Endpoint, server: true\n        end\n    \"\"\")\n\n    post_install_instructions(\"config/runtime.exs\", ~r/PHX_HOST/, \"\"\"\n    [warn] Environment based URL export is missing from runtime configuration.\n\n    Add the following to your config/runtime.exs:\n\n        host = System.get_env(\"PHX_HOST\") || \"example.com\"\n\n        config :#{app}, #{web_namespace}.Endpoint,\n          ...,\n          url: [host: host, port: 443]\n    \"\"\")\n  end\n\n  defp parse_args(args) do\n    args\n    |> OptionParser.parse(\n      strict: [ecto: :boolean, docker: :boolean, elixir: :string, otp: :string]\n    )\n    |> elem(0)\n    |> Keyword.put_new_lazy(:ecto, &ecto_sql_installed?/0)\n    |> Keyword.put_new_lazy(:socket_db_adaptor_installed, &socket_db_adaptor_installed?/0)\n    |> Keyword.put_new(:docker, false)\n    |> Keyword.put_new(:elixir, false)\n    |> Keyword.put_new(:otp, false)\n    |> Map.new()\n  end\n\n  defp ecto_instructions(app) do\n    \"\"\"\n\n        # To run migrations\n        _build/dev/rel/#{app}/bin/migrate\n    \"\"\"\n  end\n\n  defp docker_instructions do\n    \"\"\"\n\n    Using the generated Dockerfile, your release will be bundled into\n    a Docker image, ready for deployment on platforms that support Docker.\n\n    For more information about deploying with Docker see\n    https://hexdocs.pm/phoenix/releases.html#containers\n    \"\"\"\n  end\n\n  defp paths do\n    [\".\", :phoenix]\n  end\n\n  defp post_install_instructions(path, matching, msg) do\n    case File.read(path) do\n      {:ok, content} ->\n        unless content =~ matching, do: Mix.shell().info(msg)\n\n      {:error, _} ->\n        Mix.shell().info(msg)\n    end\n  end\n\n  defp ecto_sql_installed?, do: Mix.Project.deps_paths() |> Map.has_key?(:ecto_sql)\n\n  defp socket_db_adaptor_installed? do\n    Mix.Project.deps_paths(depth: 1)\n    |> Map.take([:tds, :myxql, :postgrex])\n    |> map_size() > 0\n  end\n\n  @debian \"trixie\"\n  defp elixir_and_debian_vsn(elixir_vsn, otp_vsn) do\n    url =\n      \"https://hub.docker.com/v2/namespaces/hexpm/repositories/elixir/tags?name=#{elixir_vsn}-erlang-#{otp_vsn}-debian-#{@debian}-\"\n\n    fetch_body!(url)\n    |> Phoenix.json_library().decode!()\n    |> Map.fetch!(\"results\")\n    |> Enum.find_value(:error, fn %{\"name\" => name} ->\n      if String.ends_with?(name, \"-slim\") do\n        elixir_vsn = name |> String.split(\"-\") |> List.first()\n        %{\"vsn\" => vsn} = Regex.named_captures(~r/.*debian-#{@debian}-(?<vsn>.*)-slim/, name)\n        {:ok, elixir_vsn, vsn}\n      end\n    end)\n  end\n\n  defp gen_docker(binding, opts) do\n    wanted_elixir_vsn =\n      opts[:elixir] ||\n        case Version.parse!(System.version()) do\n          %{major: major, minor: minor, pre: [\"dev\"]} -> \"#{major}.#{minor - 1}.0\"\n          _ -> System.version()\n        end\n\n    otp_vsn = opts[:otp] || otp_vsn()\n\n    vsns =\n      case elixir_and_debian_vsn(wanted_elixir_vsn, otp_vsn) do\n        {:ok, elixir_vsn, debian_vsn} ->\n          {:ok, elixir_vsn, debian_vsn}\n\n        :error ->\n          case elixir_and_debian_vsn(\"\", otp_vsn) do\n            {:ok, elixir_vsn, debian_vsn} ->\n              Logger.warning(\n                \"Docker image for Elixir #{wanted_elixir_vsn} not found, defaulting to Elixir #{elixir_vsn}\"\n              )\n\n              {:ok, elixir_vsn, debian_vsn}\n\n            :error ->\n              :error\n          end\n      end\n\n    case vsns do\n      {:ok, elixir_vsn, debian_vsn} ->\n        binding =\n          Keyword.merge(binding,\n            debian: @debian,\n            debian_vsn: debian_vsn,\n            elixir_vsn: elixir_vsn,\n            otp_vsn: otp_vsn\n          )\n\n        Mix.Phoenix.copy_from(paths(), \"priv/templates/phx.gen.release\", binding, [\n          {:eex, \"Dockerfile.eex\", \"Dockerfile\"},\n          {:eex, \"dockerignore.eex\", \".dockerignore\"}\n        ])\n\n      :error ->\n        raise \"\"\"\n        unable to fetch supported Docker image for Elixir #{wanted_elixir_vsn} and Erlang #{otp_vsn}.\n        Please check https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=#{otp_vsn} \\\n        for a suitable Elixir version\n        \"\"\"\n    end\n  end\n\n  defp ensure_app!(app) do\n    if function_exported?(Mix, :ensure_application!, 1) do\n      apply(Mix, :ensure_application!, [app])\n    else\n      {:ok, _} = Application.ensure_all_started(app)\n    end\n  end\n\n  defp fetch_body!(url) do\n    url = String.to_charlist(url)\n    Logger.debug(\"Fetching latest image information from #{url}\")\n    ensure_app!(:inets)\n    ensure_app!(:ssl)\n\n    if proxy = System.get_env(\"HTTP_PROXY\") || System.get_env(\"http_proxy\") do\n      Logger.debug(\"Using HTTP_PROXY: #{proxy}\")\n      %{host: host, port: port} = URI.parse(proxy)\n      :httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}])\n    end\n\n    if proxy = System.get_env(\"HTTPS_PROXY\") || System.get_env(\"https_proxy\") do\n      Logger.debug(\"Using HTTPS_PROXY: #{proxy}\")\n      %{host: host, port: port} = URI.parse(proxy)\n      :httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}])\n    end\n\n    # https://security.erlef.org/secure_coding_and_deployment_hardening/inets\n    http_options = [\n      ssl: [\n        verify: :verify_peer,\n        cacerts: :public_key.cacerts_get(),\n        depth: 3,\n        customize_hostname_check: [\n          match_fun: :public_key.pkix_verify_hostname_match_fun(:https)\n        ],\n        versions: protocol_versions()\n      ]\n    ]\n\n    http_client =\n      Process.get({__MODULE__, :http_client}, fn url ->\n        case :httpc.request(:get, {url, []}, http_options, body_format: :binary) do\n          {:ok, {{_, 200, _}, _headers, body}} -> body\n          other -> raise \"couldn't fetch #{url}: #{inspect(other)}\"\n        end\n      end)\n\n    http_client.(url)\n  end\n\n  defp protocol_versions do\n    otp_major_vsn = :erlang.system_info(:otp_release) |> List.to_integer()\n    if otp_major_vsn < 25, do: [:\"tlsv1.2\"], else: [:\"tlsv1.2\", :\"tlsv1.3\"]\n  end\n\n  def otp_vsn do\n    major = to_string(:erlang.system_info(:otp_release))\n    path = Path.join([:code.root_dir(), \"releases\", major, \"OTP_VERSION\"])\n\n    case File.read(path) do\n      {:ok, content} ->\n        String.trim(content)\n\n      {:error, _} ->\n        IO.warn(\"unable to read OTP minor version at #{path}. Falling back to #{major}.0\")\n        \"#{major}.0\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.schema.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Schema do\n  @shortdoc \"Generates an Ecto schema and migration file\"\n\n  @moduledoc \"\"\"\n  Generates an Ecto schema and migration.\n\n      $ mix phx.gen.schema Blog.Post blog_posts title:string views:integer\n\n  The first argument is the schema module followed by its plural\n  name (used as the table name).\n\n  The generated schema above will contain:\n\n    * a schema file in `lib/my_app/blog/post.ex`, with a `blog_posts` table\n    * a migration file for the repository\n\n  The generated migration can be skipped with `--no-migration`.\n\n  ## Contexts\n\n  Your schemas can be generated and added to a separate OTP app.\n  Make sure your configuration is properly setup or manually\n  specify the context app with the `--context-app` option with\n  the CLI.\n\n  Via config:\n\n      config :marketing_web, :generators, context_app: :marketing\n\n  Via CLI:\n\n      $ mix phx.gen.schema Blog.Post blog_posts title:string views:integer --context-app marketing\n\n  ## Attributes\n\n  The resource fields are given using `name:type` syntax\n  where type are the types supported by Ecto. Omitting\n  the type makes it default to `:string`:\n\n      $ mix phx.gen.schema Blog.Post blog_posts title views:integer\n\n  The following types are supported:\n\n  #{for attr <- Mix.Phoenix.Schema.valid_types(), do: \"  * `#{inspect attr}`\\n\"}\n    * `:datetime` - An alias for `:naive_datetime`\n\n  The generator also supports references, which we will properly\n  associate the given column to the primary key column of the\n  referenced table:\n\n      $ mix phx.gen.schema Blog.Post blog_posts title user_id:references:users\n\n  This will result in a migration with an `:integer` column\n  of `:user_id` and create an index.\n\n  Furthermore an array type can also be given if it is\n  supported by your database, although it requires the\n  type of the underlying array element to be given too:\n\n      $ mix phx.gen.schema Blog.Post blog_posts tags:array:string\n\n  Unique columns can be automatically generated by using:\n\n      $ mix phx.gen.schema Blog.Post blog_posts title:unique unique_int:integer:unique\n\n  Redact columns can be automatically generated by using:\n\n      $ mix phx.gen.schema Accounts.Superhero superheroes secret_identity:redact password:string:redact\n\n  Ecto.Enum fields can be generated by using:\n\n      $ mix phx.gen.schema Blog.Post blog_posts title status:enum:unpublished:published:deleted\n\n  If no data type is given, it defaults to a string.\n\n  ## table\n\n  By default, the table name for the migration and schema will be\n  the plural name provided for the resource. To customize this value,\n  a `--table` option may be provided. For example:\n\n      $ mix phx.gen.schema Blog.Post posts --table cms_posts\n\n  ## binary_id\n\n  Generated migration can use `binary_id` for schema's primary key\n  and its references with option `--binary-id`.\n\n  ## primary_key\n\n  By default, the primary key in the table is called `id`. This option\n  allows to change the name of the primary key column. For example:\n\n      $ mix phx.gen.schema Blog.post posts --primary-key post_id\n\n  ## repo\n\n  Generated migration can use `repo` to set the migration repository\n  folder with option `--repo`:\n\n      $ mix phx.gen.schema Blog.Post posts --repo MyApp.Repo.Auth\n\n  ## migration_dir\n\n  Generated migrations can be added to a specific `--migration-dir` which sets\n  the migration folder path:\n\n      $ mix phx.gen.schema Blog.Post posts --migration-dir /path/to/directory\n\n\n  ## prefix\n\n  By default migrations and schemas are generated without a prefix.\n\n  For PostgreSQL this sets the \"SCHEMA\" (typically set via `search_path`)\n  and for MySQL it sets the database for the generated migration and schema.\n  The prefix can be used to thematically organize your tables on the database level.\n\n  A prefix can be specified with the `--prefix` flags. For example:\n\n      $ mix phx.gen.schema Blog.Post posts --prefix blog\n\n  > #### Warning {: .warning}\n  >\n  > The flag does not generate migrations to create the schema / database.\n  > This needs to be done manually or in a separate migration.\n\n  ## Default options\n\n  This generator uses default options provided in the `:generators`\n  configuration of your application. These are the defaults:\n\n      config :your_app, :generators,\n        migration: true,\n        binary_id: false,\n        timestamp_type: :naive_datetime,\n        sample_binary_id: \"11111111-1111-1111-1111-111111111111\"\n\n  You can override those options per invocation by providing corresponding\n  switches, e.g. `--no-binary-id` to use normal ids despite the default\n  configuration or `--migration` to force generation of the migration.\n\n  ## UTC timestamps\n\n  By setting the `:timestamp_type` to `:utc_datetime`, the timestamps\n  will be created using the UTC timezone. This results in a `DateTime` struct\n  instead of a `NaiveDateTime`. This can also be set to `:utc_datetime_usec` for\n  microsecond precision.\n\n  \"\"\"\n  use Mix.Task\n\n  alias Mix.Phoenix.Schema\n\n  @switches [migration: :boolean, binary_id: :boolean, table: :string, web: :string,\n    context_app: :string, prefix: :string, repo: :string, migration_dir: :string,\n    primary_key: :string, scope: :string, no_scope: :boolean]\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise \"mix phx.gen.schema must be invoked from within your *_web application root directory\"\n    end\n\n    schema = build(args, [])\n    paths = Mix.Phoenix.generator_paths()\n\n    prompt_for_conflicts(schema)\n\n    binding = [\n      schema: schema,\n      primary_key: schema.opts[:primary_key] || :id,\n      scope: schema.scope\n    ]\n\n    schema\n    |> copy_new_files(paths, binding)\n    |> print_shell_instructions()\n  end\n\n  defp prompt_for_conflicts(schema) do\n    schema\n    |> files_to_be_generated()\n    |> Mix.Phoenix.prompt_for_conflicts()\n  end\n\n  @doc false\n  def build(args, parent_opts, help \\\\ __MODULE__) do\n    {schema_opts, parsed, _} = OptionParser.parse(args, switches: @switches)\n    [schema_name, plural | attrs] = validate_args!(parsed, help)\n\n    opts =\n      parent_opts\n      |> Keyword.merge(schema_opts)\n      |> put_context_app(schema_opts[:context_app])\n      |> maybe_update_repo_module()\n\n    Schema.new(schema_name, plural, attrs, opts)\n  end\n\n  defp maybe_update_repo_module(opts) do\n    if is_nil(opts[:repo]) do\n      opts\n    else\n      Keyword.update!(opts, :repo, &Module.concat([&1]))\n    end\n  end\n\n  defp put_context_app(opts, nil), do: opts\n  defp put_context_app(opts, string) do\n    Keyword.put(opts, :context_app, String.to_atom(string))\n  end\n\n  @doc false\n  def files_to_be_generated(%Schema{} = schema) do\n    [{:eex, \"schema.ex.eex\", schema.file}]\n  end\n\n  @doc false\n  def copy_new_files(%Schema{context_app: ctx_app, repo: repo, opts: opts} = schema, paths, binding) do\n    files = files_to_be_generated(schema)\n    Mix.Phoenix.copy_from(paths, \"priv/templates/phx.gen.schema\", binding, files)\n\n    if schema.migration? do\n      migration_dir =\n        cond do\n          migration_dir = opts[:migration_dir] ->\n            migration_dir\n\n          opts[:repo] ->\n            repo_name = repo |> Module.split() |> List.last() |> Macro.underscore()\n            Mix.Phoenix.context_app_path(ctx_app, \"priv/#{repo_name}/migrations/\")\n\n          true ->\n            Mix.Phoenix.context_app_path(ctx_app, \"priv/repo/migrations/\")\n        end\n\n      migration_path = Path.join(migration_dir, \"#{timestamp()}_create_#{schema.table}.exs\")\n\n      Mix.Phoenix.copy_from paths, \"priv/templates/phx.gen.schema\", binding, [\n        {:eex, \"migration.exs.eex\", migration_path},\n      ]\n    end\n\n    schema\n  end\n\n  @doc false\n  def print_shell_instructions(%Schema{} = schema) do\n    if schema.migration? do\n      Mix.shell().info \"\"\"\n\n      Remember to update your repository by running migrations:\n\n          $ mix ecto.migrate\n      \"\"\"\n    end\n  end\n\n  @doc false\n  def validate_args!([schema, plural | _] = args, help) do\n    cond do\n      not Schema.valid?(schema) ->\n        help.raise_with_help \"Expected the schema argument, #{inspect schema}, to be a valid module name\"\n      String.contains?(plural, \":\") or plural != Phoenix.Naming.underscore(plural) ->\n        help.raise_with_help \"Expected the plural argument, #{inspect plural}, to be all lowercase using snake_case convention\"\n      true ->\n        args\n    end\n  end\n  def validate_args!(_, help) do\n    help.raise_with_help \"Invalid arguments\"\n  end\n\n  @doc false\n  @spec raise_with_help(String.t) :: no_return()\n  def raise_with_help(msg) do\n    Mix.raise \"\"\"\n    #{msg}\n\n    mix phx.gen.schema expects both a module name and\n    the plural of the generated resource followed by\n    any number of attributes:\n\n        mix phx.gen.schema Blog.Post blog_posts title:string\n    \"\"\"\n  end\n\n  defp timestamp do\n    {{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()\n    \"#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}\"\n  end\n  defp pad(i) when i < 10, do: << ?0, ?0 + i >>\n  defp pad(i), do: to_string(i)\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.secret.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Secret do\n  @shortdoc \"Generates a secret\"\n\n  @moduledoc \"\"\"\n  Generates a secret and prints it to the terminal.\n\n      $ mix phx.gen.secret [length]\n\n  By default, mix phx.gen.secret generates a key 64 characters long.\n\n  The minimum value for `length` is 32.\n  \"\"\"\n  use Mix.Task\n\n  @doc false\n  def run([]),    do: run([\"64\"])\n  def run([int]), do: int |> parse!() |> random_string() |> Mix.shell().info()\n  def run([_|_]), do: invalid_args!()\n\n  defp parse!(int) do\n    case Integer.parse(int) do\n      {int, \"\"} -> int\n      _ -> invalid_args!()\n    end\n  end\n\n  defp random_string(length) when length > 31 do\n    :crypto.strong_rand_bytes(length) |> Base.encode64(padding: false) |> binary_part(0, length)\n  end\n  defp random_string(_), do: Mix.raise \"The secret should be at least 32 characters long\"\n\n  @spec invalid_args!() :: no_return()\n  defp invalid_args! do\n    Mix.raise \"mix phx.gen.secret expects a length as integer or no argument at all\"\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.gen.socket.ex",
    "content": "defmodule Mix.Tasks.Phx.Gen.Socket do\n  @shortdoc \"Generates a Phoenix socket handler\"\n\n  @moduledoc \"\"\"\n  Generates a Phoenix socket handler.\n\n      $ mix phx.gen.socket User\n\n  Accepts the module name for the socket.\n\n  The generated files will contain:\n\n  For a regular application:\n\n    * a client in `assets/js`\n    * a socket in `lib/my_app_web/channels`\n\n  For an umbrella application:\n\n    * a client in `apps/my_app_web/assets/js`\n    * a socket in `apps/my_app_web/lib/my_app_web/channels`\n\n  You can then generate channels with `mix phx.gen.channel`.\n  \"\"\"\n  use Mix.Task\n\n  @doc false\n  def run(args) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\n        \"mix phx.gen.socket must be invoked from within your *_web application's root directory\"\n      )\n    end\n\n    [socket_name, pre_existing_channel] = validate_args!(args)\n\n    context_app = Mix.Phoenix.context_app()\n    web_prefix = Mix.Phoenix.web_path(context_app)\n    binding = Mix.Phoenix.inflect(socket_name)\n\n    existing_channel =\n      if pre_existing_channel do\n        channel_binding = Mix.Phoenix.inflect(pre_existing_channel)\n\n        Keyword.put(\n          channel_binding,\n          :module,\n          \"#{channel_binding[:web_module]}.#{channel_binding[:scoped]}\"\n        )\n      end\n\n    binding =\n      binding\n      |> Keyword.put(:module, \"#{binding[:web_module]}.#{binding[:scoped]}\")\n      |> Keyword.put(:endpoint_module, Module.concat([binding[:web_module], Endpoint]))\n      |> Keyword.put(:web_prefix, web_prefix)\n      |> Keyword.put(:existing_channel, existing_channel)\n\n    Mix.Phoenix.check_module_name_availability!(binding[:module] <> \"Socket\")\n\n    Mix.Phoenix.copy_from(paths(), \"priv/templates/phx.gen.socket\", binding, [\n      {:eex, \"socket.ex.eex\", Path.join(web_prefix, \"channels/#{binding[:path]}_socket.ex\")},\n      {:eex, \"socket.js.eex\", \"assets/js/#{binding[:path]}_socket.js\"}\n    ])\n\n    Mix.shell().info(\"\"\"\n\n    Add the socket handler to your `#{Mix.Phoenix.web_path(context_app, \"endpoint.ex\")}`, for example:\n\n        socket \"/socket\", #{binding[:module]}Socket,\n          websocket: true,\n          longpoll: false\n\n    For the front-end integration, you need to import the `#{binding[:path]}_socket.js`\n    in your `assets/js/app.js` file:\n\n        import \"./#{binding[:path]}_socket.js\"\n    \"\"\")\n  end\n\n  @spec raise_with_help() :: no_return()\n  defp raise_with_help do\n    Mix.raise(\"\"\"\n    mix phx.gen.socket expects the module name:\n\n        mix phx.gen.socket User\n\n    \"\"\")\n  end\n\n  defp validate_args!([name, \"--from-channel\", pre_existing_channel]) do\n    unless valid_name?(name) and valid_name?(pre_existing_channel) do\n      raise_with_help()\n    end\n\n    [name, pre_existing_channel]\n  end\n\n  defp validate_args!([name]) do\n    unless valid_name?(name) do\n      raise_with_help()\n    end\n\n    [name, nil]\n  end\n\n  defp validate_args!(_), do: raise_with_help()\n\n  defp valid_name?(name) do\n    name =~ ~r/^[A-Z]\\w*(\\.[A-Z]\\w*)*$/\n  end\n\n  defp paths do\n    [\".\", :phoenix]\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.routes.ex",
    "content": "defmodule Mix.Tasks.Phx.Routes do\n  use Mix.Task\n  alias Phoenix.Router.ConsoleFormatter\n\n  @shortdoc \"Prints all routes\"\n\n  @moduledoc \"\"\"\n  Prints all routes for the default or a given router.\n  Can also locate the controller function behind a specified url.\n\n      $ mix phx.routes [ROUTER] [--info URL]\n\n  The default router is inflected from the application\n  name unless a configuration named `:namespace`\n  is set inside your application configuration. For example,\n  the configuration:\n\n      config :my_app,\n        namespace: My.App\n\n  will exhibit the routes for `My.App.Router` when this\n  task is invoked without arguments.\n\n  Umbrella projects do not have a default router and\n  therefore always expect a router to be given. An\n  alias can be added to mix.exs to automate this:\n\n      defp aliases do\n        [\n          \"phx.routes\": \"phx.routes MyAppWeb.Router\",\n          # aliases...\n        ]\n\n  ## Options\n\n    * `--info` - locate the controller function definition called by the given url\n    * `--method` - what HTTP method to use with the given url, only works when used with `--info` and defaults to `get`\n\n  ## Examples\n\n  Print all routes for the default router:\n\n      $ mix phx.routes\n\n  Print all routes for the given router:\n\n      $ mix phx.routes MyApp.AnotherRouter\n\n  Print information about the controller function called by a specified url:\n\n      $ mix phx.routes --info http://0.0.0.0:4000/home\n        Module: RouteInfoTestWeb.PageController\n        Function: :index\n        /home/my_app/controllers/page_controller.ex:4\n\n  Print information about the controller function called by a specified url and HTTP method:\n\n      $ mix phx.routes --info http://0.0.0.0:4000/users --method post\n        Module: RouteInfoTestWeb.UserController\n        Function: :create\n        /home/my_app/controllers/user_controller.ex:24\n  \"\"\"\n\n  @doc false\n  def run(args, base \\\\ Mix.Phoenix.base()) do\n    if \"--no-compile\" not in args do\n      Mix.Task.run(\"compile\")\n    end\n\n    Mix.Task.reenable(\"phx.routes\")\n\n    {opts, args, _} =\n      OptionParser.parse(args, switches: [endpoint: :string, router: :string, info: :string])\n\n    {router_mod, endpoint_mod} =\n      case args do\n        [passed_router] -> {router(passed_router, base), opts[:endpoint]}\n        [] -> {router(opts[:router], base), endpoint(opts[:endpoint], base)}\n      end\n\n    case Keyword.fetch(opts, :info) do\n      {:ok, url} ->\n        get_url_info(url, {router_mod, opts})\n\n      :error ->\n        router_mod\n        |> ConsoleFormatter.format(endpoint_mod)\n        |> Mix.shell().info()\n    end\n  end\n\n  def get_url_info(url, {router_mod, opts}) do\n    %{path: path} = URI.parse(url)\n\n    method = opts |> Keyword.get(:method, \"get\") |> String.upcase()\n    meta = Phoenix.Router.route_info(router_mod, method, path, \"\")\n    %{plug: plug, plug_opts: plug_opts} = meta\n\n    {module, func_name} =\n      case meta[:mfa] do\n        {mod, fun, _} -> {mod, fun}\n        _ -> {plug, plug_opts}\n      end\n\n    Mix.shell().info(\"Module: #{inspect(module)}\")\n    if func_name, do: Mix.shell().info(\"Function: #{inspect(func_name)}\")\n\n    file_path = get_file_path(module)\n\n    if line = get_line_number(module, func_name) do\n      Mix.shell().info(\"#{file_path}:#{line}\")\n    else\n      Mix.shell().info(\"#{file_path}\")\n    end\n  end\n\n  defp endpoint(nil, base) do\n    loaded(web_mod(base, \"Endpoint\"))\n  end\n\n  defp endpoint(module, _base) do\n    loaded(Module.concat([module]))\n  end\n\n  defp router(nil, base) do\n    if Mix.Project.umbrella?() do\n      Mix.raise(\"\"\"\n      umbrella applications require an explicit router to be given to phx.routes, for example:\n\n          $ mix phx.routes MyAppWeb.Router\n\n      An alias can be added to mix.exs aliases to automate this:\n\n          \"phx.routes\": \"phx.routes MyAppWeb.Router\"\n\n      \"\"\")\n    end\n\n    web_router = web_mod(base, \"Router\")\n    old_router = app_mod(base, \"Router\")\n\n    loaded(web_router) || loaded(old_router) ||\n      Mix.raise(\"\"\"\n      no router found at #{inspect(web_router)} or #{inspect(old_router)}.\n      An explicit router module may be given to phx.routes, for example:\n\n          $ mix phx.routes MyAppWeb.Router\n\n      An alias can be added to mix.exs aliases to automate this:\n\n          \"phx.routes\": \"phx.routes MyAppWeb.Router\"\n\n      \"\"\")\n  end\n\n  defp router(router_name, _base) do\n    arg_router = Module.concat([router_name])\n    loaded(arg_router) || Mix.raise(\"the provided router, #{inspect(arg_router)}, does not exist\")\n  end\n\n  defp loaded(module) do\n    if Code.ensure_loaded?(module), do: module\n  end\n\n  defp app_mod(base, name), do: Module.concat([base, name])\n\n  defp web_mod(base, name), do: Module.concat([\"#{base}Web\", name])\n\n  defp get_file_path(module_name) do\n    [compile_infos] = Keyword.get_values(module_name.module_info(), :compile)\n    [source] = Keyword.get_values(compile_infos, :source)\n    Path.relative_to_cwd(source)\n  end\n\n  defp get_line_number(_, nil), do: nil\n\n  defp get_line_number(module, function_name) do\n    {_, _, _, _, _, _, functions_list} = Code.fetch_docs(module)\n\n    function_infos =\n      functions_list\n      |> Enum.find(fn {{type, name, _}, _, _, _, _} ->\n        type == :function and name == function_name\n      end)\n\n    case function_infos do\n      {_, anno, _, _, _} -> :erl_anno.line(anno)\n      nil -> nil\n    end\n  end\nend\n"
  },
  {
    "path": "lib/mix/tasks/phx.server.ex",
    "content": "defmodule Mix.Tasks.Phx.Server do\n  use Mix.Task\n\n  @shortdoc \"Starts applications and their servers\"\n\n  @moduledoc \"\"\"\n  Starts the application by configuring all endpoints servers to run.\n\n  Note: to start the endpoint without using this mix task you must set\n  `server: true` in your `Phoenix.Endpoint` configuration.\n\n  ## Command line options\n\n    * `--open` - open browser window for each started endpoint\n\n  Furthermore, this task accepts the same command-line options as\n  `mix run`.\n\n  For example, to run `phx.server` without recompiling:\n\n      $ mix phx.server --no-compile\n\n  The `--no-halt` flag is automatically added.\n\n  Note that the `--no-deps-check` flag cannot be used this way,\n  because Mix needs to check dependencies to find `phx.server`.\n\n  To run `phx.server` without checking dependencies, you can run:\n\n      $ mix do deps.loadpaths --no-deps-check, phx.server\n  \"\"\"\n\n  @impl true\n  def run(args) do\n    Application.put_env(:phoenix, :serve_endpoints, true, persistent: true)\n    Mix.Tasks.Run.run(run_args() ++ open_args(args))\n  end\n\n  defp iex_running? do\n    Code.ensure_loaded?(IEx) and IEx.started?()\n  end\n\n  defp open_args(args) do\n    if \"--open\" in args do\n      Application.put_env(:phoenix, :browser_open, true)\n      args -- [\"--open\"]\n    else\n      args\n    end\n  end\n\n  defp run_args do\n    if iex_running?(), do: [], else: [\"--no-halt\"]\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/channel/server.ex",
    "content": "defmodule Phoenix.Channel.Server do\n  @moduledoc false\n  use GenServer, restart: :temporary\n\n  require Logger\n\n  alias Phoenix.PubSub\n  alias Phoenix.Socket\n  alias Phoenix.Socket.{Broadcast, Message, Reply, PoolSupervisor}\n\n  ## Socket API\n\n  @doc \"\"\"\n  Joins the channel in socket with authentication payload.\n  \"\"\"\n  @spec join(Socket.t(), module, Message.t(), keyword) :: {:ok, term, pid} | {:error, term}\n  def join(socket, channel, message, opts) do\n    %{topic: topic, payload: payload, ref: ref, join_ref: join_ref} = message\n\n    starter = opts[:starter] || (&PoolSupervisor.start_child/3)\n    assigns = Map.merge(socket.assigns, Keyword.get(opts, :assigns, %{}))\n\n    socket = %{\n      socket\n      | topic: topic,\n        channel: channel,\n        join_ref: join_ref || ref,\n        assigns: assigns\n    }\n\n    ref = make_ref()\n    from = {self(), ref}\n    child_spec = channel.child_spec({socket.endpoint, from})\n\n    case starter.(socket, from, child_spec) do\n      {:ok, pid} ->\n        send(pid, {Phoenix.Channel, payload, from, socket})\n        mon_ref = Process.monitor(pid)\n\n        receive do\n          {^ref, {:ok, reply}} ->\n            Process.demonitor(mon_ref, [:flush])\n            {:ok, reply, pid}\n\n          {^ref, {:error, reply}} ->\n            Process.demonitor(mon_ref, [:flush])\n            {:error, reply}\n\n          {:DOWN, ^mon_ref, _, _, reason} ->\n            Logger.error(fn -> Exception.format_exit(reason) end)\n            {:error, %{reason: \"join crashed\"}}\n        end\n\n      {:error, reason} ->\n        Logger.error(fn -> Exception.format_exit(reason) end)\n        {:error, %{reason: \"join crashed\"}}\n    end\n  end\n\n  @doc \"\"\"\n  Gets the socket from the channel.\n\n  Used by channel tests.\n  \"\"\"\n  @spec socket(pid) :: Socket.t()\n  def socket(pid) do\n    GenServer.call(pid, :socket)\n  end\n\n  @doc \"\"\"\n  Emulates the socket being closed.\n\n  Used by channel tests.\n  \"\"\"\n  @spec close(pid, timeout) :: :ok\n  def close(pid, timeout) do\n    GenServer.cast(pid, :close)\n    ref = Process.monitor(pid)\n\n    receive do\n      {:DOWN, ^ref, _, _, _} -> :ok\n    after\n      timeout ->\n        Process.exit(pid, :kill)\n        receive do: ({:DOWN, ^ref, _, _, _} -> :ok)\n    end\n  end\n\n  ## Channel API\n\n  @doc \"\"\"\n  Hook invoked by Phoenix.PubSub dispatch.\n  \"\"\"\n  def dispatch(subscribers, from, %Broadcast{event: event} = msg) do\n    Enum.reduce(subscribers, %{}, fn\n      {pid, _}, cache when pid == from ->\n        cache\n\n      {pid, {:fastlane, fastlane_pid, serializer, event_intercepts}}, cache ->\n        if event in event_intercepts do\n          send(pid, msg)\n          cache\n        else\n          case cache do\n            %{^serializer => encoded_msg} ->\n              send(fastlane_pid, encoded_msg)\n              cache\n\n            %{} ->\n              encoded_msg = serializer.fastlane!(msg)\n              send(fastlane_pid, encoded_msg)\n              Map.put(cache, serializer, encoded_msg)\n          end\n        end\n\n      {pid, _}, cache ->\n        send(pid, msg)\n        cache\n    end)\n\n    :ok\n  end\n\n  def dispatch(entries, :none, message) do\n    for {pid, _} <- entries do\n      send(pid, message)\n    end\n\n    :ok\n  end\n\n  def dispatch(entries, from, message) do\n    for {pid, _} <- entries, pid != from do\n      send(pid, message)\n    end\n\n    :ok\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `topic`, `event` and `payload`.\n\n  The message is encoded as `Phoenix.Socket.Broadcast`.\n  \"\"\"\n  def broadcast(pubsub_server, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.broadcast(pubsub_server, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `topic`, `event` and `payload`.\n\n  Raises in case of crashes.\n  \"\"\"\n  def broadcast!(pubsub_server, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.broadcast!(pubsub_server, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `from`, `topic`, `event` and `payload`.\n\n  The message is encoded as `Phoenix.Socket.Broadcast`.\n  \"\"\"\n  def broadcast_from(pubsub_server, from, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.broadcast_from(pubsub_server, from, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `from`, `topic`, `event` and `payload`.\n\n  Raises in case of crashes.\n  \"\"\"\n  def broadcast_from!(pubsub_server, from, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.broadcast_from!(pubsub_server, from, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `topic`, `event` and `payload`.\n\n  The message is encoded as `Phoenix.Socket.Broadcast`.\n  \"\"\"\n  def local_broadcast(pubsub_server, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.local_broadcast(pubsub_server, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Broadcasts on the given pubsub server with the given\n  `from`, `topic`, `event` and `payload`.\n\n  The message is encoded as `Phoenix.Socket.Broadcast`.\n  \"\"\"\n  def local_broadcast_from(pubsub_server, from, topic, event, payload)\n      when is_binary(topic) and is_binary(event) do\n    broadcast = %Broadcast{\n      topic: topic,\n      event: event,\n      payload: payload\n    }\n\n    PubSub.local_broadcast_from(pubsub_server, from, topic, broadcast, __MODULE__)\n  end\n\n  @doc \"\"\"\n  Pushes a message with the given topic, event and payload\n  to the given process.\n\n  Payloads are serialized before sending with the configured serializer.\n  \"\"\"\n  def push(pid, join_ref, topic, event, payload, serializer)\n      when is_binary(topic) and is_binary(event) do\n    message = %Message{join_ref: join_ref, topic: topic, event: event, payload: payload}\n    send(pid, serializer.encode!(message))\n    :ok\n  end\n\n  @doc \"\"\"\n  Replies to a given ref to the transport process.\n\n  Payloads are serialized before sending with the configured serializer.\n  \"\"\"\n  def reply(pid, join_ref, ref, topic, {status, payload}, serializer)\n      when is_binary(topic) do\n    reply = %Reply{topic: topic, join_ref: join_ref, ref: ref, status: status, payload: payload}\n    send(pid, serializer.encode!(reply))\n    :ok\n  end\n\n  ## Callbacks\n\n  @doc false\n  def init({_endpoint, {pid, _}}) do\n    {:ok, Process.monitor(pid)}\n  end\n\n  @doc false\n  def handle_call(:socket, _from, socket) do\n    {:reply, socket, socket}\n  end\n\n  @doc false\n  def handle_call(msg, from, socket) do\n    msg\n    |> socket.channel.handle_call(from, socket)\n    |> handle_result(:handle_call)\n  end\n\n  @doc false\n  def handle_cast(:close, socket) do\n    {:stop, {:shutdown, :closed}, socket}\n  end\n\n  @doc false\n  def handle_cast(msg, socket) do\n    msg\n    |> socket.channel.handle_cast(socket)\n    |> handle_result(:handle_cast)\n  end\n\n  @doc false\n  def handle_info({Phoenix.Channel, auth_payload, {pid, _} = from, socket}, ref) do\n    Process.demonitor(ref)\n    %{channel: channel, topic: topic, private: private} = socket\n    Process.put(:\"$initial_call\", {channel, :join, 3})\n    Process.put(:\"$callers\", [pid])\n\n    # TODO: replace with Process.put_label/2 when we require Elixir 1.17\n    Process.put(:\"$process_label\", {Phoenix.Channel, channel, topic})\n\n    socket = %{\n      socket\n      | channel_pid: self(),\n        private: Map.merge(channel.__socket__(:private), private)\n    }\n\n    start = System.monotonic_time()\n    {reply, state} = channel_join(channel, topic, auth_payload, socket)\n    duration = System.monotonic_time() - start\n    metadata = %{params: auth_payload, socket: socket, result: elem(reply, 0)}\n    :telemetry.execute([:phoenix, :channel_joined], %{duration: duration}, metadata)\n    GenServer.reply(from, reply)\n    state\n  end\n\n  def handle_info(%Message{topic: topic, event: \"phx_leave\", ref: ref}, %{topic: topic} = socket) do\n    handle_in({:stop, {:shutdown, :left}, :ok, put_in(socket.ref, ref)})\n  end\n\n  def handle_info(\n        %Message{topic: topic, event: event, payload: payload, ref: ref},\n        %{topic: topic} = socket\n      ) do\n    start = System.monotonic_time()\n    result = socket.channel.handle_in(event, payload, put_in(socket.ref, ref))\n    duration = System.monotonic_time() - start\n    metadata = %{ref: ref, event: event, params: payload, socket: socket}\n    :telemetry.execute([:phoenix, :channel_handled_in], %{duration: duration}, metadata)\n    handle_in(result)\n  end\n\n  def handle_info(\n        %Broadcast{event: \"phx_drain\"},\n        %{transport_pid: transport_pid} = socket\n      ) do\n    send(transport_pid, :socket_drain)\n    {:stop, {:shutdown, :draining}, socket}\n  end\n\n  def handle_info(\n        %Broadcast{topic: topic, event: event, payload: payload},\n        %Socket{topic: topic} = socket\n      ) do\n    event\n    |> socket.channel.handle_out(payload, socket)\n    |> handle_result(:handle_out)\n  end\n\n  def handle_info({:DOWN, ref, _, _, reason}, ref) do\n    {:stop, reason, ref}\n  end\n\n  def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do\n    reason = if reason == :normal, do: {:shutdown, :closed}, else: reason\n    {:stop, reason, socket}\n  end\n\n  def handle_info(msg, %{channel: channel} = socket) do\n    if function_exported?(channel, :handle_info, 2) do\n      msg\n      |> socket.channel.handle_info(socket)\n      |> handle_result(:handle_info)\n    else\n      warn_unexpected_msg(:handle_info, 2, msg, channel)\n      {:noreply, socket}\n    end\n  end\n\n  @doc false\n  def code_change(old, %{channel: channel} = socket, extra) do\n    if function_exported?(channel, :code_change, 3) do\n      channel.code_change(old, socket, extra)\n    else\n      {:ok, socket}\n    end\n  end\n\n  @doc false\n  def terminate(reason, %{channel: channel} = socket) do\n    if function_exported?(channel, :terminate, 2) do\n      channel.terminate(reason, socket)\n    else\n      :ok\n    end\n  end\n\n  def terminate(_reason, _socket) do\n    :ok\n  end\n\n  ## Joins\n\n  defp channel_join(channel, topic, auth_payload, socket) do\n    case channel.join(topic, auth_payload, socket) do\n      {:ok, socket} ->\n        {{:ok, %{}}, init_join(socket, channel, topic)}\n\n      {:ok, reply, socket} ->\n        {{:ok, reply}, init_join(socket, channel, topic)}\n\n      {:error, reply} ->\n        {{:error, reply}, {:stop, :shutdown, socket}}\n\n      other ->\n        raise \"\"\"\n        channel #{inspect(socket.channel)}.join/3 is expected to return one of:\n\n            {:ok, Socket.t} |\n            {:ok, reply :: map, Socket.t} |\n            {:error, reply :: map}\n\n        got #{inspect(other)}\n        \"\"\"\n    end\n  end\n\n  defp init_join(socket, channel, topic) do\n    %{transport_pid: transport_pid, serializer: serializer, pubsub_server: pubsub_server} = socket\n\n    unless pubsub_server do\n      raise \"\"\"\n      The :pubsub_server was not configured for endpoint #{inspect(socket.endpoint)}.\n      Make sure to start a PubSub process in your application supervision tree:\n\n          {Phoenix.PubSub, [name: YOURAPP.PubSub, adapter: Phoenix.PubSub.PG2]}\n\n      And then add it to your endpoint config:\n\n          config :YOURAPP, YOURAPPWeb.Endpoint,\n            # ...\n            pubsub_server: YOURAPP.PubSub\n      \"\"\"\n    end\n\n    Process.monitor(transport_pid)\n    fastlane = {:fastlane, transport_pid, serializer, channel.__intercepts__()}\n    PubSub.subscribe(pubsub_server, topic, metadata: fastlane)\n\n    {:noreply, %{socket | joined: true}}\n  end\n\n  ## Handle results\n\n  defp handle_result({:stop, reason, socket}, _callback) do\n    case reason do\n      :normal -> send_socket_close(socket, reason)\n      :shutdown -> send_socket_close(socket, reason)\n      {:shutdown, _} -> send_socket_close(socket, reason)\n      _ -> :noop\n    end\n\n    {:stop, reason, socket}\n  end\n\n  defp handle_result({:reply, resp, socket}, :handle_call) do\n    {:reply, resp, socket}\n  end\n\n  defp handle_result({:noreply, socket}, callback)\n       when callback in [:handle_call, :handle_cast] do\n    {:noreply, socket}\n  end\n\n  defp handle_result({:noreply, socket}, _callback) do\n    {:noreply, put_in(socket.ref, nil)}\n  end\n\n  defp handle_result({:noreply, socket, timeout_or_hibernate}, _callback) do\n    {:noreply, put_in(socket.ref, nil), timeout_or_hibernate}\n  end\n\n  defp handle_result(result, :handle_in) do\n    raise \"\"\"\n    Expected handle_in/3 to return one of:\n\n        {:noreply, Socket.t} |\n        {:noreply, Socket.t, timeout | :hibernate} |\n        {:reply, {status :: atom, response :: map}, Socket.t} |\n        {:reply, status :: atom, Socket.t} |\n        {:stop, reason :: term, Socket.t} |\n        {:stop, reason :: term, {status :: atom, response :: map}, Socket.t} |\n        {:stop, reason :: term, status :: atom, Socket.t}\n\n    got #{inspect(result)}\n    \"\"\"\n  end\n\n  defp handle_result(result, callback) do\n    raise \"\"\"\n    Expected #{callback} to return one of:\n\n        {:noreply, Socket.t} |\n        {:noreply, Socket.t, timeout | :hibernate} |\n        {:stop, reason :: term, Socket.t} |\n\n    got #{inspect(result)}\n    \"\"\"\n  end\n\n  defp send_socket_close(%{transport_pid: transport_pid}, reason) do\n    send(transport_pid, {:socket_close, self(), reason})\n  end\n\n  ## Handle in/replies\n\n  defp handle_in({:reply, reply, %Socket{} = socket}) do\n    handle_reply(socket, reply)\n    {:noreply, put_in(socket.ref, nil)}\n  end\n\n  defp handle_in({:stop, reason, reply, socket}) do\n    handle_reply(socket, reply)\n    handle_result({:stop, reason, socket}, :handle_in)\n  end\n\n  defp handle_in(other) do\n    handle_result(other, :handle_in)\n  end\n\n  defp handle_reply(socket, {status, payload}) when is_atom(status) do\n    reply(\n      socket.transport_pid,\n      socket.join_ref,\n      socket.ref,\n      socket.topic,\n      {status, payload},\n      socket.serializer\n    )\n  end\n\n  defp handle_reply(socket, status) when is_atom(status) do\n    handle_reply(socket, {status, %{}})\n  end\n\n  defp handle_reply(_socket, reply) do\n    raise \"\"\"\n    Channel replies from handle_in/3 are expected to be one of:\n\n        status :: atom\n        {status :: atom, response :: map}\n\n    for example:\n\n        {:reply, :ok, socket}\n        {:reply, {:ok, %{}}, socket}\n        {:stop, :shutdown, {:error, %{}}, socket}\n\n    got #{inspect(reply)}\n    \"\"\"\n  end\n\n  defp warn_unexpected_msg(fun, arity, msg, channel) do\n    proc =\n      case Process.info(self(), :registered_name) do\n        {_, []} -> self()\n        {_, name} -> name\n      end\n\n    :error_logger.warning_msg(\n      ~c\"~p ~p received unexpected message in #{fun}/#{arity}: ~p~n\",\n      [channel, proc, msg]\n    )\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/channel.ex",
    "content": "defmodule Phoenix.Channel do\n  @moduledoc ~S\"\"\"\n  Defines a Phoenix Channel.\n\n  Channels provide a means for bidirectional communication from clients that\n  integrate with the `Phoenix.PubSub` layer for soft-realtime functionality.\n\n  For a conceptual overview, see the [Channels guide](channels.html).\n\n  ## Topics & Callbacks\n\n  Every time you join a channel, you need to choose which particular topic you\n  want to listen to. The topic is just an identifier, but by convention it is\n  often made of two parts: `\"topic:subtopic\"`. Using the `\"topic:subtopic\"`\n  approach pairs nicely with the `Phoenix.Socket.channel/3` allowing you to\n  match on all topics starting with a given prefix by using a splat (the `*`\n  character) as the last character in the topic pattern:\n\n      channel \"room:*\", MyAppWeb.RoomChannel\n\n  Any topic coming into the router with the `\"room:\"` prefix would dispatch\n  to `MyAppWeb.RoomChannel` in the above example. Topics can also be pattern\n  matched in your channels' `join/3` callback to pluck out the scoped pattern:\n\n      # handles the special `\"lobby\"` subtopic\n      def join(\"room:lobby\", _payload, socket) do\n        {:ok, socket}\n      end\n\n      # handles any other subtopic as the room ID, for example `\"room:12\"`, `\"room:34\"`\n      def join(\"room:\" <> room_id, _payload, socket) do\n        {:ok, socket}\n      end\n\n  The first argument is the topic, the second argument is a map payload given by\n  the client, and the third argument is an instance of `Phoenix.Socket`. The\n  `socket` is provided to all channel callbacks, so check its module and\n  documentation to learn its fields and the different ways to interact with it.\n\n  ## Authorization\n\n  Clients must join a channel to send and receive PubSub events on that channel.\n  Your channels must implement a `join/3` callback that authorizes the socket\n  for the given topic. For example, you could check if the user is allowed to\n  join that particular room.\n\n  To authorize a socket in `join/3`, return `{:ok, socket}`.\n  To refuse authorization in `join/3`, return `{:error, reply}`.\n\n  ## Incoming Events\n\n  After a client has successfully joined a channel, incoming events from the\n  client are routed through the channel's `handle_in/3` callbacks. Within these\n  callbacks, you can perform any action. Incoming callbacks must return the\n  `socket` to maintain ephemeral state.\n\n  Typically you'll either forward a message to all listeners with\n  `broadcast!/3` or reply directly to a client event for request/response style\n  messaging.\n\n  General message payloads are received as maps:\n\n      def handle_in(\"new_msg\", %{\"uid\" => uid, \"body\" => body}, socket) do\n        ...\n        {:reply, :ok, socket}\n      end\n\n  Binary data payloads are passed as a `{:binary, data}` tuple:\n\n      def handle_in(\"file_chunk\", {:binary, chunk}, socket) do\n        ...\n        {:reply, :ok, socket}\n      end\n\n  ## Broadcasts\n\n  You can broadcast events from anywhere in your application to a topic by\n  the `broadcast` function in the endpoint:\n\n      MyAppWeb.Endpoint.broadcast!(\"room:13\", \"new_message\", %{content: \"hello\"})\n\n  It is also possible to broadcast directly from channels. Here's an example of\n  receiving an incoming `\"new_msg\"` event from one client, and broadcasting the\n  message to all topic subscribers for this socket.\n\n      def handle_in(\"new_msg\", %{\"uid\" => uid, \"body\" => body}, socket) do\n        broadcast!(socket, \"new_msg\", %{uid: uid, body: body})\n        {:noreply, socket}\n      end\n\n  ## Replies\n\n  Replies are useful for acknowledging a client's message or responding with\n  the results of an operation. A reply is sent only to the client connected to\n  the current channel process. Behind the scenes, they include the client\n  message `ref`, which allows the client to correlate the reply it receives\n  with the message it sent.\n\n  For example, imagine creating a resource and replying with the created record:\n\n      def handle_in(\"create:post\", attrs, socket) do\n        changeset = Post.changeset(%Post{}, attrs)\n\n        if changeset.valid? do\n          post = Repo.insert!(changeset)\n          response = MyAppWeb.PostView.render(\"show.json\", %{post: post})\n          {:reply, {:ok, response}, socket}\n        else\n          response = MyAppWeb.ChangesetView.render(\"errors.json\", %{changeset: changeset})\n          {:reply, {:error, response}, socket}\n        end\n      end\n\n  Or you may just want to confirm that the operation succeeded:\n\n      def handle_in(\"create:post\", attrs, socket) do\n        changeset = Post.changeset(%Post{}, attrs)\n\n        if changeset.valid? do\n          Repo.insert!(changeset)\n          {:reply, :ok, socket}\n        else\n          {:reply, :error, socket}\n        end\n      end\n\n  Binary data is also supported with replies via a `{:binary, data}` tuple:\n\n      {:reply, {:ok, {:binary, bin}}, socket}\n\n  If you don't want to send a reply to the client, you can return:\n\n      {:noreply, socket}\n\n  One situation when you might do this is if you need to reply later; see\n  `reply/2`.\n\n  ## Pushes\n\n  Calling `push/3` allows you to send a message to the client which is not a\n  reply to a specific client message. Because it is not a reply, a pushed\n  message does not contain a client message `ref`; there is no prior client\n  message to relate it to.\n\n  Possible use cases include notifying a client that:\n  - You've auto-saved the user's document\n  - The user's game is ending soon\n  - The IoT device's settings should be updated\n\n  For example, you could `push/3` a message to the client in `handle_info/3`\n  after receiving a `PubSub` message relevant to them.\n\n      alias Phoenix.Socket.Broadcast\n      def handle_info(%Broadcast{topic: _, event: event, payload: payload}, socket) do\n        push(socket, event, payload)\n        {:noreply, socket}\n      end\n\n  Push data can be given in the form of a map or a tagged `{:binary, data}`\n  tuple:\n\n      # client asks for their current rank. reply contains it, and client\n      # is also pushed a leader board and a badge image\n      def handle_in(\"current_rank\", _, socket) do\n        push(socket, \"leaders\", %{leaders: Game.get_leaders(socket.assigns.game_id)})\n        push(socket, \"badge\", {:binary, File.read!(socket.assigns.badge_path)})\n        {:reply, %{val: Game.get_rank(socket.assigns[:user])}, socket}\n      end\n\n  Note that in this example, `push/3` is called from `handle_in/3`; in this way\n  you can essentially reply N times to a single message from the client. See\n  `reply/2` for why this may be desirable.\n\n  ## Intercepting Outgoing Events\n\n  When an event is broadcasted with `broadcast/3`, each channel subscriber can\n  choose to intercept the event and have their `handle_out/3` callback triggered.\n  This allows the event's payload to be customized on a socket by socket basis\n  to append extra information, or conditionally filter the message from being\n  delivered. If the event is not intercepted with `Phoenix.Channel.intercept/1`,\n  then the message is pushed directly to the client:\n\n      intercept [\"new_msg\", \"user_joined\"]\n\n      # for every socket subscribing to this topic, append an `is_editable`\n      # value for client metadata.\n      def handle_out(\"new_msg\", msg, socket) do\n        push(socket, \"new_msg\", Map.merge(msg,\n          %{is_editable: User.can_edit_message?(socket.assigns[:user], msg)}\n        ))\n        {:noreply, socket}\n      end\n\n      # do not send broadcasted `\"user_joined\"` events if this socket's user\n      # is ignoring the user who joined.\n      def handle_out(\"user_joined\", msg, socket) do\n        unless User.ignoring?(socket.assigns[:user], msg.user_id) do\n          push(socket, \"user_joined\", msg)\n        end\n        {:noreply, socket}\n      end\n\n  ## Terminate\n\n  On termination, the channel callback `terminate/2` will be invoked with\n  the error reason and the socket.\n\n  If we are terminating because the client left, the reason will be\n  `{:shutdown, :left}`. Similarly, if we are terminating because the\n  client connection was closed, the reason will be `{:shutdown, :closed}`.\n\n  If any of the callbacks return a `:stop` tuple, it will also\n  trigger terminate with the reason given in the tuple.\n\n  `terminate/2`, however, won't be invoked in case of errors nor in\n  case of exits. This is the same behaviour as you find in Elixir\n  abstractions like `GenServer` and others. Similar to `GenServer`,\n  it would also be possible to `:trap_exit` to guarantee that `terminate/2`\n  is invoked. This practice is not encouraged though.\n\n  Generally speaking, if you want to clean something up, it is better to\n  monitor your channel process and do the clean up from another process.\n  All channel callbacks, including `join/3`, are called from within the\n  channel process. Therefore, `self()` in any of them returns the PID to\n  be monitored.\n\n  ## Exit reasons when stopping a channel\n\n  When the channel callbacks return a `:stop` tuple, such as:\n\n      {:stop, :shutdown, socket}\n      {:stop, {:error, :enoent}, socket}\n\n  the second argument is the exit reason, which follows the same behaviour as\n  standard `GenServer` exits.\n\n  You have three options to choose from when shutting down a channel:\n\n    * `:normal` - in such cases, the exit won't be logged and linked processes\n      do not exit\n\n    * `:shutdown` or `{:shutdown, term}` - in such cases, the exit won't be\n      logged and linked processes exit with the same reason unless they're\n      trapping exits\n\n    * any other term - in such cases, the exit will be logged and linked\n      processes exit with the same reason unless they're trapping exits\n\n  ## Subscribing to external topics\n\n  Sometimes you may need to programmatically subscribe a socket to external\n  topics in addition to the internal `socket.topic`. For example,\n  imagine you have a bidding system where a remote client dynamically sets\n  preferences on products they want to receive bidding notifications on.\n  Instead of requiring a unique channel process and topic per\n  preference, a more efficient and simple approach would be to subscribe a\n  single channel to relevant notifications via your endpoint. For example:\n\n      defmodule MyAppWeb.Endpoint.NotificationChannel do\n        use Phoenix.Channel\n\n        def join(\"notification:\" <> user_id, %{\"ids\" => ids}, socket) do\n          topics = for product_id <- ids, do: \"product:#{product_id}\"\n\n          {:ok, socket\n                |> assign(:topics, [])\n                |> put_new_topics(topics)}\n        end\n\n        def handle_in(\"watch\", %{\"product_id\" => id}, socket) do\n          {:reply, :ok, put_new_topics(socket, [\"product:#{id}\"])}\n        end\n\n        def handle_in(\"unwatch\", %{\"product_id\" => id}, socket) do\n          {:reply, :ok, MyAppWeb.Endpoint.unsubscribe(\"product:#{id}\")}\n        end\n\n        defp put_new_topics(socket, topics) do\n          Enum.reduce(topics, socket, fn topic, acc ->\n            topics = acc.assigns.topics\n            if topic in topics do\n              acc\n            else\n              :ok = MyAppWeb.Endpoint.subscribe(topic)\n              assign(acc, :topics, [topic | topics])\n            end\n          end)\n        end\n      end\n\n  Note: the caller must be responsible for preventing duplicate subscriptions.\n  After calling `subscribe/1` from your endpoint, the same flow applies to\n  handling regular Elixir messages within your channel. Most often, you'll\n  simply relay the `%Phoenix.Socket.Broadcast{}` event and payload:\n\n      alias Phoenix.Socket.Broadcast\n      def handle_info(%Broadcast{topic: _, event: event, payload: payload}, socket) do\n        push(socket, event, payload)\n        {:noreply, socket}\n      end\n\n  ## Hibernation\n\n  From Erlang/OTP 20, channels automatically hibernate to save memory\n  after 15_000 milliseconds of inactivity. This can be customized by\n  passing the `:hibernate_after` option to `use Phoenix.Channel`:\n\n      use Phoenix.Channel, hibernate_after: 60_000\n\n  You can also set it to `:infinity` to fully disable it.\n\n  ## Shutdown\n\n  You can configure the shutdown behavior of each channel used when your\n  application is shutting down by setting the `:shutdown` value on use:\n\n      use Phoenix.Channel, shutdown: 5_000\n\n  It defaults to 5_000. The supported values are described under the\n  in the `Supervisor` module docs.\n\n  ## Logging\n\n  By default, channel `\"join\"` and `\"handle_in\"` events are logged, using\n  the level `:info` and `:debug`, respectively. You can change the level used\n  for each event, or disable logs, per event type by setting the `:log_join`\n  and `:log_handle_in` options when using `Phoenix.Channel`. For example, the\n  following configuration logs join events as `:info`, but disables logging for\n  incoming events:\n\n      use Phoenix.Channel, log_join: :info, log_handle_in: false\n\n  Note that changing an event type's level doesn't affect what is logged,\n  unless you set it to `false`, it affects the associated level.\n  \"\"\"\n  alias Phoenix.Socket\n  alias Phoenix.Channel.Server\n\n  @type payload :: map | term | {:binary, binary}\n  @type reply :: status :: atom | {status :: atom, response :: payload}\n  @type socket_ref ::\n          {transport_pid :: Pid, serializer :: module, topic :: binary, ref :: binary,\n           join_ref :: binary}\n\n  @doc \"\"\"\n  Handle channel joins by `topic`.\n\n  To authorize a socket, return `{:ok, socket}` or `{:ok, reply, socket}`. To\n  refuse authorization, return `{:error, reason}`.\n\n  Payloads are serialized before sending with the configured serializer.\n\n  ## Example\n\n      def join(\"room:lobby\", payload, socket) do\n        if authorized?(payload) do\n          {:ok, socket}\n        else\n          {:error, %{reason: \"unauthorized\"}}\n        end\n      end\n\n  \"\"\"\n  @callback join(topic :: binary, payload :: payload, socket :: Socket.t()) ::\n              {:ok, Socket.t()}\n              | {:ok, reply :: payload, Socket.t()}\n              | {:error, reason :: map}\n\n  @doc \"\"\"\n  Handle incoming `event`s.\n\n  Payloads are serialized before sending with the configured serializer.\n\n  ## Example\n\n      def handle_in(\"ping\", payload, socket) do\n        {:reply, {:ok, payload}, socket}\n      end\n  \"\"\"\n  @callback handle_in(event :: String.t(), payload :: payload, socket :: Socket.t()) ::\n              {:noreply, Socket.t()}\n              | {:noreply, Socket.t(), timeout | :hibernate}\n              | {:reply, reply, Socket.t()}\n              | {:stop, reason :: term, Socket.t()}\n              | {:stop, reason :: term, reply, Socket.t()}\n\n  @doc \"\"\"\n  Intercepts outgoing `event`s.\n\n  See `intercept/1`.\n  \"\"\"\n  @callback handle_out(event :: String.t(), payload :: payload, socket :: Socket.t()) ::\n              {:noreply, Socket.t()}\n              | {:noreply, Socket.t(), timeout | :hibernate}\n              | {:stop, reason :: term, Socket.t()}\n\n  @doc \"\"\"\n  Handle regular Elixir process messages.\n\n  See `c:GenServer.handle_info/2`.\n  \"\"\"\n  @callback handle_info(msg :: term, socket :: Socket.t()) ::\n              {:noreply, Socket.t()}\n              | {:stop, reason :: term, Socket.t()}\n\n  @doc \"\"\"\n  Handle regular GenServer call messages.\n\n  See `c:GenServer.handle_call/3`.\n  \"\"\"\n  @callback handle_call(msg :: term, from :: {pid, tag :: term}, socket :: Socket.t()) ::\n              {:reply, response :: term, Socket.t()}\n              | {:noreply, Socket.t()}\n              | {:stop, reason :: term, Socket.t()}\n\n  @doc \"\"\"\n  Handle regular GenServer cast messages.\n\n  See `c:GenServer.handle_cast/2`.\n  \"\"\"\n  @callback handle_cast(msg :: term, socket :: Socket.t()) ::\n              {:noreply, Socket.t()}\n              | {:stop, reason :: term, Socket.t()}\n\n  @doc false\n  @callback code_change(old_vsn, Socket.t(), extra :: term) ::\n              {:ok, Socket.t()}\n              | {:error, reason :: term}\n            when old_vsn: term | {:down, term}\n\n  @doc \"\"\"\n  Invoked when the channel process is about to exit.\n\n  See `c:GenServer.terminate/2`.\n  \"\"\"\n  @callback terminate(\n              reason :: :normal | :shutdown | {:shutdown, :left | :closed | term},\n              Socket.t()\n            ) ::\n              term\n\n  @optional_callbacks handle_in: 3,\n                      handle_out: 3,\n                      handle_info: 2,\n                      handle_call: 3,\n                      handle_cast: 2,\n                      code_change: 3,\n                      terminate: 2\n\n  defmacro __using__(opts \\\\ []) do\n    quote do\n      opts = unquote(opts)\n      @behaviour unquote(__MODULE__)\n      @on_definition unquote(__MODULE__)\n      @before_compile unquote(__MODULE__)\n      @phoenix_intercepts []\n      @phoenix_log_join Keyword.get(opts, :log_join, :info)\n      @phoenix_log_handle_in Keyword.get(opts, :log_handle_in, :debug)\n      @phoenix_hibernate_after Keyword.get(opts, :hibernate_after, 15_000)\n      @phoenix_shutdown Keyword.get(opts, :shutdown, 5000)\n\n      import unquote(__MODULE__)\n      import Phoenix.Socket, only: [assign: 3, assign: 2]\n\n      def child_spec(init_arg) do\n        %{\n          id: __MODULE__,\n          start: {__MODULE__, :start_link, [init_arg]},\n          shutdown: @phoenix_shutdown,\n          restart: :temporary\n        }\n      end\n\n      def start_link(triplet) do\n        GenServer.start_link(Phoenix.Channel.Server, triplet,\n          hibernate_after: @phoenix_hibernate_after\n        )\n      end\n\n      def __socket__(:private) do\n        %{log_join: @phoenix_log_join, log_handle_in: @phoenix_log_handle_in}\n      end\n    end\n  end\n\n  defmacro __before_compile__(_) do\n    quote do\n      def __intercepts__, do: @phoenix_intercepts\n    end\n  end\n\n  @doc \"\"\"\n  Defines which Channel events to intercept for `handle_out/3` callbacks.\n\n  By default, broadcasted events are pushed directly to the client, but\n  intercepting events gives your channel a chance to customize the event\n  for the client to append extra information or filter the message from being\n  delivered.\n\n  *Note*: intercepting events can introduce significantly more overhead if a\n  large number of subscribers must customize a message since the broadcast will\n  be encoded N times instead of a single shared encoding across all subscribers.\n\n  ## Examples\n\n      intercept [\"new_msg\"]\n\n      def handle_out(\"new_msg\", payload, socket) do\n        push(socket, \"new_msg\", Map.merge(payload,\n          is_editable: User.can_edit_message?(socket.assigns[:user], payload)\n        ))\n        {:noreply, socket}\n      end\n\n  `handle_out/3` callbacks must return one of:\n\n      {:noreply, Socket.t} |\n      {:noreply, Socket.t, timeout | :hibernate} |\n      {:stop, reason :: term, Socket.t}\n\n  \"\"\"\n  defmacro intercept(events) do\n    quote do\n      @phoenix_intercepts unquote(events)\n    end\n  end\n\n  @doc false\n  def __on_definition__(env, :def, :handle_out, [event, _payload, _socket], _, _)\n      when is_binary(event) do\n    unless event in Module.get_attribute(env.module, :phoenix_intercepts) do\n      IO.write(\n        \"#{Path.relative_to(env.file, File.cwd!())}:#{env.line}: [warning] \" <>\n          \"An intercept for event \\\"#{event}\\\" has not yet been defined in #{env.module}.handle_out/3. \" <>\n          \"Add \\\"#{event}\\\" to your list of intercepted events with intercept/1\"\n      )\n    end\n  end\n\n  def __on_definition__(_env, _kind, _name, _args, _guards, _body) do\n    :ok\n  end\n\n  @doc \"\"\"\n  Broadcast an event to all subscribers of the socket topic.\n\n  The event's message must be a serializable map or a tagged `{:binary, data}`\n  tuple where `data` is binary data.\n\n  ## Examples\n\n      iex> broadcast(socket, \"new_message\", %{id: 1, content: \"hello\"})\n      :ok\n\n      iex> broadcast(socket, \"new_message\", {:binary, \"hello\"})\n      :ok\n\n  \"\"\"\n  def broadcast(socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)\n    Server.broadcast(pubsub_server, topic, event, message)\n  end\n\n  @doc \"\"\"\n  Same as `broadcast/3`, but raises if broadcast fails.\n  \"\"\"\n  def broadcast!(socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic} = assert_joined!(socket)\n    Server.broadcast!(pubsub_server, topic, event, message)\n  end\n\n  @doc \"\"\"\n  Broadcast event from pid to all subscribers of the socket topic.\n\n  The channel that owns the socket will not receive the published\n  message. The event's message must be a serializable map or a tagged\n  `{:binary, data}` tuple where `data` is binary data.\n\n  ## Examples\n\n      iex> broadcast_from(socket, \"new_message\", %{id: 1, content: \"hello\"})\n      :ok\n\n      iex> broadcast_from(socket, \"new_message\", {:binary, \"hello\"})\n      :ok\n\n  \"\"\"\n  def broadcast_from(socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} =\n      assert_joined!(socket)\n\n    Server.broadcast_from(pubsub_server, channel_pid, topic, event, message)\n  end\n\n  @doc \"\"\"\n  Same as `broadcast_from/3`, but raises if broadcast fails.\n  \"\"\"\n  def broadcast_from!(socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic, channel_pid: channel_pid} =\n      assert_joined!(socket)\n\n    Server.broadcast_from!(pubsub_server, channel_pid, topic, event, message)\n  end\n\n  @doc \"\"\"\n  Sends an event directly to the connected client without requiring a prior\n  message from the client.\n\n  The event's message must be a serializable map or a tagged `{:binary, data}`\n  tuple where `data` is binary data.\n\n  Note that unlike some in client libraries, this server-side `push/3` does not\n  return a reference. If you need to get a reply from the client and to\n  correlate that reply with the message you pushed, you'll need to include a\n  unique identifier in the message, track it in the Channel's state, have the\n  client include it in its reply, and examine the ref when the reply comes to\n  `handle_in/3`.\n\n  ## Examples\n\n      iex> push(socket, \"new_message\", %{id: 1, content: \"hello\"})\n      :ok\n\n      iex> push(socket, \"new_message\", {:binary, \"hello\"})\n      :ok\n\n  \"\"\"\n  def push(socket, event, message) do\n    %{transport_pid: transport_pid, topic: topic} = assert_joined!(socket)\n    Server.push(transport_pid, socket.join_ref, topic, event, message, socket.serializer)\n  end\n\n  @doc \"\"\"\n  Replies asynchronously to a socket push.\n\n  The usual way of replying to a client's message is to return a tuple from `handle_in/3`\n  like:\n\n      {:reply, {status, payload}, socket}\n\n  But sometimes you need to reply to a push asynchronously - that is, after\n  your `handle_in/3` callback completes. For example, you might need to perform\n  work in another process and reply when it's finished.\n\n  You can do this by generating a reference to the socket with `socket_ref/1`\n  and calling `reply/2` with that ref when you're ready to reply.\n\n  *Note*: A `socket_ref` is required so the `socket` itself is not leaked\n  outside the channel. The `socket` holds information such as assigns and\n  transport configuration, so it's important to not copy this information\n  outside of the channel that owns it.\n\n  Technically, `reply/2` will allow you to reply multiple times to the same\n  client message, and each reply will include the client message `ref`. But the\n  client may expect only one reply; in that case, `push/3` would be preferable\n  for the additional messages.\n\n  Payloads are serialized before sending with the configured serializer.\n\n  ## Examples\n\n      def handle_in(\"work\", payload, socket) do\n        Worker.perform(payload, socket_ref(socket))\n        {:noreply, socket}\n      end\n\n      def handle_info({:work_complete, result, ref}, socket) do\n        reply(ref, {:ok, result})\n        {:noreply, socket}\n      end\n\n  \"\"\"\n  @spec reply(socket_ref, reply) :: :ok\n  def reply(socket_ref, status) when is_atom(status) do\n    reply(socket_ref, {status, %{}})\n  end\n\n  def reply({transport_pid, serializer, topic, ref, join_ref}, {status, payload}) do\n    Server.reply(transport_pid, join_ref, ref, topic, {status, payload}, serializer)\n  end\n\n  @doc \"\"\"\n  Generates a `socket_ref` for an async reply.\n\n  See `reply/2` for example usage.\n  \"\"\"\n  @spec socket_ref(Socket.t()) :: socket_ref\n  def socket_ref(%Socket{joined: true, ref: ref} = socket) when not is_nil(ref) do\n    {socket.transport_pid, socket.serializer, socket.topic, ref, socket.join_ref}\n  end\n\n  def socket_ref(_socket) do\n    raise ArgumentError, \"\"\"\n    socket refs can only be generated for a socket that has joined with a push ref\n    \"\"\"\n  end\n\n  defp assert_joined!(%Socket{joined: true} = socket) do\n    socket\n  end\n\n  defp assert_joined!(%Socket{joined: false}) do\n    raise \"\"\"\n    push/3, reply/2, and broadcast/3 can only be called after the socket has finished joining.\n    To push a message on join, send to self and handle in handle_info/2. For example:\n\n        def join(topic, auth_msg, socket) do\n          ...\n          send(self(), :after_join)\n          {:ok, socket}\n        end\n\n        def handle_info(:after_join, socket) do\n          push(socket, \"feed\", %{list: feed_items(socket)})\n          {:noreply, socket}\n        end\n\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/code_reloader/mix_listener.ex",
    "content": "defmodule Phoenix.CodeReloader.MixListener do\n  @moduledoc false\n\n  use GenServer\n\n  @name __MODULE__\n\n  @spec start_link(keyword) :: GenServer.on_start()\n  def start_link(_opts) do\n    GenServer.start_link(__MODULE__, {}, name: @name)\n  end\n\n  @spec started? :: boolean()\n  def started? do\n    Process.whereis(Phoenix.CodeReloader.MixListener) != nil\n  end\n\n  @doc \"\"\"\n  Unloads all modules invalidated by external compilations.\n\n  Only reloads modules from the given apps.\n  \"\"\"\n  @spec purge([atom()]) :: :ok\n  def purge(apps) do\n    GenServer.call(@name, {:purge, apps}, :infinity)\n  end\n\n  @impl true\n  def init({}) do\n    {:ok, %{to_purge: %{}}}\n  end\n\n  @impl true\n  def handle_call({:purge, apps}, _from, state) do\n    for app <- apps, modules = state.to_purge[app] do\n      purge_modules(modules)\n    end\n\n    {:reply, :ok, %{state | to_purge: %{}}}\n  end\n\n  @impl true\n  def handle_info({:modules_compiled, info}, state) do\n    if info.os_pid == System.pid() do\n      # Ignore compilations from ourselves, because the modules are\n      # already updated in memory\n      {:noreply, state}\n    else\n      %{changed: changed, removed: removed} = info.modules_diff\n\n      state =\n        update_in(state.to_purge[info.app], fn to_purge ->\n          to_purge = to_purge || MapSet.new()\n          to_purge = Enum.into(changed, to_purge)\n          Enum.into(removed, to_purge)\n        end)\n\n      {:noreply, state}\n    end\n  end\n\n  def handle_info(_message, state) do\n    {:noreply, state}\n  end\n\n  defp purge_modules(modules) do\n    for module <- modules do\n      :code.purge(module)\n      :code.delete(module)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/code_reloader/proxy.ex",
    "content": "# A tiny proxy that stores all output sent to the group leader\n# while forwarding all requests to it.\ndefmodule Phoenix.CodeReloader.Proxy do\n  @moduledoc false\n  use GenServer\n\n  def start() do\n    GenServer.start(__MODULE__, :ok)\n  end\n\n  def diagnostics(proxy, diagnostics) do\n    GenServer.cast(proxy, {:diagnostics, diagnostics})\n  end\n\n  def stop(proxy) do\n    GenServer.call(proxy, :stop, :infinity)\n  end\n\n  ## Callbacks\n\n  def init(:ok) do\n    {:ok, []}\n  end\n\n  def handle_cast({:diagnostics, diagnostics}, output) do\n    {:noreply, diagnostics |> Enum.map(&diagnostic_to_chars/1) |> Enum.reverse(output)}\n  end\n\n  def handle_call(:stop, _from, output) do\n    {:stop, :normal, Enum.reverse(output), output}\n  end\n\n  def handle_info(msg, output) do\n    case msg do\n      {:io_request, from, reply, {:put_chars, chars}} ->\n        put_chars(from, reply, chars, output)\n\n      {:io_request, from, reply, {:put_chars, m, f, as}} ->\n        put_chars(from, reply, apply(m, f, as), output)\n\n      {:io_request, from, reply, {:put_chars, _encoding, chars}} ->\n        put_chars(from, reply, chars, output)\n\n      {:io_request, from, reply, {:put_chars, _encoding, m, f, as}} ->\n        put_chars(from, reply, apply(m, f, as), output)\n\n      {:io_request, _from, _reply, _request} = msg ->\n        send(Process.group_leader(), msg)\n        {:noreply, output}\n\n      _ ->\n        {:noreply, output}\n    end\n  end\n\n  defp put_chars(from, reply, chars, output) do\n    send(Process.group_leader(), {:io_request, from, reply, {:put_chars, chars}})\n    {:noreply, [chars | output]}\n  end\n\n  defp diagnostic_to_chars(%{severity: :error, message: \"**\" <> _ = message}) do\n    \"\\n#{message}\\n\"\n  end\n\n  defp diagnostic_to_chars(%{severity: severity, message: message, file: file, position: position}) when is_binary(file) do\n    \"\\n#{severity}: #{message}\\n  #{Path.relative_to_cwd(file)}#{position(position)}\\n\"\n  end\n\n  defp diagnostic_to_chars(%{severity: severity, message: message}) do\n    \"\\n#{severity}: #{message}\\n\"\n  end\n\n  defp position({line, col}), do: \":#{line}:#{col}\"\n  defp position(line) when is_integer(line) and line > 0, do: \":#{line}\"\n  defp position(_), do: \"\"\nend\n"
  },
  {
    "path": "lib/phoenix/code_reloader/server.ex",
    "content": "defmodule Phoenix.CodeReloader.Server do\n  @moduledoc false\n  use GenServer\n\n  require Logger\n  alias Phoenix.CodeReloader.Proxy\n\n  def start_link(_) do\n    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)\n  end\n\n  def check_symlinks do\n    GenServer.call(__MODULE__, :check_symlinks, :infinity)\n  end\n\n  def reload!(endpoint, opts) do\n    GenServer.call(__MODULE__, {:reload!, endpoint, opts}, :infinity)\n  end\n\n  def sync do\n    pid = Process.whereis(__MODULE__)\n    ref = Process.monitor(pid)\n    GenServer.cast(pid, {:sync, self(), ref})\n\n    receive do\n      ^ref -> :ok\n      {:DOWN, ^ref, _, _, _} -> :ok\n    end\n  end\n\n  ## Callbacks\n\n  def init(:ok) do\n    {:ok, %{check_symlinks: true, timestamp: timestamp()}}\n  end\n\n  def handle_call(:check_symlinks, _from, state) do\n    if state.check_symlinks and Code.ensure_loaded?(Mix.Project) and not Mix.Project.umbrella?() and\n         File.dir?(\"priv\") do\n      priv_path = \"#{Mix.Project.app_path()}/priv\"\n\n      case :file.read_link(priv_path) do\n        {:ok, _} ->\n          :ok\n\n        {:error, _} ->\n          if can_symlink?() do\n            File.rm_rf(priv_path)\n            Mix.Project.build_structure()\n          else\n            Logger.warning(\n              \"Phoenix is unable to create symlinks. Phoenix's code reloader will run \" <>\n                \"considerably faster if symlinks are allowed.\" <> os_symlink(:os.type())\n            )\n          end\n      end\n    end\n\n    {:reply, :ok, %{state | check_symlinks: false}}\n  end\n\n  def handle_call({:reload!, endpoint, opts}, from, state) do\n    compilers = endpoint.config(:reloadable_compilers)\n    apps = endpoint.config(:reloadable_apps) || default_reloadable_apps()\n    args = Keyword.get(opts, :reloadable_args, [\"--no-all-warnings\"])\n\n    froms = all_waiting([from], endpoint)\n\n    {backup, res, out} =\n      with_build_lock(fn ->\n        purge_fallback? =\n          if Phoenix.CodeReloader.MixListener.started?() do\n            Phoenix.CodeReloader.MixListener.purge(apps)\n            false\n          else\n            warn_missing_mix_listener()\n            true\n          end\n\n        # We do a backup of the endpoint in case compilation fails.\n        # If so we can bring it back to finish the request handling.\n        backup = load_backup(endpoint)\n\n        {res, out} =\n          proxy_io(fn ->\n            try do\n              task_loaded = Code.ensure_loaded(Mix.Task)\n              mix_compile(task_loaded, compilers, apps, args, state.timestamp, purge_fallback?)\n            catch\n              :exit, {:shutdown, 1} ->\n                :error\n\n              kind, reason ->\n                IO.puts(Exception.format(kind, reason, __STACKTRACE__))\n                :error\n            end\n          end)\n\n        {backup, res, out}\n      end)\n\n    reply =\n      case res do\n        :ok ->\n          :ok\n\n        :error ->\n          write_backup(backup)\n          {:error, IO.iodata_to_binary(out)}\n      end\n\n    Enum.each(froms, &GenServer.reply(&1, reply))\n    {:noreply, %{state | timestamp: timestamp()}}\n  end\n\n  def handle_cast({:sync, pid, ref}, state) do\n    send(pid, ref)\n    {:noreply, state}\n  end\n\n  def handle_info(_, state) do\n    {:noreply, state}\n  end\n\n  defp default_reloadable_apps() do\n    if Mix.Project.umbrella?() do\n      Enum.map(Mix.Dep.Umbrella.cached(), & &1.app)\n    else\n      [Mix.Project.config()[:app]]\n    end\n  end\n\n  defp os_symlink({:win32, _}),\n    do:\n      \" On Windows, the lack of symlinks may even cause empty assets to be served. \" <>\n        \"Luckily, you can address this issue by starting your Windows terminal at least \" <>\n        \"once with \\\"Run as Administrator\\\" and then running your Phoenix application.\"\n\n  defp os_symlink(_),\n    do: \"\"\n\n  defp can_symlink?() do\n    build_path = Mix.Project.build_path()\n    symlink = Path.join(Path.dirname(build_path), \"__phoenix__\")\n\n    case File.ln_s(build_path, symlink) do\n      :ok ->\n        File.rm_rf(symlink)\n        true\n\n      {:error, :eexist} ->\n        File.rm_rf(symlink)\n        true\n\n      {:error, _} ->\n        false\n    end\n  end\n\n  defp load_backup(mod) do\n    mod\n    |> :code.which()\n    |> read_backup()\n  end\n\n  defp read_backup(path) when is_list(path) do\n    case File.read(path) do\n      {:ok, binary} -> {:ok, path, binary}\n      _ -> :error\n    end\n  end\n\n  defp read_backup(_path), do: :error\n\n  defp write_backup({:ok, path, file}), do: File.write!(path, file)\n  defp write_backup(:error), do: :ok\n\n  defp all_waiting(acc, endpoint) do\n    receive do\n      {:\"$gen_call\", from, {:reload!, ^endpoint, _}} -> all_waiting([from | acc], endpoint)\n    after\n      0 -> acc\n    end\n  end\n\n  if Version.match?(System.version(), \">= 1.18.0-dev\") do\n    defp warn_missing_mix_listener do\n      if Mix.Project.get() != Phoenix.MixProject do\n        IO.warn(\"\"\"\n        a Mix listener expected by Phoenix.CodeReloader is missing.\n\n        Please add the listener to your mix.exs configuration, like so:\n\n            def project do\n              [\n                ...,\n                listeners: [Phoenix.CodeReloader]\n              ]\n            end\n\n        \"\"\")\n      end\n    end\n  else\n    defp warn_missing_mix_listener do\n      :ok\n    end\n  end\n\n  defp mix_compile(\n         {:module, Mix.Task},\n         compilers,\n         apps_to_reload,\n         compile_args,\n         timestamp,\n         purge_fallback?\n       ) do\n    config = Mix.Project.config()\n    path = Mix.Project.consolidation_path(config)\n\n    mix_compile_deps(\n      Mix.Dep.cached(),\n      apps_to_reload,\n      compile_args,\n      compilers,\n      timestamp,\n      path,\n      purge_fallback?\n    )\n\n    mix_compile_project(\n      config[:app],\n      apps_to_reload,\n      compile_args,\n      compilers,\n      timestamp,\n      path,\n      purge_fallback?\n    )\n\n    if config[:consolidate_protocols] do\n      # If we are consolidating protocols, we need to purge all of its modules\n      # to ensure the consolidated versions are loaded. \"mix compile\" performs\n      # a similar task.\n      Code.prepend_path(path)\n      purge_modules(path)\n    end\n\n    :ok\n  end\n\n  defp mix_compile({:error, _reason}, _, _, _, _, _) do\n    raise \"the Code Reloader is enabled but Mix is not available. If you want to \" <>\n            \"use the Code Reloader in production or inside an escript, you must add \" <>\n            \":mix to your applications list. Otherwise, you must disable code reloading \" <>\n            \"in such environments\"\n  end\n\n  defp mix_compile_deps(\n         deps,\n         apps_to_reload,\n         compile_args,\n         compilers,\n         timestamp,\n         path,\n         purge_fallback?\n       ) do\n    for dep <- deps, dep.app in apps_to_reload do\n      Mix.Dep.in_dependency(dep, fn _ ->\n        mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?)\n      end)\n    end\n  end\n\n  defp mix_compile_project(nil, _, _, _, _, _, _), do: :ok\n\n  defp mix_compile_project(\n         app,\n         apps_to_reload,\n         compile_args,\n         compilers,\n         timestamp,\n         path,\n         purge_fallback?\n       ) do\n    if app in apps_to_reload do\n      mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?)\n    end\n  end\n\n  defp mix_compile_unless_stale_config(compilers, compile_args, timestamp, path, purge_fallback?) do\n    manifests = Mix.Tasks.Compile.Elixir.manifests()\n    configs = Mix.Project.config_files()\n    config = Mix.Project.config()\n\n    case Mix.Utils.extract_stale(configs, manifests) do\n      [] ->\n        # If the manifests are more recent than the timestamp,\n        # someone updated this app behind the scenes, so purge all beams.\n        # TODO: remove once we depend on Elixir 1.18\n        if purge_fallback? and Mix.Utils.stale?(manifests, [timestamp]) do\n          purge_modules(Path.join(Mix.Project.app_path(config), \"ebin\"))\n        end\n\n        mix_compile(compilers, compile_args, config, path)\n\n      files ->\n        raise \"\"\"\n        could not compile application: #{Mix.Project.config()[:app]}.\n\n        You must restart your server after changing configuration files or your dependencies.\n        In particular, the following files changed and must be recomputed on a server restart:\n\n          * #{Enum.map_join(files, \"\\n  * \", &Path.relative_to_cwd/1)}\n\n        \"\"\"\n    end\n  end\n\n  defp mix_compile(compilers, compile_args, config, consolidation_path) do\n    all = config[:compilers] || Mix.compilers()\n\n    compilers =\n      for compiler <- compilers, compiler in all do\n        Mix.Task.reenable(\"compile.#{compiler}\")\n        compiler\n      end\n\n    # We call build_structure mostly for Windows so new\n    # assets in priv are copied to the build directory.\n    Mix.Project.build_structure(config)\n\n    args = [\n      # TODO: The purge option may no longer be required from Elixir v1.18\n      \"--purge-consolidation-path-if-stale\",\n      consolidation_path,\n      # Since Elixir v1.20, Elixir no longer automatically purges compiler\n      # modules, which is ok for most workflows, but since code reloading never\n      # shuts down the server, we enable purging to avoid too many temp modules.\n      \"--purge-compiler-modules\" | compile_args\n    ]\n\n    {status, diagnostics} =\n      with_logger_app(config, fn ->\n        run_compilers(compilers, args, :noop, [])\n      end)\n\n    Proxy.diagnostics(Process.group_leader(), diagnostics)\n\n    cond do\n      status == :error ->\n        if \"--return-errors\" not in args do\n          exit({:shutdown, 1})\n        end\n\n      status == :ok && config[:consolidate_protocols] ->\n        # TODO: Calling compile.protocols is no longer be required from Elixir v1.19\n        Mix.Task.reenable(\"compile.protocols\")\n        Mix.Task.run(\"compile.protocols\", [])\n        :ok\n\n      true ->\n        :ok\n    end\n  end\n\n  defp timestamp, do: System.system_time(:second)\n\n  defp purge_modules(path) do\n    with {:ok, beams} <- File.ls(path) do\n      for beam <- beams do\n        case :binary.split(beam, \".beam\") do\n          [module, \"\"] -> module |> String.to_atom() |> purge_module()\n          _ -> :ok\n        end\n      end\n\n      :ok\n    end\n  end\n\n  defp purge_module(module) do\n    :code.purge(module)\n    :code.delete(module)\n  end\n\n  defp proxy_io(fun) do\n    original_gl = Process.group_leader()\n    {:ok, proxy_gl} = Proxy.start()\n    Process.group_leader(self(), proxy_gl)\n\n    try do\n      {fun.(), Proxy.stop(proxy_gl)}\n    after\n      Process.group_leader(self(), original_gl)\n      Process.exit(proxy_gl, :kill)\n    end\n  end\n\n  ## TODO: Replace this by Mix.Task.Compiler.run/2 on Elixir v1.19+\n\n  defp run_compilers([], _, status, diagnostics) do\n    {status, diagnostics}\n  end\n\n  defp run_compilers([compiler | rest], args, status, diagnostics) do\n    {new_status, new_diagnostics} = run_compiler(compiler, args)\n    diagnostics = diagnostics ++ new_diagnostics\n\n    case new_status do\n      :error -> {:error, diagnostics}\n      :ok -> run_compilers(rest, args, :ok, diagnostics)\n      :noop -> run_compilers(rest, args, status, diagnostics)\n    end\n  end\n\n  defp run_compiler(compiler, args) do\n    result = normalize(Mix.Task.run(\"compile.#{compiler}\", args), compiler)\n    Enum.reduce(Mix.ProjectStack.pop_after_compiler(compiler), result, & &1.(&2))\n  end\n\n  defp normalize(result, name) do\n    case result do\n      {status, diagnostics} when status in [:ok, :noop, :error] and is_list(diagnostics) ->\n        {status, diagnostics}\n\n      # ok/noop can come from tasks that have already run\n      _ when result in [:ok, :noop] ->\n        {result, []}\n\n      _ ->\n        # TODO: Convert this to an error on v2.0\n        Mix.shell().error(\n          \"warning: Mix compiler #{inspect(name)} was supposed to return \" <>\n            \"{:ok | :noop | :error, [diagnostic]} but it returned #{inspect(result)}\"\n        )\n\n        {:noop, []}\n    end\n  end\n\n  # TODO: remove once we depend on Elixir 1.17\n  defp with_logger_app(config, fun) do\n    app = Keyword.fetch!(config, :app)\n    logger_config_app = Application.get_env(:logger, :compile_time_application)\n\n    try do\n      Logger.configure(compile_time_application: app)\n      fun.()\n    after\n      Logger.configure(compile_time_application: logger_config_app)\n    end\n  end\n\n  # TODO: remove once we depend on Elixir 1.18\n  if Code.ensure_loaded?(Mix.Project) and function_exported?(Mix.Project, :with_build_lock, 1) do\n    defp with_build_lock(fun), do: Mix.Project.with_build_lock(fun)\n  else\n    defp with_build_lock(fun), do: fun.()\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/code_reloader.ex",
    "content": "defmodule Phoenix.CodeReloader do\n  @moduledoc \"\"\"\n  A plug and module to handle automatic code reloading.\n\n  To avoid race conditions, all code reloads are funneled through a\n  sequential call operation.\n  \"\"\"\n\n  ## Server delegation\n\n  @doc \"\"\"\n  Reloads code for the current Mix project by invoking the\n  `:reloadable_compilers` on the list of `:reloadable_apps`.\n\n  This is configured in your application environment like:\n\n      config :your_app, YourAppWeb.Endpoint,\n        reloadable_compilers: [:gettext, :elixir],\n        reloadable_apps: [:ui, :backend]\n\n  Keep in mind `:reloadable_compilers` must be a subset of the\n  `:compilers` specified in `project/0` in your `mix.exs`.\n\n  The `:reloadable_apps` defaults to `nil`. In such case\n  default behaviour is to reload the current project if it\n  consists of a single app, or all applications within an umbrella\n  project. You can set `:reloadable_apps` to a subset of default\n  applications to reload only some of them, an empty list - to\n  effectively disable the code reloader, or include external\n  applications from library dependencies.\n\n  This function is a no-op and returns `:ok` if Mix is not available.\n\n  The reloader should also be configured as a Mix listener in project's\n  mix.exs file (since Elixir v1.18):\n\n      def project do\n        [\n          ...,\n          listeners: [Phoenix.CodeReloader]\n        ]\n      end\n\n  This way the reloader can notice whenever the project is compiled\n  concurrently.\n\n  ## Options\n\n    * `:reloadable_args` - additional CLI args to pass to the compiler tasks.\n      Defaults to `[\"--no-all-warnings\"]` so only warnings related to the\n      files being compiled are printed\n\n  \"\"\"\n  @spec reload(module, keyword) :: :ok | {:error, binary()}\n  def reload(endpoint, opts \\\\ []) do\n    if Code.ensure_loaded?(Mix.Project), do: reload!(endpoint, opts), else: :ok\n  end\n\n  @doc \"\"\"\n  Same as `reload/1` but it will raise if Mix is not available.\n  \"\"\"\n  @spec reload!(module, keyword) :: :ok | {:error, binary()}\n  defdelegate reload!(endpoint, opts), to: Phoenix.CodeReloader.Server\n\n  @doc \"\"\"\n  Synchronizes with the code server if it is alive.\n\n  It returns `:ok`. If it is not running, it also returns `:ok`.\n  \"\"\"\n  @spec sync :: :ok\n  defdelegate sync, to: Phoenix.CodeReloader.Server\n\n  @doc false\n  @spec child_spec(keyword) :: Supervisor.child_spec()\n  defdelegate child_spec(opts), to: Phoenix.CodeReloader.MixListener\n\n  ## Plug\n\n  @behaviour Plug\n  import Plug.Conn\n\n  @style %{\n    light: %{\n      primary: \"#EB532D\",\n      accent: \"#a0b0c0\",\n      text_color: \"#304050\",\n      background: \"#ffffff\",\n      heading_background: \"#f9f9fa\"\n    },\n    dark: %{\n      primary: \"#FF6B4A\",\n      accent: \"#c0c0c0\",\n      text_color: \"#e5e5e5\",\n      background: \"#1a1a1a\",\n      heading_background: \"#2a2a2a\"\n    },\n    logo:\n      \"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K\",\n    monospace_font: \"menlo, consolas, monospace\"\n  }\n\n  @doc \"\"\"\n  API used by Plug to start the code reloader.\n  \"\"\"\n  def init(opts) do\n    Keyword.put_new(opts, :reloader, &Phoenix.CodeReloader.reload/2)\n  end\n\n  @doc \"\"\"\n  API used by Plug to invoke the code reloader on every request.\n  \"\"\"\n  def call(conn, opts) do\n    case opts[:reloader].(conn.private.phoenix_endpoint, opts) do\n      :ok ->\n        conn\n\n      {:error, output} ->\n        conn\n        |> put_resp_content_type(\"text/html\")\n        |> send_resp(500, template(output))\n        |> halt()\n    end\n  end\n\n  defp template(output) do\n    \"\"\"\n    <!DOCTYPE html>\n    <html>\n    <head>\n        <meta charset=\"utf-8\">\n        <title>CompileError</title>\n        <meta name=\"viewport\" content=\"width=device-width\">\n        <style>/*! normalize.css v4.2.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,optgroup,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input{overflow:visible}button,select{text-transform:none}button,html [type=\"button\"],[type=\"reset\"],[type=\"submit\"]{-webkit-appearance:button}button::-moz-focus-inner,[type=\"button\"]::-moz-focus-inner,[type=\"reset\"]::-moz-focus-inner,[type=\"submit\"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=\"button\"]:-moz-focusring,[type=\"reset\"]:-moz-focusring,[type=\"submit\"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type=\"checkbox\"],[type=\"radio\"]{box-sizing:border-box;padding:0}[type=\"number\"]::-webkit-inner-spin-button,[type=\"number\"]::-webkit-outer-spin-button{height:auto}[type=\"search\"]{-webkit-appearance:textfield;outline-offset:-2px}[type=\"search\"]::-webkit-search-cancel-button,[type=\"search\"]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-input-placeholder{color:inherit;opacity:0.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}</style>\n        <style>\n        html, body, td, input {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Roboto\", \"Oxygen\", \"Ubuntu\", \"Cantarell\", \"Fira Sans\", \"Droid Sans\", \"Helvetica Neue\", sans-serif;\n        }\n\n        * {\n            box-sizing: border-box;\n        }\n\n        html {\n            font-size: 15px;\n            line-height: 1.6;\n            background: #{@style.light.background};\n            color: #{@style.light.text_color};\n        }\n\n        @media (prefers-color-scheme: dark) {\n            html {\n                background: #{@style.dark.background};\n                color: #{@style.dark.text_color};\n            }\n        }\n\n        @media (max-width: 768px) {\n            html {\n                 font-size: 14px;\n            }\n        }\n\n        @media (max-width: 480px) {\n            html {\n                 font-size: 13px;\n            }\n        }\n\n        button:focus,\n        summary:focus {\n            outline: 0;\n        }\n\n        summary {\n            cursor: pointer;\n        }\n\n        pre {\n            font-family: #{@style.monospace_font};\n            max-width: 100%;\n        }\n\n        .heading-block {\n            background: #{@style.light.heading_background};\n        }\n\n        @media (prefers-color-scheme: dark) {\n            .heading-block {\n                background: #{@style.dark.heading_background};\n            }\n        }\n\n        .heading-block,\n        .output-block {\n            padding: 48px;\n        }\n\n        @media (max-width: 768px) {\n            .heading-block,\n            .output-block {\n                padding: 32px;\n            }\n        }\n\n        @media (max-width: 480px) {\n            .heading-block,\n            .output-block {\n                padding: 16px;\n            }\n        }\n\n        /*\n         * Exception logo\n         */\n\n        .exception-logo {\n            position: absolute;\n            right: 48px;\n            top: 48px;\n            pointer-events: none;\n            width: 100%;\n        }\n\n        .exception-logo:before {\n            content: '';\n            display: block;\n            height: 64px;\n            width: 100%;\n            background-size: auto 100%;\n            background-image: url(\"#{@style.logo}\");\n            background-position: right 0;\n            background-repeat: no-repeat;\n            margin-bottom: 16px;\n        }\n\n        @media (max-width: 768px) {\n            .exception-logo {\n                position: static;\n            }\n\n            .exception-logo:before {\n                height: 32px;\n                background-position: left 0;\n            }\n        }\n\n        @media (max-width: 480px) {\n            .exception-logo {\n                display: none;\n            }\n        }\n\n        /*\n         * Exception info\n         */\n\n        /* Compensate for logo placement */\n        @media (min-width: 769px) {\n            .exception-info {\n                max-width: 90%;\n            }\n        }\n\n        .exception-info > .error,\n        .exception-info > .subtext {\n            margin: 0;\n            padding: 0;\n        }\n\n        .exception-info > .error {\n            font-size: 1em;\n            font-weight: 700;\n            color: #{@style.light.primary};\n        }\n\n        @media (prefers-color-scheme: dark) {\n            .exception-info > .error {\n                color: #{@style.dark.primary};\n            }\n        }\n\n        .exception-info > .subtext {\n            font-size: 1em;\n            font-weight: 400;\n            color: #{@style.light.accent};\n        }\n\n        @media (prefers-color-scheme: dark) {\n            .exception-info > .subtext {\n                color: #{@style.dark.accent};\n            }\n        }\n\n        @media (max-width: 768px) {\n            .exception-info > .title {\n                font-size: #{:math.pow(1.15, 4)}em;\n            }\n        }\n\n        @media (max-width: 480px) {\n            .exception-info > .title {\n                font-size: #{:math.pow(1.1, 4)}em;\n            }\n        }\n\n        .code-block {\n            margin: 0;\n            font-size: .85em;\n            line-height: 1.6;\n            white-space: pre-wrap;\n        }\n        </style>\n    </head>\n    <body>\n        <div class=\"heading-block\">\n            <aside class=\"exception-logo\"></aside>\n            <header class=\"exception-info\">\n                <h5 class=\"error\">Compilation error</h5>\n                <h5 class=\"subtext\">Console output is shown below.</h5>\n            </header>\n        </div>\n        <div class=\"output-block\">\n            <pre class=\"code code-block\">#{format_output(output)}</pre>\n        </div>\n    </body>\n    </html>\n    \"\"\"\n  end\n\n  defp format_output(output) do\n    output\n    |> String.trim()\n    |> remove_ansi_escapes()\n    |> Plug.HTML.html_escape()\n  end\n\n  defp remove_ansi_escapes(text) do\n    Regex.replace(~r/\\e\\[[0-9;]*[a-zA-Z]/, text, \"\")\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/config.ex",
    "content": "defmodule Phoenix.Config do\n  # Handles Phoenix configuration.\n  #\n  # This module is private to Phoenix and should not be accessed\n  # directly. The Phoenix endpoint configuration can be accessed\n  # at runtime using the `config/2` function.\n  @moduledoc false\n\n  use GenServer\n\n  @doc \"\"\"\n  Starts a Phoenix configuration handler.\n  \"\"\"\n  def start_link({module, config, defaults, opts}) do\n    permanent = Keyword.keys(defaults)\n    GenServer.start_link(__MODULE__, {module, config, permanent}, opts)\n  end\n\n  @doc \"\"\"\n  Puts a given key-value pair in config.\n  \"\"\"\n  def put(module, key, value) do\n    :ets.insert(module, {key, value})\n  end\n\n  @doc \"\"\"\n  Adds permanent configuration.\n\n  Permanent configuration is not deleted on hot code reload.\n  \"\"\"\n  def permanent(module, key, value) do\n    pid = :ets.lookup_element(module, :__config__, 2)\n    GenServer.call(pid, {:permanent, key, value})\n  end\n\n  @doc \"\"\"\n  Caches a value in Phoenix configuration handler for the module.\n\n  The given function needs to return a tuple with `:cache` if the\n  value should be cached or `:nocache` if the value should not be\n  cached because it can be consequently considered stale.\n\n  Notice writes are not serialized to the server, we expect the\n  function that generates the cache to be idempotent.\n  \"\"\"\n  @spec cache(module, term, (module -> {:cache | :nocache, term})) :: term\n  def cache(module, key, fun) do\n    try do\n      :ets.lookup(module, key)\n    rescue\n      e ->\n        case :ets.info(module) do\n          :undefined ->\n            raise \"could not find ets table for endpoint #{inspect(module)}. Make sure your endpoint is started and note you cannot access endpoint functions at compile-time\"\n\n          _ ->\n            reraise e, __STACKTRACE__\n        end\n    else\n      [{^key, :cache, val}] ->\n        val\n\n      [] ->\n        case fun.(module) do\n          {:cache, val} ->\n            :ets.insert(module, {key, :cache, val})\n            val\n\n          {:nocache, val} ->\n            val\n        end\n    end\n  end\n\n  @doc \"\"\"\n  Clears all cached entries in the endpoint.\n  \"\"\"\n  @spec clear_cache(module) :: :ok\n  def clear_cache(module) do\n    :ets.match_delete(module, {:_, :cache, :_})\n    :ok\n  end\n\n  @doc \"\"\"\n  Reads the configuration for module from the given OTP app.\n\n  Useful to read a particular value at compilation time.\n  \"\"\"\n  def from_env(otp_app, module, defaults) do\n    config = fetch_config(otp_app, module)\n\n    merge(defaults, config)\n  end\n\n  defp fetch_config(otp_app, module) do\n    case Application.fetch_env(otp_app, module) do\n      {:ok, conf} -> conf\n      :error -> []\n    end\n  end\n\n  @doc \"\"\"\n  Take 2 keyword lists and merge them recursively.\n\n  Used to merge configuration values into defaults.\n  \"\"\"\n  def merge(a, b), do: Keyword.merge(a, b, &merger/3)\n\n  defp merger(_k, v1, v2) do\n    if Keyword.keyword?(v1) and Keyword.keyword?(v2) do\n      Keyword.merge(v1, v2, &merger/3)\n    else\n      v2\n    end\n  end\n\n  @doc \"\"\"\n  Changes the configuration for the given module.\n\n  It receives a keyword list with changed config and another\n  with removed ones. The changed config are updated while the\n  removed ones stop the configuration server, effectively removing\n  the table.\n  \"\"\"\n  def config_change(module, changed, removed) do\n    pid = :ets.lookup_element(module, :__config__, 2)\n    GenServer.call(pid, {:config_change, changed, removed})\n  end\n\n  # Callbacks\n\n  def init({module, config, permanent}) do\n    :ets.new(module, [:named_table, :public, read_concurrency: true])\n    update(module, config, [])\n    :ets.insert(module, {:__config__, self()})\n    {:ok, {module, [:__config__ | permanent]}}\n  end\n\n  def handle_call({:permanent, key, value}, _from, {module, permanent}) do\n    :ets.insert(module, {key, value})\n    {:reply, :ok, {module, [key | permanent]}}\n  end\n\n  def handle_call({:config_change, changed, removed}, _from, {module, permanent}) do\n    cond do\n      changed = changed[module] ->\n        update(module, changed, permanent)\n        {:reply, :ok, {module, permanent}}\n\n      module in removed ->\n        {:stop, :normal, :ok, {module, permanent}}\n\n      true ->\n        clear_cache(module)\n        {:reply, :ok, {module, permanent}}\n    end\n  end\n\n  defp update(module, config, permanent) do\n    old_keys = :ets.select(module, [{{:\"$1\", :_}, [], [:\"$1\"]}])\n    new_keys = Enum.map(config, &elem(&1, 0))\n    Enum.each((old_keys -- new_keys) -- permanent, &:ets.delete(module, &1))\n    :ets.insert(module, config)\n    clear_cache(module)\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/controller/pipeline.ex",
    "content": "defmodule Phoenix.Controller.Pipeline do\n  @moduledoc false\n\n  @doc false\n  defmacro __using__(_) do\n    quote do\n      @behaviour Plug\n\n      require Phoenix.Endpoint\n      import Phoenix.Controller.Pipeline\n\n      Module.register_attribute(__MODULE__, :plugs, accumulate: true)\n      @before_compile Phoenix.Controller.Pipeline\n      @phoenix_fallback :unregistered\n\n      @doc false\n      def init(opts), do: opts\n\n      @doc false\n      def call(conn, action) when is_atom(action) do\n        conn\n        |> merge_private(\n          phoenix_controller: __MODULE__,\n          phoenix_action: action\n        )\n        |> phoenix_controller_pipeline(action)\n      end\n\n      @doc false\n      def action(%Plug.Conn{private: %{phoenix_action: action}} = conn, _options) do\n        apply(__MODULE__, action, [conn, conn.params])\n      end\n\n      defoverridable init: 1, call: 2, action: 2\n    end\n  end\n\n  @doc false\n  def __action_fallback__(plug, caller) do\n    plug = Macro.expand(plug, %{caller | function: {:init, 1}})\n    quote bind_quoted: [plug: plug] do\n      @phoenix_fallback Phoenix.Controller.Pipeline.validate_fallback(\n                          plug,\n                          __MODULE__,\n                          Module.get_attribute(__MODULE__, :phoenix_fallback)\n                        )\n    end\n  end\n\n  @doc false\n  def validate_fallback(plug, module, fallback) do\n    cond do\n      fallback == nil ->\n        raise \"\"\"\n        action_fallback can only be called when using Phoenix.Controller.\n        Add `use Phoenix.Controller` to #{inspect(module)}\n        \"\"\"\n\n      fallback != :unregistered ->\n        raise \"action_fallback can only be called a single time per controller.\"\n\n      not is_atom(plug) ->\n        raise ArgumentError,\n              \"expected action_fallback to be a module or function plug, got #{inspect(plug)}\"\n\n      fallback == :unregistered ->\n        case Atom.to_charlist(plug) do\n          ~c\"Elixir.\" ++ _ -> {:module, plug}\n          _ -> {:function, plug}\n        end\n    end\n  end\n\n  @doc false\n  defmacro __before_compile__(env) do\n    action = {:action, [], true}\n    plugs = [action | Module.get_attribute(env.module, :plugs)]\n\n    {conn, body} =\n      Plug.Builder.compile(env, plugs,\n        log_on_halt: :debug,\n        init_mode: Phoenix.plug_init_mode()\n      )\n\n    fallback_ast =\n      env.module\n      |> Module.get_attribute(:phoenix_fallback)\n      |> build_fallback()\n\n    quote do\n      defoverridable action: 2\n\n      def action(var!(conn_before), opts) do\n        try do\n          var!(conn_after) = super(var!(conn_before), opts)\n          unquote(fallback_ast)\n        catch\n          :error, reason ->\n            Phoenix.Controller.Pipeline.__catch__(\n              var!(conn_before),\n              reason,\n              __MODULE__,\n              var!(conn_before).private.phoenix_action,\n              __STACKTRACE__\n            )\n        end\n      end\n\n      defp phoenix_controller_pipeline(unquote(conn), var!(action)) do\n        var!(conn) = unquote(conn)\n        var!(controller) = __MODULE__\n        _ = var!(conn)\n        _ = var!(controller)\n        _ = var!(action)\n\n        unquote(body)\n      end\n    end\n  end\n\n  defp build_fallback(:unregistered) do\n    quote do: var!(conn_after)\n  end\n\n  defp build_fallback({:module, plug}) do\n    quote bind_quoted: binding() do\n      case var!(conn_after) do\n        %Plug.Conn{} = conn_after -> conn_after\n        val -> plug.call(var!(conn_before), plug.init(val))\n      end\n    end\n  end\n\n  defp build_fallback({:function, plug}) do\n    quote do\n      case var!(conn_after) do\n        %Plug.Conn{} = conn_after -> conn_after\n        val -> unquote(plug)(var!(conn_before), val)\n      end\n    end\n  end\n\n  @doc false\n  def __catch__(\n        %Plug.Conn{},\n        :function_clause,\n        controller,\n        action,\n        [{controller, action, [%Plug.Conn{} | _] = action_args, _loc} | _] = stack\n      ) do\n    args = [module: controller, function: action, arity: length(action_args), args: action_args]\n    reraise Phoenix.ActionClauseError, args, stack\n  end\n\n  def __catch__(%Plug.Conn{} = conn, reason, _controller, _action, stack) do\n    Plug.Conn.WrapperError.reraise(conn, :error, reason, stack)\n  end\n\n  @doc \"\"\"\n  Stores a plug to be executed as part of the plug pipeline.\n  \"\"\"\n  defmacro plug(plug)\n\n  defmacro plug({:when, _, [plug, guards]}), do: plug(plug, [], guards, __CALLER__)\n\n  defmacro plug(plug), do: plug(plug, [], true, __CALLER__)\n\n  @doc \"\"\"\n  Stores a plug with the given options to be executed as part of\n  the plug pipeline.\n  \"\"\"\n  defmacro plug(plug, opts)\n\n  defmacro plug(plug, {:when, _, [opts, guards]}), do: plug(plug, opts, guards, __CALLER__)\n\n  defmacro plug(plug, opts), do: plug(plug, opts, true, __CALLER__)\n\n  defp plug(plug, opts, guards, caller) do\n    runtime? = Phoenix.plug_init_mode() == :runtime\n\n    plug =\n      if runtime? do\n        expand_alias(plug, caller)\n      else\n        plug\n      end\n\n    opts =\n      if runtime? and Macro.quoted_literal?(opts) do\n        Macro.prewalk(opts, &expand_alias(&1, caller))\n      else\n        opts\n      end\n\n    quote do\n      @plugs {unquote(plug), unquote(opts), unquote(escape_guards(guards))}\n    end\n  end\n\n  defp expand_alias({:__aliases__, _, _} = alias, env),\n    do: Macro.expand(alias, %{env | function: {:init, 1}})\n\n  defp expand_alias(other, _env), do: other\n\n  defp escape_guards({pre_expanded, _, [_ | _]} = node)\n       when pre_expanded in [:@, :__aliases__],\n       do: node\n\n  defp escape_guards({left, meta, right}),\n    do: {:{}, [], [escape_guards(left), meta, escape_guards(right)]}\n\n  defp escape_guards({left, right}),\n    do: {escape_guards(left), escape_guards(right)}\n\n  defp escape_guards([_ | _] = list),\n    do: Enum.map(list, &escape_guards/1)\n\n  defp escape_guards(node),\n    do: node\nend\n"
  },
  {
    "path": "lib/phoenix/controller.ex",
    "content": "defmodule Phoenix.Controller do\n  import Plug.Conn\n  alias Plug.Conn.AlreadySentError\n\n  require Logger\n\n  @unsent [:unset, :set, :set_chunked, :set_file]\n\n  # View/Layout deprecation plan\n  # 1. DONE! Deprecate :namespace option in favor of :layouts on use\n  # 2. Deprecate the :layouts option in use Phoenix.Controller\n  # 3. Deprecate setting a non-format view/layout on put_*\n  # 4. Deprecate rendering a view/layout from :_\n\n  @type view :: atom()\n  @type layout :: {module(), layout_name :: atom()} | false\n\n  @moduledoc \"\"\"\n  Controllers are used to group common functionality in the same\n  (pluggable) module.\n\n  For example, the route:\n\n      get \"/users/:id\", MyAppWeb.UserController, :show\n\n  will invoke the `show/2` action in the `MyAppWeb.UserController`:\n\n      defmodule MyAppWeb.UserController do\n        use MyAppWeb, :controller\n\n        def show(conn, %{\"id\" => id}) do\n          user = Repo.get(User, id)\n          render(conn, :show, user: user)\n        end\n      end\n\n  An action is a regular function that receives the connection\n  and the request parameters as arguments. The connection is a\n  `Plug.Conn` struct, as specified by the Plug library.\n\n  Then we invoke `render/3`, passing the connection, the template\n  to render (typically named after the action), and the `user: user`\n  as assigns. We will explore all of those concepts next.\n\n  ## Connection\n\n  A controller by default provides many convenience functions for\n  manipulating the connection, rendering templates, and more.\n\n  Those functions are imported from two modules:\n\n    * `Plug.Conn` - a collection of low-level functions to work with\n      the connection\n\n    * `Phoenix.Controller` - functions provided by Phoenix\n      to support rendering, and other Phoenix specific behaviour\n\n  If you want to have functions that manipulate the connection\n  without fully implementing the controller, you can import both\n  modules directly instead of `use Phoenix.Controller`.\n\n  ## Rendering\n\n  One of the main features provided by controllers is the ability\n  to perform content negotiation and render templates based on\n  information sent by the client.\n\n  There are two ways to render content in a controller. One option\n  is to invoke format-specific functions, such as `html/2` and `json/2`.\n\n  However, most commonly controllers invoke custom modules called\n  views. Views are modules capable of rendering a custom format.\n  This is done by specifying the option `:formats` when defining\n  the controller:\n\n      use Phoenix.Controller, formats: [:html, :json]\n\n   Now, when invoking `render/3`, a controller named `MyAppWeb.UserController`\n   will invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` respectively\n   when rendering each format:\n\n      def show(conn, %{\"id\" => id}) do\n        user = Repo.get(User, id)\n        # Will invoke UserHTML.show(%{user: user}) for html requests\n        # Will invoke UserJSON.show(%{user: user}) for json requests\n        render(conn, :show, user: user)\n      end\n\n  You can also specify formats to render by calling `put_view/2`\n  directly with a connection. For example, instead of inferring the\n  the view names from the controller, as done in:\n\n      use Phoenix.Controller, formats: [:html, :json]\n\n  You can write the above explicitly in your actions as:\n\n      put_view(conn, html: MyAppWeb.UserHTML, json: MyAppWeb.UserJSON)\n\n  Or as a plug:\n\n      plug :put_view, html: MyAppWeb.UserHTML, json: MyAppWeb.UserJSON\n\n  ## Layouts\n\n  Many applications have shared content that they want to include on every\n  page, most often the `<head>` tag and its contents. In Phoenix, this is\n  done via the `put_root_layout` function:\n\n      put_root_layout(conn, html: {MyAppWeb.Layouts, :root})\n\n  In most applications, this is invoked as a Plug in your application router:\n\n      plug :put_root_layout, html: {MyAppWeb.Layouts, :root}\n\n  This layout is shared by all controllers, and also by `Phoenix.LiveView`.\n\n  However, you can also specify controller-specific layouts using `put_layout/2`,\n  although this functionality is discouraged in Phoenix v1.8 in favor of using\n  function components to build your application.\n\n  ## Options\n\n  When used, the controller supports the following options to customize\n  template rendering:\n\n    * `:formats` - the formats this controller will render\n      by default. For example, specifying `formats: [:html, :json]`\n      for a controller named `MyAppWeb.UserController` will\n      invoke `MyAppWeb.UserHTML` and `MyAppWeb.UserJSON` when\n      respectively rendering each format.\n\n  The `:formats` option is required. You may set it to an empty list\n  if you don't expect to render any format upfront. To retain the\n  behaviour of older Phoenix versions, you can explicitly pass the\n  \"View\" suffix to the `:formats` option:\n\n      use Phoenix.Controller, formats: [html: \"View\", json: \"View\"]\n\n  ## Plug pipeline\n\n  As with routers, controllers also have their own plug pipeline.\n  However, different from routers, controllers have a single pipeline:\n\n      defmodule MyAppWeb.UserController do\n        use MyAppWeb, :controller\n\n        plug :authenticate, usernames: [\"jose\", \"eric\", \"sonny\"]\n\n        def show(conn, params) do\n          # authenticated users only\n        end\n\n        defp authenticate(conn, options) do\n          if get_session(conn, :username) in options[:usernames] do\n            conn\n          else\n            conn |> redirect(to: \"/\") |> halt()\n          end\n        end\n      end\n\n  The `:authenticate` plug will be invoked before the action. If the\n  plug calls `Plug.Conn.halt/1` (which is by default imported into\n  controllers), it will halt the pipeline and won't invoke the action.\n\n  ### Guards\n\n  `plug/2` in controllers supports guards, allowing a developer to configure\n  a plug to only run in some particular action.\n\n      plug :do_something when action in [:show, :edit]\n\n  Due to operator precedence in Elixir, if the second argument is a keyword list,\n  we need to wrap the keyword in `[...]` when using `when`:\n\n      plug :authenticate, [usernames: [\"jose\", \"eric\", \"sonny\"]] when action in [:show, :edit]\n      plug :authenticate, [usernames: [\"admin\"]] when not action in [:index]\n\n  The first plug will run only when action is show or edit. The second plug will\n  always run, except for the index action.\n\n  Those guards work like regular Elixir guards and the only variables accessible\n  in the guard are `conn`, the `action` as an atom and the `controller` as an\n  alias.\n\n  ## Controllers are plugs\n\n  Like routers, controllers are plugs, but they are wired to dispatch\n  to a particular function which is called an action.\n\n  For example, the route:\n\n      get \"/users/:id\", UserController, :show\n\n  will invoke `UserController` as a plug:\n\n      UserController.call(conn, :show)\n\n  which will trigger the plug pipeline and which will eventually\n  invoke the inner action plug that dispatches to the `show/2`\n  function in `UserController`.\n\n  As controllers are plugs, they implement both [`init/1`](`c:Plug.init/1`) and\n  [`call/2`](`c:Plug.call/2`), and it also provides a function named `action/2`\n  which is responsible for dispatching the appropriate action\n  after the plug stack (and is also overridable).\n\n  ### Overriding `action/2` for custom arguments\n\n  Phoenix injects an `action/2` plug in your controller which calls the\n  function matched from the router. By default, it passes the conn and params.\n  In some cases, overriding the `action/2` plug in your controller is a\n  useful way to inject arguments into your actions that you would otherwise\n  need to repeatedly fetch off the connection. For example, imagine if you\n  stored a `conn.assigns.current_user` in the connection and wanted quick\n  access to the user for every action in your controller:\n\n      def action(conn, _) do\n        args = [conn, conn.params, conn.assigns.current_user]\n        apply(__MODULE__, action_name(conn), args)\n      end\n\n      def index(conn, _params, user) do\n        videos = Repo.all(user_videos(user))\n        # ...\n      end\n\n      def delete(conn, %{\"id\" => id}, user) do\n        video = Repo.get!(user_videos(user), id)\n        # ...\n      end\n\n  \"\"\"\n  defmacro __using__(opts) do\n    opts =\n      if Macro.quoted_literal?(opts) do\n        Macro.prewalk(opts, &expand_alias(&1, __CALLER__))\n      else\n        opts\n      end\n\n    quote bind_quoted: [opts: opts] do\n      import Phoenix.Controller\n      import Plug.Conn\n\n      use Phoenix.Controller.Pipeline\n\n      with {layout, view} <- Phoenix.Controller.__plugs__(__MODULE__, opts) do\n        plug :put_new_layout, layout\n        plug :put_new_view, view\n      end\n    end\n  end\n\n  defp expand_alias({:__aliases__, _, _} = alias, env),\n    do: Macro.expand(alias, %{env | function: {:action, 2}})\n\n  defp expand_alias(other, _env), do: other\n\n  @doc \"\"\"\n  Registers the plug to call as a fallback to the controller action.\n\n  A fallback plug is useful to translate common domain data structures\n  into a valid `%Plug.Conn{}` response. If the controller action fails to\n  return a `%Plug.Conn{}`, the provided plug will be called and receive\n  the controller's `%Plug.Conn{}` as it was before the action was invoked\n  along with the value returned from the controller action.\n\n  ## Examples\n\n      defmodule MyController do\n        use Phoenix.Controller\n\n        action_fallback MyFallbackController\n\n        def show(conn, %{\"id\" => id}, current_user) do\n          with {:ok, post} <- Blog.fetch_post(id),\n               :ok <- Authorizer.authorize(current_user, :view, post) do\n\n            render(conn, \"show.json\", post: post)\n          end\n        end\n      end\n\n  In the above example, `with` is used to match only a successful\n  post fetch, followed by valid authorization for the current user.\n  In the event either of those fail to match, `with` will not invoke\n  the render block and instead return the unmatched value. In this case,\n  imagine `Blog.fetch_post/2` returned `{:error, :not_found}` or\n  `Authorizer.authorize/3` returned `{:error, :unauthorized}`. For cases\n  where these data structures serve as return values across multiple\n  boundaries in our domain, a single fallback module can be used to\n  translate the value into a valid response. For example, you could\n  write the following fallback controller to handle the above values:\n\n      defmodule MyFallbackController do\n        use Phoenix.Controller\n\n        def call(conn, {:error, :not_found}) do\n          conn\n          |> put_status(:not_found)\n          |> put_view(MyErrorView)\n          |> render(:\"404\")\n        end\n\n        def call(conn, {:error, :unauthorized}) do\n          conn\n          |> put_status(:forbidden)\n          |> put_view(MyErrorView)\n          |> render(:\"403\")\n        end\n      end\n  \"\"\"\n  defmacro action_fallback(plug) do\n    Phoenix.Controller.Pipeline.__action_fallback__(plug, __CALLER__)\n  end\n\n  @doc \"\"\"\n  Returns the action name as an atom, raises if unavailable.\n  \"\"\"\n  @spec action_name(Plug.Conn.t()) :: atom\n  def action_name(conn), do: conn.private.phoenix_action\n\n  @doc \"\"\"\n  Returns the controller module as an atom, raises if unavailable.\n  \"\"\"\n  @spec controller_module(Plug.Conn.t()) :: atom\n  def controller_module(conn), do: conn.private.phoenix_controller\n\n  @doc \"\"\"\n  Returns the router module as an atom, raises if unavailable.\n  \"\"\"\n  @spec router_module(Plug.Conn.t()) :: atom\n  def router_module(conn), do: conn.private.phoenix_router\n\n  @doc \"\"\"\n  Returns the endpoint module as an atom, raises if unavailable.\n  \"\"\"\n  @spec endpoint_module(Plug.Conn.t()) :: atom\n  def endpoint_module(conn), do: conn.private.phoenix_endpoint\n\n  @doc \"\"\"\n  Returns the template name rendered in the view as a string\n  (or nil if no template was rendered).\n  \"\"\"\n  @spec view_template(Plug.Conn.t()) :: binary | nil\n  def view_template(conn) do\n    conn.private[:phoenix_template]\n  end\n\n  @doc \"\"\"\n  Sends JSON response.\n\n  It uses the configured `:json_library` under the `:phoenix`\n  application for `:json` to pick up the encoder module.\n\n  ## Examples\n\n      iex> json(conn, %{id: 123})\n\n  \"\"\"\n  @spec json(Plug.Conn.t(), term) :: Plug.Conn.t()\n  def json(conn, data) do\n    response = Phoenix.json_library().encode_to_iodata!(data)\n    send_resp(conn, conn.status || 200, \"application/json\", response)\n  end\n\n  @doc \"\"\"\n  A plug that may convert a JSON response into a JSONP one.\n\n  In case a JSON response is returned, it will be converted\n  to a JSONP as long as the callback field is present in\n  the query string. The callback field itself defaults to\n  \"callback\", but may be configured with the callback option.\n\n  In case there is no callback or the response is not encoded\n  in JSON format, it is a no-op.\n\n  Only alphanumeric characters and underscore are allowed in the\n  callback name. Otherwise an exception is raised.\n\n  ## Examples\n\n      # Will convert JSON to JSONP if callback=someFunction is given\n      plug :allow_jsonp\n\n      # Will convert JSON to JSONP if cb=someFunction is given\n      plug :allow_jsonp, callback: \"cb\"\n\n  \"\"\"\n  @spec allow_jsonp(Plug.Conn.t(), Keyword.t()) :: Plug.Conn.t()\n  def allow_jsonp(conn, opts \\\\ []) do\n    callback = Keyword.get(opts, :callback, \"callback\")\n\n    case Map.fetch(conn.query_params, callback) do\n      :error ->\n        conn\n\n      {:ok, \"\"} ->\n        conn\n\n      {:ok, cb} ->\n        validate_jsonp_callback!(cb)\n\n        register_before_send(conn, fn conn ->\n          if json_response?(conn) do\n            conn\n            |> put_resp_header(\"content-type\", \"application/javascript\")\n            |> resp(conn.status, jsonp_body(conn.resp_body, cb))\n          else\n            conn\n          end\n        end)\n    end\n  end\n\n  defp json_response?(conn) do\n    case get_resp_header(conn, \"content-type\") do\n      [\"application/json;\" <> _] -> true\n      [\"application/json\"] -> true\n      _ -> false\n    end\n  end\n\n  defp jsonp_body(data, callback) do\n    body =\n      data\n      |> IO.iodata_to_binary()\n      |> String.replace(<<0x2028::utf8>>, \"\\\\u2028\")\n      |> String.replace(<<0x2029::utf8>>, \"\\\\u2029\")\n\n    \"/**/ typeof #{callback} === 'function' && #{callback}(#{body});\"\n  end\n\n  defp validate_jsonp_callback!(<<h, t::binary>>)\n       when h in ?0..?9 or h in ?A..?Z or h in ?a..?z or h == ?_,\n       do: validate_jsonp_callback!(t)\n\n  defp validate_jsonp_callback!(<<>>), do: :ok\n\n  defp validate_jsonp_callback!(_),\n    do: raise(ArgumentError, \"the JSONP callback name contains invalid characters\")\n\n  @doc \"\"\"\n  Sends text response.\n\n  ## Examples\n\n      iex> text(conn, \"hello\")\n\n      iex> text(conn, :implements_to_string)\n\n  \"\"\"\n  @spec text(Plug.Conn.t(), String.Chars.t()) :: Plug.Conn.t()\n  def text(conn, data) do\n    send_resp(conn, conn.status || 200, \"text/plain\", to_string(data))\n  end\n\n  @doc \"\"\"\n  Sends html response.\n\n  ## Examples\n\n      iex> html(conn, \"<html><head>...\")\n\n  \"\"\"\n  @spec html(Plug.Conn.t(), iodata) :: Plug.Conn.t()\n  def html(conn, data) do\n    send_resp(conn, conn.status || 200, \"text/html\", data)\n  end\n\n  @doc \"\"\"\n  Sends redirect response to the given url.\n\n  For security, `:to` only accepts paths. Use the `:external`\n  option to redirect to any URL.\n\n  The response will be sent with the status code defined within\n  the connection, via `Plug.Conn.put_status/2`. If no status\n  code is set, a 302 response is sent.\n\n  ## Examples\n\n      iex> redirect(conn, to: \"/login\")\n\n      iex> redirect(conn, external: \"https://elixir-lang.org\")\n\n  \"\"\"\n  def redirect(conn, opts) when is_list(opts) do\n    url = url(opts)\n    html = Plug.HTML.html_escape(url)\n    body = \"<html><body>You are being <a href=\\\"#{html}\\\">redirected</a>.</body></html>\"\n\n    conn\n    |> put_resp_header(\"location\", url)\n    |> send_resp(conn.status || 302, \"text/html\", body)\n  end\n\n  defp url(opts) do\n    cond do\n      to = opts[:to] -> validate_local_url(to)\n      external = opts[:external] -> external\n      true -> raise ArgumentError, \"expected :to or :external option in redirect/2\"\n    end\n  end\n\n  @invalid_local_url_chars [\"\\\\\", \"/%09\", \"/\\t\"]\n  defp validate_local_url(\"//\" <> _ = to), do: raise_invalid_url(to)\n\n  defp validate_local_url(\"/\" <> _ = to) do\n    if String.contains?(to, @invalid_local_url_chars) do\n      raise ArgumentError, \"unsafe characters detected for local redirect in URL #{inspect(to)}\"\n    else\n      to\n    end\n  end\n\n  defp validate_local_url(to), do: raise_invalid_url(to)\n\n  @spec raise_invalid_url(term()) :: no_return()\n  defp raise_invalid_url(url) do\n    raise ArgumentError, \"the :to option in redirect expects a path but was #{inspect(url)}\"\n  end\n\n  @doc \"\"\"\n  Stores the view for rendering.\n\n  Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.\n\n  ## Examples\n\n      iex> put_view(conn, html: AppHTML, json: AppJSON)\n\n  \"\"\"\n  @spec put_view(Plug.Conn.t(), [{format :: atom, view}] | view) :: Plug.Conn.t()\n  def put_view(%Plug.Conn{state: state} = conn, formats) when state in @unsent do\n    put_private_view(conn, :phoenix_view, :replace, formats)\n  end\n\n  def put_view(%Plug.Conn{} = conn, module) do\n    raise(AlreadySentError, \"\"\"\n    the response was already sent.\n\n        Status code: #{conn.status}\n        Request path: #{conn.request_path}\n        Method: #{conn.method}\n        View module: #{inspect(module)}\n    \"\"\")\n  end\n\n  defp put_private_view(conn, priv_key, kind, formats) when is_list(formats) do\n    formats = Enum.into(formats, %{}, fn {format, value} -> {to_string(format), value} end)\n    put_private_formats(conn, priv_key, kind, formats)\n  end\n\n  # TODO: Deprecate this whole branch on Phoenix v1.9\n  defp put_private_view(conn, priv_key, kind, value) do\n    put_private_formats(conn, priv_key, kind, %{_: value})\n  end\n\n  defp put_private_formats(conn, priv_key, kind, formats) when kind in [:new, :replace] do\n    update_in(conn.private, fn private ->\n      existing = Map.get(private, priv_key, %{})\n\n      new_formats =\n        case kind do\n          :new -> Map.merge(formats, existing)\n          :replace -> Map.merge(existing, formats)\n        end\n\n      Map.put(private, priv_key, new_formats)\n    end)\n  end\n\n  @doc \"\"\"\n  Stores the view for rendering if one was not stored yet.\n\n  Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.\n  \"\"\"\n  # TODO: Remove | view from the spec once we deprecate put_new_view on controllers on v1.9\n  @spec put_new_view(Plug.Conn.t(), [{format :: atom, view}] | view) :: Plug.Conn.t()\n  def put_new_view(%Plug.Conn{state: state} = conn, formats) when state in @unsent do\n    put_private_view(conn, :phoenix_view, :new, formats)\n  end\n\n  def put_new_view(%Plug.Conn{} = conn, module) do\n    raise(AlreadySentError, \"\"\"\n    the response was already sent.\n\n        Status code: #{conn.status}\n        Request path: #{conn.request_path}\n        Method: #{conn.method}\n        View module: #{inspect(module)}\n    \"\"\")\n  end\n\n  @doc \"\"\"\n  Retrieves the current view for the given format.\n\n  If no format is given, takes the current one from the connection.\n  \"\"\"\n  @spec view_module(Plug.Conn.t(), binary | nil) :: atom\n  def view_module(conn, format \\\\ nil) do\n    format = format || get_safe_format(conn)\n\n    # TODO: Remove the first branch once code paths are deprecated and then removed\n    case conn.private[:phoenix_view] do\n      %{_: value} when value != nil ->\n        value\n\n      %{^format => value} ->\n        value\n\n      formats ->\n        raise \"no view was found for the format: #{inspect(format)}. \" <>\n                \"The supported formats are: #{inspect(Map.keys(formats || %{}) -- [:_])}\"\n    end\n  end\n\n  @doc \"\"\"\n  Stores the layout for rendering.\n\n  The layout must be given as keyword list where the key is the request\n  format the layout will be applied to (such as `:html`) and the value\n  is one of:\n\n    * `{module, layout}` with the `module` the layout is defined and\n      the name of the `layout` as an atom\n\n    * `false` which disables the layout\n\n  If `false` is given without a format, all layouts are disabled.\n\n  ## Examples\n\n      iex> layout(conn)\n      false\n\n      iex> conn = put_layout(conn, html: {AppView, :application})\n      iex> layout(conn)\n      {AppView, :application}\n\n      iex> conn = put_layout(conn, html: {AppView, :print})\n      iex> layout(conn)\n      {AppView, :print}\n\n  Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.\n  \"\"\"\n  @spec put_layout(Plug.Conn.t(), [{format :: atom, layout}] | false) :: Plug.Conn.t()\n  def put_layout(%Plug.Conn{state: state} = conn, layout) do\n    if state in @unsent do\n      put_private_layout(conn, :phoenix_layout, :replace, layout)\n    else\n      raise AlreadySentError, \"\"\"\n      the response was already sent.\n\n          Status code: #{conn.status}\n          Request path: #{conn.request_path}\n          Method: #{conn.method}\n          Layout: #{inspect(layout)}\n      \"\"\"\n    end\n  end\n\n  defp put_private_layout(conn, private_key, kind, layouts) when is_list(layouts) do\n    formats =\n      Map.new(layouts, fn\n        {format, false} ->\n          {Atom.to_string(format), false}\n\n        {format, layout} when is_atom(layout) ->\n          format = Atom.to_string(format)\n\n          case conn.private[private_key] do\n            %{^format => {mod, _}} ->\n              IO.warn(\"\"\"\n              specifying a layout without module is deprecated, use #{format}: #{inspect({mod, layout})} instead\\\n              \"\"\")\n\n              {format, {mod, layout}}\n\n            %{} ->\n              raise \"cannot use put_layout/2 or put_root_layout/2 with atom because \" <>\n                      \"there is no previous layout set for format #{inspect(format)}\"\n          end\n\n        {format, {mod, layout}} when is_atom(mod) and is_atom(layout) ->\n          {Atom.to_string(format), {mod, layout}}\n\n        {format, other} ->\n          raise ArgumentError, \"\"\"\n          put_layout and put_root_layout expects an module and template per format, such as:\n\n              #{format}: {MyView, :app}\n\n          Got:\n\n              #{inspect(other)}\n          \"\"\"\n      end)\n\n    put_private_formats(conn, private_key, kind, formats)\n  end\n\n  defp put_private_layout(conn, private_key, kind, no_format) do\n    case no_format do\n      false ->\n        put_private_formats(conn, private_key, kind, %{_: false})\n\n      # TODO: Deprecate this branch on Phoenix v1.9\n      {mod, layout} when is_atom(mod) ->\n        put_private_formats(conn, private_key, kind, %{_: {mod, layout}})\n\n      layout when is_binary(layout) or is_atom(layout) ->\n        case Map.get(conn.private, private_key, %{_: false}) do\n          %{_: {mod, _}} ->\n            IO.warn(\"\"\"\n            specifying put_layout(conn, template) or put_new_layout(conn, template) is deprecated, \\\n            specify the layout with the format instead: put_layout(conn, html: #{inspect({mod, layout})})\n            \"\"\")\n\n            put_private_formats(conn, private_key, kind, %{_: {mod, layout}})\n\n          %{_: false} ->\n            raise \"cannot use put_layout/2 or put_root_layout/2 with atom/binary when layout is false, use a tuple instead\"\n\n          %{} ->\n            raise \"you must pass the format when using put_layout/2 or put_root_layout/2 and a previous format was set, \" <>\n                    \"such as: put_layout(conn, html: #{inspect(layout)})\"\n        end\n    end\n  end\n\n  @doc \"\"\"\n  Stores the layout for rendering if one was not stored yet.\n\n  See `put_layout/2` for more information.\n\n  Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.\n  \"\"\"\n  # TODO: Remove | layout from the spec once we deprecate put_new_layout on controllers\n  @spec put_new_layout(Plug.Conn.t(), [{format :: atom, layout}] | layout) :: Plug.Conn.t()\n  def put_new_layout(%Plug.Conn{state: state} = conn, layout)\n      when (is_tuple(layout) and tuple_size(layout) == 2) or is_list(layout) or layout == false do\n    unless state in @unsent do\n      raise(AlreadySentError, \"\"\"\n      the response was already sent.\n\n          Status code: #{conn.status}\n          Request path: #{conn.request_path}\n          Method: #{conn.method}\n          Layout: #{inspect(layout)}\n      \"\"\")\n    end\n\n    put_private_layout(conn, :phoenix_layout, :new, layout)\n  end\n\n  @doc \"\"\"\n  Stores the root layout for rendering.\n\n  The layout must be given as keyword list where the key is the request\n  format the layout will be applied to (such as `:html`) and the value\n  is one of:\n\n    * `{module, layout}` with the `module` the layout is defined and\n      the name of the `layout` as an atom\n\n    * `layout` when the name of the layout. This requires a layout for\n      the given format in the shape of `{module, layout}` to be previously\n      given\n\n    * `false` which disables the layout\n\n  ## Examples\n\n      iex> root_layout(conn)\n      false\n\n      iex> conn = put_root_layout(conn, html: {AppView, :root})\n      iex> root_layout(conn)\n      {AppView, :root}\n\n      iex> conn = put_root_layout(conn, html: :bare)\n      iex> root_layout(conn)\n      {AppView, :bare}\n\n  Raises `Plug.Conn.AlreadySentError` if `conn` is already sent.\n  \"\"\"\n  @spec put_root_layout(Plug.Conn.t(), [{format :: atom, layout}] | false) ::\n          Plug.Conn.t()\n  def put_root_layout(%Plug.Conn{state: state} = conn, layout) do\n    if state in @unsent do\n      put_private_layout(conn, :phoenix_root_layout, :replace, layout)\n    else\n      raise AlreadySentError, \"\"\"\n      the response was already sent.\n\n          Status code: #{conn.status}\n          Request path: #{conn.request_path}\n          Method: #{conn.method}\n          Layout: #{inspect(layout)}\n      \"\"\"\n    end\n  end\n\n  @doc false\n  @deprecated \"put_layout_formats/2 is deprecated, pass a keyword list to put_layout/put_root_layout instead\"\n  @spec put_layout_formats(Plug.Conn.t(), [String.t()]) :: Plug.Conn.t()\n  def put_layout_formats(%Plug.Conn{state: state} = conn, formats)\n      when state in @unsent and is_list(formats) do\n    put_private(conn, :phoenix_layout_formats, formats)\n  end\n\n  def put_layout_formats(%Plug.Conn{} = conn, _formats) do\n    raise(AlreadySentError, \"\"\"\n    the response was already sent.\n\n        Status code: #{conn.status}\n        Request path: #{conn.request_path}\n        Method: #{conn.method}\n    \"\"\")\n  end\n\n  @doc false\n  @deprecated \"layout_formats/1 is deprecated, pass a keyword list to put_layout/put_root_layout instead\"\n  @spec layout_formats(Plug.Conn.t()) :: [String.t()]\n  def layout_formats(conn) do\n    Map.get(conn.private, :phoenix_layout_formats, ~w(html))\n  end\n\n  @doc \"\"\"\n  Retrieves the current layout for the given format.\n\n  If no format is given, takes the current one from the connection.\n  \"\"\"\n  @spec layout(Plug.Conn.t(), binary | nil) :: {atom, String.t() | atom} | false\n  def layout(conn, format \\\\ nil) do\n    get_private_layout(conn, :phoenix_layout, format)\n  end\n\n  @doc \"\"\"\n  Retrieves the current root layout for the given format.\n\n  If no format is given, takes the current one from the connection.\n  \"\"\"\n  @spec root_layout(Plug.Conn.t(), binary | nil) :: {atom, String.t() | atom} | false\n  def root_layout(conn, format \\\\ nil) do\n    get_private_layout(conn, :phoenix_root_layout, format)\n  end\n\n  defp get_private_layout(conn, priv_key, format) do\n    format = format || get_safe_format(conn)\n\n    # TODO: Remove _ handling once layouts(false) is set to remove all formats\n    case conn.private[priv_key] do\n      %{_: value} -> if format in [nil | layout_formats(conn)], do: value, else: false\n      %{^format => value} -> value\n      _ -> false\n    end\n  end\n\n  @doc \"\"\"\n  Render the given template or the default template\n  specified by the current action with the given assigns.\n\n  See `render/3` for more information.\n  \"\"\"\n  @spec render(Plug.Conn.t(), Keyword.t() | map | binary | atom) :: Plug.Conn.t()\n  def render(conn, template_or_assigns \\\\ [])\n\n  def render(conn, template) when is_binary(template) or is_atom(template) do\n    render(conn, template, [])\n  end\n\n  def render(conn, assigns) do\n    render(conn, action_name(conn), assigns)\n  end\n\n  @doc \"\"\"\n  Renders the given `template` and `assigns` based on the `conn` information.\n\n  Once the template is rendered, the template format is set as the response\n  content type (for example, an HTML template will set \"text/html\" as response\n  content type) and the data is sent to the client with default status of 200.\n\n  ## Arguments\n\n    * `conn` - the `Plug.Conn` struct\n\n    * `template` - which may be an atom or a string. If an atom, like `:index`,\n      it will render a template with the same format as the one returned by\n      `get_format/1`. For example, for an HTML request, it will render\n      the \"index.html\" template. If the template is a string, it must contain\n      the extension too, like \"index.json\"\n\n    * `assigns` - a dictionary with the assigns to be used in the view. Those\n      assigns are merged and have higher precedence than the connection assigns\n      (`conn.assigns`)\n\n  ## Examples\n\n  To render a template, you must configure your controller with the formats\n  to render. You can do so on `use`, which will infer the modules based on\n  the controller name:\n\n      defmodule MyAppWeb.UserController do\n        # Will use MyAppWeb.UserHTML and MyAppWeb.UserJSON\n        use Phoenix.Controller, formats: [:html, :json]\n      end\n\n  With the formats set, you can render in two ways, either passing a string\n  with the template name and explicit format:\n\n      def show(conn, _params) do\n        render(conn, \"show.html\", message: \"Hello\")\n      end\n\n  The example above renders a template \"show.html\" from the `MyAppWeb.UserHTML`\n  and sets the response content type to \"text/html\".\n\n  Or, if you want the template format to be set dynamically based on the request,\n  you can pass an atom instead (without the extension):\n\n      def show(conn, _params) do\n        render(conn, :show, message: \"Hello\")\n      end\n\n  If the formats are not known at compile-time, you can call `put_view/2`\n  at runtime:\n\n      defmodule MyAppWeb.UserController do\n        use Phoenix.Controller\n\n        def show(conn, _params) do\n          conn\n          |> put_view(html: MyAppWeb.UserHTML)\n          |> render(\"show.html\", message: \"Hello\")\n        end\n      end\n\n  \"\"\"\n  @spec render(Plug.Conn.t(), binary | atom, Keyword.t() | map) :: Plug.Conn.t()\n  def render(conn, template, assigns)\n      when is_atom(template) and (is_map(assigns) or is_list(assigns)) do\n    format =\n      get_format(conn) ||\n        raise \"cannot render template #{inspect(template)} because conn.params[\\\"_format\\\"] is not set. \" <>\n                \"Please set `plug :accepts, ~w(html json ...)` in your pipeline.\"\n\n    render_and_send(conn, format, Atom.to_string(template), assigns)\n  end\n\n  def render(conn, template, assigns)\n      when is_binary(template) and (is_map(assigns) or is_list(assigns)) do\n    {base, format} = split_template(template)\n    conn |> put_format(format) |> render_and_send(format, base, assigns)\n  end\n\n  def render(conn, view, template)\n      when is_atom(view) and (is_binary(template) or is_atom(template)) do\n    IO.warn(\n      \"Phoenix.Controller.render/3 with a view is deprecated, see the documentation for render/3 for an alternative\"\n    )\n\n    render(conn, view, template, [])\n  end\n\n  @doc false\n  @deprecated \"render/4 is deprecated. Use put_view + render/3\"\n  def render(conn, view, template, assigns)\n      when is_atom(view) and (is_binary(template) or is_atom(template)) do\n    conn\n    |> put_view(view)\n    |> render(template, assigns)\n  end\n\n  defp render_and_send(conn, format, template, assigns) do\n    view = view_module(conn, format)\n    conn = prepare_assigns(conn, assigns, template, format)\n    data = render_with_layouts(conn, view, template, format)\n\n    conn\n    |> ensure_resp_content_type(MIME.type(format))\n    |> send_resp(conn.status || 200, data)\n  end\n\n  defp render_with_layouts(conn, view, template, format) do\n    render_assigns = Map.put(conn.assigns, :conn, conn)\n\n    case root_layout(conn, format) do\n      {layout_mod, layout_tpl} ->\n        {layout_base, _} = split_template(layout_tpl)\n        inner = template_render(view, template, format, render_assigns)\n        root_assigns = render_assigns |> Map.put(:inner_content, inner) |> Map.delete(:layout)\n        template_render_to_iodata(layout_mod, layout_base, format, root_assigns)\n\n      false ->\n        template_render_to_iodata(view, template, format, render_assigns)\n    end\n  end\n\n  defp template_render(view, template, format, assigns) do\n    metadata = %{view: view, template: template, format: format}\n\n    :telemetry.span([:phoenix, :controller, :render], metadata, fn ->\n      {Phoenix.Template.render(view, template, format, assigns), metadata}\n    end)\n  end\n\n  defp template_render_to_iodata(view, template, format, assigns) do\n    metadata = %{view: view, template: template, format: format}\n\n    :telemetry.span([:phoenix, :controller, :render], metadata, fn ->\n      {Phoenix.Template.render_to_iodata(view, template, format, assigns), metadata}\n    end)\n  end\n\n  defp prepare_assigns(conn, assigns, template, format) do\n    assigns = to_map(assigns)\n\n    layout =\n      case assigns_layout(conn, assigns, format) do\n        {mod, layout} when is_binary(layout) -> {mod, Path.rootname(layout)}\n        {mod, layout} when is_atom(layout) -> {mod, Atom.to_string(layout)}\n        false -> false\n      end\n\n    conn\n    |> put_private(:phoenix_template, template <> \".\" <> format)\n    |> Map.update!(:assigns, fn prev ->\n      prev\n      |> Map.merge(assigns)\n      |> Map.put(:layout, layout)\n    end)\n  end\n\n  defp assigns_layout(_conn, %{layout: layout}, _format), do: layout\n\n  defp assigns_layout(conn, _assigns, format) do\n    # TODO: Remove _ handling once layouts(false) is set to remove all formats\n    case conn.private[:phoenix_layout] do\n      %{^format => bad_value, _: good_value} when good_value != false ->\n        IO.warn(\"\"\"\n        conflicting layouts found. A layout has been set with format, such as:\n\n            put_layout(conn, #{format}: #{inspect(bad_value)})\n\n        But also without format:\n\n            put_layout(conn, #{inspect(good_value)})\n\n        In this case, the layout without format will always win.\n        Passing the layout without a format is currently soft-deprecated.\n        If you use layouts with formats, make sure that they are\n        used everywhere. Also remember to configure your controller\n        to use layouts with formats:\n\n            use Phoenix.Controller, layouts: [#{format}: #{inspect(bad_value)}]\n        \"\"\")\n\n        if format in layout_formats(conn), do: good_value, else: false\n\n      %{_: value} ->\n        if format in layout_formats(conn), do: value, else: false\n\n      %{^format => value} ->\n        value\n\n      _ ->\n        false\n    end\n  end\n\n  defp to_map(assigns) when is_map(assigns), do: assigns\n  defp to_map(assigns) when is_list(assigns), do: :maps.from_list(assigns)\n\n  defp split_template(name) when is_atom(name), do: {Atom.to_string(name), nil}\n\n  defp split_template(name) when is_binary(name) do\n    case :binary.split(name, \".\") do\n      [base, format] ->\n        {base, format}\n\n      [^name] ->\n        raise \"cannot render template #{inspect(name)} without format. Use an atom if the \" <>\n                \"template format is meant to be set dynamically based on the request format\"\n\n      [base | formats] ->\n        {base, List.last(formats)}\n    end\n  end\n\n  defp send_resp(conn, default_status, default_content_type, body) do\n    conn\n    |> ensure_resp_content_type(default_content_type)\n    |> send_resp(conn.status || default_status, body)\n  end\n\n  defp ensure_resp_content_type(%Plug.Conn{resp_headers: resp_headers} = conn, content_type) do\n    if List.keyfind(resp_headers, \"content-type\", 0) do\n      conn\n    else\n      content_type = content_type <> \"; charset=utf-8\"\n      %{conn | resp_headers: [{\"content-type\", content_type} | resp_headers]}\n    end\n  end\n\n  @doc \"\"\"\n  Puts the url string or `%URI{}` to be used for route generation.\n\n  This function overrides the default URL generation pulled\n  from the `%Plug.Conn{}`'s endpoint configuration.\n\n  ## Examples\n\n  Imagine your application is configured to run on \"example.com\"\n  but after the user signs in, you want all links to use\n  \"some_user.example.com\". You can do so by setting the proper\n  router url configuration:\n\n      def put_router_url_by_user(conn) do\n        put_router_url(conn, get_user_from_conn(conn).account_name <> \".example.com\")\n      end\n\n  Now when you call `Routes.some_route_url(conn, ...)`, it will use\n  the router url set above. Keep in mind that, if you want to generate\n  routes to the *current* domain, it is preferred to use\n  `Routes.some_route_path` helpers, as those are always relative.\n  \"\"\"\n  def put_router_url(conn, %URI{} = uri) do\n    put_private(conn, :phoenix_router_url, URI.to_string(uri))\n  end\n\n  def put_router_url(conn, url) when is_binary(url) do\n    put_private(conn, :phoenix_router_url, url)\n  end\n\n  @doc \"\"\"\n  Puts the URL or `%URI{}` to be used for the static url generation.\n\n  Using this function on a `%Plug.Conn{}` struct tells `static_url/2` to use\n  the given information for URL generation instead of the `%Plug.Conn{}`'s\n  endpoint configuration (much like `put_router_url/2` but for static URLs).\n  \"\"\"\n  def put_static_url(conn, %URI{} = uri) do\n    put_private(conn, :phoenix_static_url, URI.to_string(uri))\n  end\n\n  def put_static_url(conn, url) when is_binary(url) do\n    put_private(conn, :phoenix_static_url, url)\n  end\n\n  @doc \"\"\"\n  Puts the format in the connection.\n\n  This format is used when rendering a template as an atom.\n  For example, `render(conn, :foo)` will render `\"foo.FORMAT\"`\n  where the format is the one set here. The default format\n  is typically set from the negotiation done in `accepts/2`.\n\n  See `get_format/1` for retrieval.\n  \"\"\"\n  def put_format(conn, format), do: put_private(conn, :phoenix_format, to_string(format))\n\n  @doc \"\"\"\n  Returns the request format, such as \"json\", \"html\".\n\n  This format is used when rendering a template as an atom.\n  For example, `render(conn, :foo)` will render `\"foo.FORMAT\"`\n  where the format is the one set here. The default format\n  is typically set from the negotiation done in `accepts/2`.\n  \"\"\"\n  def get_format(conn) do\n    conn.private[:phoenix_format] || conn.params[\"_format\"]\n  end\n\n  defp get_safe_format(conn) do\n    conn.private[:phoenix_format] ||\n      case conn.params do\n        %{\"_format\" => format} -> format\n        %{} -> nil\n      end\n  end\n\n  @doc \"\"\"\n  Sends the given file or binary as a download.\n\n  The second argument must be `{:binary, contents}`, where\n  `contents` will be sent as download, or`{:file, path}`,\n  where `path` is the filesystem location of the file to\n  be sent. Be careful to not interpolate the path from\n  external parameters, as it could allow traversal of the\n  filesystem.\n\n  The download is achieved by setting \"content-disposition\"\n  to attachment. The \"content-type\" will also be set based\n  on the extension of the given filename but can be customized\n  via the `:content_type` and `:charset` options.\n\n  ## Options\n\n    * `:filename` - the filename to be presented to the user\n      as download\n    * `:content_type` - the content type of the file or binary\n      sent as download. It is automatically inferred from the\n      filename extension\n    * `:disposition` - specifies disposition type\n      (`:attachment` or `:inline`). If `:attachment` was used,\n      user will be prompted to save the file. If `:inline` was used,\n      the browser will attempt to open the file.\n      Defaults to `:attachment`.\n    * `:charset` - the charset of the file, such as \"utf-8\".\n      Defaults to none\n    * `:offset` - the bytes to offset when reading. Defaults to `0`\n    * `:length` - the total bytes to read. Defaults to `:all`\n    * `:encode` - encodes the filename using `URI.encode/2`.\n      Defaults to `true`. When `false`, disables encoding. If you\n      disable encoding, you need to guarantee there are no special\n      characters in the filename, such as quotes, newlines, etc.\n      Otherwise you can expose your application to security attacks\n\n  ## Examples\n\n  To send a file that is stored inside your application priv\n  directory:\n\n      path = Application.app_dir(:my_app, \"priv/prospectus.pdf\")\n      send_download(conn, {:file, path})\n\n  When using `{:file, path}`, the filename is inferred from the\n  given path but may also be set explicitly.\n\n  To allow the user to download contents that are in memory as\n  a binary or string:\n\n      send_download(conn, {:binary, \"world\"}, filename: \"hello.txt\")\n\n  See `Plug.Conn.send_file/3` and `Plug.Conn.send_resp/3` if you\n  would like to access the low-level functions used to send files\n  and responses via Plug.\n  \"\"\"\n  def send_download(conn, kind, opts \\\\ [])\n\n  def send_download(conn, {:file, path}, opts) do\n    filename = opts[:filename] || Path.basename(path)\n    offset = opts[:offset] || 0\n    length = opts[:length] || :all\n\n    conn\n    |> prepare_send_download(filename, opts)\n    |> send_file(conn.status || 200, path, offset, length)\n  end\n\n  def send_download(conn, {:binary, contents}, opts) do\n    filename =\n      opts[:filename] || raise \":filename option is required when sending binary download\"\n\n    conn\n    |> prepare_send_download(filename, opts)\n    |> send_resp(conn.status || 200, contents)\n  end\n\n  defp prepare_send_download(conn, filename, opts) do\n    content_type = opts[:content_type] || MIME.from_path(filename)\n    encoded_filename = encode_filename(filename, Keyword.get(opts, :encode, true))\n    disposition_type = get_disposition_type(Keyword.get(opts, :disposition, :attachment))\n    warn_if_ajax(conn)\n\n    disposition = ~s[#{disposition_type}; filename=\"#{encoded_filename}\"]\n\n    disposition =\n      if encoded_filename != filename do\n        disposition <> \"; filename*=utf-8''#{encoded_filename}\"\n      else\n        disposition\n      end\n\n    conn\n    |> put_resp_content_type(content_type, opts[:charset])\n    |> put_resp_header(\"content-disposition\", disposition)\n  end\n\n  defp encode_filename(filename, false), do: filename\n  defp encode_filename(filename, true), do: URI.encode(filename, &URI.char_unreserved?/1)\n\n  defp get_disposition_type(:attachment), do: \"attachment\"\n  defp get_disposition_type(:inline), do: \"inline\"\n\n  defp get_disposition_type(other),\n    do:\n      raise(\n        ArgumentError,\n        \"expected :disposition to be :attachment or :inline, got: #{inspect(other)}\"\n      )\n\n  defp ajax?(conn) do\n    case get_req_header(conn, \"x-requested-with\") do\n      [value] -> value in [\"XMLHttpRequest\", \"xmlhttprequest\"]\n      [] -> false\n    end\n  end\n\n  defp warn_if_ajax(conn) do\n    if ajax?(conn) do\n      Logger.warning(\n        \"send_download/3 has been invoked during an AJAX request. \" <>\n          \"The download may not work as expected under XMLHttpRequest\"\n      )\n    end\n  end\n\n  @doc \"\"\"\n  Scrubs the parameters from the request.\n\n  This process is two-fold:\n\n    * Checks to see if the `required_key` is present\n    * Changes empty parameters of `required_key` (recursively) to nils\n\n  This function is useful for removing empty strings sent\n  via HTML forms. If you are providing an API, there\n  is likely no need to invoke `scrub_params/2`.\n\n  If the `required_key` is not present, it will\n  raise `Phoenix.MissingParamError`.\n\n  ## Examples\n\n      iex> scrub_params(conn, \"user\")\n\n  \"\"\"\n  @spec scrub_params(Plug.Conn.t(), String.t()) :: Plug.Conn.t()\n  def scrub_params(%Plug.Conn{} = conn, required_key) when is_binary(required_key) do\n    param = Map.get(conn.params, required_key) |> scrub_param()\n\n    unless param do\n      raise Phoenix.MissingParamError, key: required_key\n    end\n\n    params = Map.put(conn.params, required_key, param)\n    %{conn | params: params}\n  end\n\n  defp scrub_param(%{__struct__: mod} = struct) when is_atom(mod) do\n    struct\n  end\n\n  defp scrub_param(%{} = param) do\n    Enum.reduce(param, %{}, fn {k, v}, acc ->\n      Map.put(acc, k, scrub_param(v))\n    end)\n  end\n\n  defp scrub_param(param) when is_list(param) do\n    Enum.map(param, &scrub_param/1)\n  end\n\n  defp scrub_param(param) do\n    if scrub?(param), do: nil, else: param\n  end\n\n  defp scrub?(\" \" <> rest), do: scrub?(rest)\n  defp scrub?(\"\"), do: true\n  defp scrub?(_), do: false\n\n  @doc \"\"\"\n  Enables CSRF protection.\n\n  Currently used as a wrapper function for `Plug.CSRFProtection`\n  and mainly serves as a function plug in `YourApp.Router`.\n\n  Check `get_csrf_token/0` and `delete_csrf_token/0` for\n  retrieving and deleting CSRF tokens.\n  \"\"\"\n  def protect_from_forgery(conn, opts \\\\ []) do\n    Plug.CSRFProtection.call(conn, Plug.CSRFProtection.init(opts))\n  end\n\n  @doc \"\"\"\n  Put headers that improve browser security.\n\n  It sets the following headers, if they are not already set:\n\n    * `content-security-policy` - It sets `frame-ancestors` and\n      `base-uri` to `self`, restricting embedding and the use of\n      `<base>` element to same origin respectively. It is equivalent\n      to setting `\"base-uri 'self'; frame-ancestors 'self';\"`\n\n    * `referrer-policy` - only send origin on cross origin requests\n\n    * `x-content-type-options` - set to nosniff. This requires\n      script and style tags to be sent with proper content type\n\n    * `x-permitted-cross-domain-policies` - set to none to restrict\n      Adobe Flash Player’s access to data\n\n  A custom headers map may also be given to be merged with defaults.\n\n  It is recommended for custom header keys to be in lowercase, to avoid sending\n  duplicate keys or invalid responses.\n  \"\"\"\n  def put_secure_browser_headers(conn, headers \\\\ %{})\n\n  def put_secure_browser_headers(conn, []) do\n    put_secure_defaults(conn)\n  end\n\n  def put_secure_browser_headers(conn, headers) when is_map(headers) do\n    conn\n    |> put_secure_defaults()\n    |> merge_resp_headers(headers)\n  end\n\n  defp put_secure_defaults(%Plug.Conn{resp_headers: resp_headers} = conn) do\n    headers = [\n      {\"referrer-policy\", \"strict-origin-when-cross-origin\"},\n      {\"content-security-policy\", \"base-uri 'self'; frame-ancestors 'self';\"},\n      {\"x-content-type-options\", \"nosniff\"},\n      {\"x-permitted-cross-domain-policies\", \"none\"}\n    ]\n\n    resp_headers =\n      Enum.reduce(headers, resp_headers, fn {key, _} = pair, acc ->\n        case :lists.keymember(key, 1, acc) do\n          true -> acc\n          false -> [pair | acc]\n        end\n      end)\n\n    %{conn | resp_headers: resp_headers}\n  end\n\n  @doc \"\"\"\n  Gets or generates a CSRF token.\n\n  If a token exists, it is returned, otherwise it is generated and stored\n  in the process dictionary.\n  \"\"\"\n  defdelegate get_csrf_token(), to: Plug.CSRFProtection\n\n  @doc \"\"\"\n  Deletes the CSRF token from the process dictionary.\n\n  *Note*: The token is deleted only after a response has been sent.\n  \"\"\"\n  defdelegate delete_csrf_token(), to: Plug.CSRFProtection\n\n  @doc \"\"\"\n  Performs content negotiation based on the available formats.\n\n  It receives a connection, a list of formats that the server\n  is capable of rendering and then proceeds to perform content\n  negotiation based on the request information. If the client\n  accepts any of the given formats, the request proceeds.\n\n  If the request contains a \"_format\" parameter, it is\n  considered to be the format desired by the client. If no\n  \"_format\" parameter is available, this function will parse\n  the \"accept\" header and find a matching format accordingly.\n\n  This function is useful when you may want to serve different\n  content-types (such as JSON and HTML) from the same routes.\n  However, if you always have distinct routes, you can also\n  disable content negotiation and simply hardcode your format\n  of choice in your route pipelines:\n\n      plug :put_format, \"html\"\n\n  It is important to notice that browsers have historically\n  sent bad accept headers. For this reason, this function will\n  default to \"html\" format whenever:\n\n    * the accepted list of arguments contains the \"html\" format\n\n    * the accept header specified more than one media type preceded\n      or followed by the wildcard media type \"`*/*`\"\n\n  This function raises `Phoenix.NotAcceptableError`, which is rendered\n  with status 406, whenever the server cannot serve a response in any\n  of the formats expected by the client.\n\n  ## Examples\n\n  `accepts/2` can be invoked as a function:\n\n      iex> accepts(conn, [\"html\", \"json\"])\n\n  or used as a plug:\n\n      plug :accepts, [\"html\", \"json\"]\n      plug :accepts, ~w(html json)\n\n  ## Custom media types\n\n  It is possible to add custom media types to your Phoenix application.\n  The first step is to teach Plug about those new media types in\n  your `config/config.exs` file:\n\n      config :mime, :types, %{\n        \"application/vnd.api+json\" => [\"json-api\"]\n      }\n\n  The key is the media type, the value is a list of formats the\n  media type can be identified with. For example, by using\n  \"json-api\", you will be able to use templates with extension\n  \"index.json-api\" or to force a particular format in a given\n  URL by sending \"?_format=json-api\".\n\n  After this change, you must recompile plug:\n\n      $ mix deps.clean mime --build\n      $ mix deps.get\n\n  And now you can use it in accepts too:\n\n      plug :accepts, [\"html\", \"json-api\"]\n\n  \"\"\"\n  @spec accepts(Plug.Conn.t(), [binary]) :: Plug.Conn.t()\n  def accepts(conn, [_ | _] = accepted) do\n    case conn.params do\n      %{\"_format\" => format} ->\n        handle_params_accept(conn, format, accepted)\n\n      %{} ->\n        handle_header_accept(conn, get_req_header(conn, \"accept\"), accepted)\n    end\n  end\n\n  defp handle_params_accept(conn, format, accepted) do\n    if format in accepted do\n      put_format(conn, format)\n    else\n      raise Phoenix.NotAcceptableError,\n        message: \"unknown format #{inspect(format)}, expected one of #{inspect(accepted)}\",\n        accepts: accepted\n    end\n  end\n\n  # In case there is no accept header or the header is */*\n  # we use the first format specified in the accepts list.\n  defp handle_header_accept(conn, header, [first | _]) when header == [] or header == [\"*/*\"] do\n    put_format(conn, first)\n  end\n\n  # In case there is a header, we need to parse it.\n  # But before we check for */* because if one exists and we serve html,\n  # we unfortunately need to assume it is a browser sending us a request.\n  defp handle_header_accept(conn, [header | _], accepted) do\n    if header =~ \"*/*\" and \"html\" in accepted do\n      put_format(conn, \"html\")\n    else\n      parse_header_accept(conn, String.split(header, \",\"), [], accepted)\n    end\n  end\n\n  defp parse_header_accept(conn, [h | t], acc, accepted) do\n    case Plug.Conn.Utils.media_type(h) do\n      {:ok, type, subtype, args} ->\n        exts = parse_exts(type, subtype)\n        q = parse_q(args)\n\n        if format = q === 1.0 && find_format(exts, accepted) do\n          put_format(conn, format)\n        else\n          parse_header_accept(conn, t, [{-q, h, exts} | acc], accepted)\n        end\n\n      :error ->\n        parse_header_accept(conn, t, acc, accepted)\n    end\n  end\n\n  defp parse_header_accept(conn, [], acc, accepted) do\n    acc\n    |> Enum.sort()\n    |> Enum.find_value(&parse_header_accept(conn, &1, accepted))\n    |> Kernel.||(refuse(conn, acc, accepted))\n  end\n\n  defp parse_header_accept(conn, {_, _, exts}, accepted) do\n    if format = find_format(exts, accepted) do\n      put_format(conn, format)\n    end\n  end\n\n  defp parse_q(args) do\n    case Map.fetch(args, \"q\") do\n      {:ok, float} ->\n        case Float.parse(float) do\n          {float, _} -> float\n          :error -> 1.0\n        end\n\n      :error ->\n        1.0\n    end\n  end\n\n  defp parse_exts(\"*\", \"*\"), do: \"*/*\"\n  defp parse_exts(type, \"*\"), do: type\n  defp parse_exts(type, subtype), do: MIME.extensions(type <> \"/\" <> subtype)\n\n  defp find_format(\"*/*\", accepted), do: Enum.fetch!(accepted, 0)\n  defp find_format(exts, accepted) when is_list(exts), do: Enum.find(exts, &(&1 in accepted))\n  defp find_format(_type_range, []), do: nil\n\n  defp find_format(type_range, [h | t]) do\n    mime_type = MIME.type(h)\n\n    case Plug.Conn.Utils.media_type(mime_type) do\n      {:ok, accepted_type, _subtype, _args} when type_range === accepted_type -> h\n      _ -> find_format(type_range, t)\n    end\n  end\n\n  @spec refuse(term(), [tuple], [binary]) :: no_return()\n  defp refuse(_conn, given, accepted) do\n    raise Phoenix.NotAcceptableError,\n      accepts: accepted,\n      message: \"\"\"\n      no supported media type in accept header.\n\n      Expected one of #{inspect(accepted)} but got the following formats:\n\n        * #{Enum.map_join(given, \"\\n  \", fn {_, header, exts} -> inspect(header) <> \" with extensions: \" <> inspect(exts) end)}\n\n      To accept custom formats, register them under the :mime library\n      in your config/config.exs file:\n\n          config :mime, :types, %{\n            \"application/xml\" => [\"xml\"]\n          }\n\n      And then run `mix deps.clean --build mime` to force it to be recompiled.\n      \"\"\"\n  end\n\n  @doc \"\"\"\n  Fetches the flash storage.\n  \"\"\"\n  def fetch_flash(conn, _opts \\\\ []) do\n    if Map.get(conn.assigns, :flash) do\n      conn\n    else\n      session_flash = get_session(conn, \"phoenix_flash\")\n      conn = persist_flash(conn, session_flash || %{})\n\n      register_before_send(conn, fn conn ->\n        flash = conn.assigns.flash\n        flash_size = map_size(flash)\n\n        cond do\n          is_nil(session_flash) and flash_size == 0 ->\n            conn\n\n          flash_size > 0 and conn.status in 300..308 ->\n            put_session(conn, \"phoenix_flash\", flash)\n\n          true ->\n            delete_session(conn, \"phoenix_flash\")\n        end\n      end)\n    end\n  end\n\n  @doc \"\"\"\n  Merges a map into the flash.\n\n  Returns the updated connection.\n\n  ## Examples\n\n      iex> conn = merge_flash(conn, info: \"Welcome Back!\")\n      iex> Phoenix.Flash.get(conn.assigns.flash, :info)\n      \"Welcome Back!\"\n\n  \"\"\"\n  def merge_flash(conn, enumerable) do\n    map = for {k, v} <- enumerable, into: %{}, do: {flash_key(k), v}\n    persist_flash(conn, Map.merge(Map.get(conn.assigns, :flash, %{}), map))\n  end\n\n  @doc \"\"\"\n  Persists a value in flash.\n\n  `key` can be any atom or binary value. Phoenix does not enforce which keys\n  are stored in the flash, as long as the values are internally consistent.\n  By default, the Phoenix generators use `:info` and `:error` keys.\n\n  Returns the updated connection.\n\n  ## Examples\n\n      iex> conn = put_flash(conn, :info, \"Welcome Back!\")\n      iex> Phoenix.Flash.get(conn.assigns.flash, :info)\n      \"Welcome Back!\"\n\n  \"\"\"\n  def put_flash(conn, key, message) do\n    flash =\n      Map.get(conn.assigns, :flash) ||\n        raise ArgumentError, message: \"flash not fetched, call fetch_flash/2\"\n\n    persist_flash(conn, Map.put(flash, flash_key(key), message))\n  end\n\n  @doc \"\"\"\n  Returns a map of previously set flash messages or an empty map.\n\n  ## Examples\n\n      iex> get_flash(conn)\n      %{}\n\n      iex> conn = put_flash(conn, :info, \"Welcome Back!\")\n      iex> get_flash(conn)\n      %{\"info\" => \"Welcome Back!\"}\n\n  \"\"\"\n  @deprecated \"get_flash/1 is deprecated. Use the @flash assign provided by the :fetch_flash plug\"\n  def get_flash(conn) do\n    Map.get(conn.assigns, :flash) ||\n      raise ArgumentError, message: \"flash not fetched, call fetch_flash/2\"\n  end\n\n  @doc \"\"\"\n  Returns a message from flash by `key` (or `nil` if no message is available for `key`).\n\n  ## Examples\n\n      iex> conn = put_flash(conn, :info, \"Welcome Back!\")\n      iex> get_flash(conn, :info)\n      \"Welcome Back!\"\n\n  \"\"\"\n  @deprecated \"get_flash/2 is deprecated. Use Phoenix.Flash.get(@flash, key) instead\"\n  def get_flash(conn, key) do\n    get_flash(conn)[flash_key(key)]\n  end\n\n  @doc \"\"\"\n  Generates a status message from the template name.\n\n  ## Examples\n\n      iex> status_message_from_template(\"404.html\")\n      \"Not Found\"\n      iex> status_message_from_template(\"whatever.html\")\n      \"Internal Server Error\"\n\n  \"\"\"\n  def status_message_from_template(template) do\n    template\n    |> String.split(\".\")\n    |> hd()\n    |> String.to_integer()\n    |> Plug.Conn.Status.reason_phrase()\n  rescue\n    _ -> \"Internal Server Error\"\n  end\n\n  @doc \"\"\"\n  Clears all flash messages.\n  \"\"\"\n  def clear_flash(conn) do\n    persist_flash(conn, %{})\n  end\n\n  defp flash_key(binary) when is_binary(binary), do: binary\n  defp flash_key(atom) when is_atom(atom), do: Atom.to_string(atom)\n\n  defp persist_flash(conn, value) do\n    assign(conn, :flash, value)\n  end\n\n  @doc \"\"\"\n  Returns the current request path with its default query parameters:\n\n      iex> current_path(conn)\n      \"/users/123?existing=param\"\n\n  See `current_path/2` to override the default parameters.\n\n  The path is normalized based on the `conn.script_name` and\n  `conn.path_info`. For example, \"/foo//bar/\" will become \"/foo/bar\".\n  If you want the original path, use `conn.request_path` instead.\n  \"\"\"\n  def current_path(%Plug.Conn{query_string: \"\"} = conn) do\n    normalized_request_path(conn)\n  end\n\n  def current_path(%Plug.Conn{query_string: query_string} = conn) do\n    normalized_request_path(conn) <> \"?\" <> query_string\n  end\n\n  @doc \"\"\"\n  Returns the current path with the given query parameters.\n\n  You may also retrieve only the request path by passing an\n  empty map of params.\n\n  ## Examples\n\n      iex> current_path(conn)\n      \"/users/123?existing=param\"\n\n      iex> current_path(conn, %{new: \"param\"})\n      \"/users/123?new=param\"\n\n      iex> current_path(conn, %{filter: %{status: [\"draft\", \"published\"]}})\n      \"/users/123?filter[status][]=draft&filter[status][]=published\"\n\n      iex> current_path(conn, %{})\n      \"/users/123\"\n\n  The path is normalized based on the `conn.script_name` and\n  `conn.path_info`. For example, \"/foo//bar/\" will become \"/foo/bar\".\n  If you want the original path, use `conn.request_path` instead.\n  \"\"\"\n  def current_path(%Plug.Conn{} = conn, params) when params == %{} do\n    normalized_request_path(conn)\n  end\n\n  def current_path(%Plug.Conn{} = conn, params) do\n    normalized_request_path(conn) <> \"?\" <> Plug.Conn.Query.encode(params)\n  end\n\n  defp normalized_request_path(%{path_info: info, script_name: script}) do\n    \"/\" <> Enum.join(script ++ info, \"/\")\n  end\n\n  @doc \"\"\"\n  Returns the current request url with its default query parameters:\n\n      iex> current_url(conn)\n      \"https://www.example.com/users/123?existing=param\"\n\n  See `current_url/2` to override the default parameters.\n  \"\"\"\n  def current_url(%Plug.Conn{} = conn) do\n    Phoenix.VerifiedRoutes.unverified_url(conn, current_path(conn))\n  end\n\n  @doc ~S\"\"\"\n  Returns the current request URL with query params.\n\n  The path will be retrieved from the currently requested path via\n  `current_path/1`. The scheme, host and others will be received from\n  the URL configuration in your Phoenix endpoint. The reason we don't\n  use the host and scheme information in the request is because most\n  applications are behind proxies and the host and scheme may not\n  actually reflect the host and scheme accessed by the client. If you\n  want to access the url precisely as requested by the client, see\n  `Plug.Conn.request_url/1`.\n\n  ## Examples\n\n      iex> current_url(conn)\n      \"https://www.example.com/users/123?existing=param\"\n\n      iex> current_url(conn, %{new: \"param\"})\n      \"https://www.example.com/users/123?new=param\"\n\n      iex> current_url(conn, %{})\n      \"https://www.example.com/users/123\"\n\n  ## Custom URL Generation\n\n  In some cases, you'll need to generate a request's URL, but using a\n  different scheme, different host, etc. This can be accomplished in\n  two ways.\n\n  If you want to do so in a case-by-case basis, you can define a custom\n  function that gets the endpoint URI configuration and changes it accordingly.\n  For example, to get the current URL always in HTTPS format:\n\n      def current_secure_url(conn, params \\\\ %{}) do\n        current_uri = MyAppWeb.Endpoint.struct_url()\n        current_path = Phoenix.Controller.current_path(conn, params)\n        Phoenix.VerifiedRoutes.unverified_url(%URI{current_uri | scheme: \"https\"}, current_path)\n      end\n\n  However, if you want all generated URLs to always have a certain schema,\n  host, etc, you may use `put_router_url/2`.\n  \"\"\"\n  def current_url(%Plug.Conn{} = conn, %{} = params) do\n    Phoenix.VerifiedRoutes.unverified_url(conn, current_path(conn, params))\n  end\n\n  @doc \"\"\"\n  Assigns multiple key-value pairs to the connection.\n  Accepts a keyword list, a map, or a single-argument function.\n\n  This function accepts a map or keyword list of assigns and merges them into\n  the connection's assigns. It is equivalent to calling `Plug.Conn.assign/3`\n  multiple times.\n\n  If a function is given, it takes the current assigns as an argument and its return\n  value will be merged into the current assigns.\n\n  ## Examples\n\n      assign(conn, name: \"Alice\", role: :admin)\n      assign(conn, %{name: \"Alice\", role: :admin})\n      assign(conn, fn %{name: name, logo: logo} -> %{title: Enum.join([name, logo], \" | \")} end)\n  \"\"\"\n  def assign(conn, keyword_or_map_or_fun)\n\n  def assign(conn, fun) when is_function(fun, 1) do\n    assign(conn, fun.(conn.assigns))\n  end\n\n  defdelegate assign(conn, assigns), to: Plug.Conn, as: :merge_assigns\n\n  @doc false\n  def __plugs__(controller_module, opts) do\n    if Keyword.get(opts, :put_default_views, true) do\n      base = Phoenix.Naming.unsuffix(controller_module, \"Controller\")\n\n      view =\n        case Keyword.fetch(opts, :formats) do\n          {:ok, formats} when is_list(formats) ->\n            Enum.map(formats, fn\n              format when is_atom(format) ->\n                {format, :\"#{base}#{String.upcase(to_string(format))}\"}\n\n              {format, suffix} ->\n                {format, :\"#{base}#{suffix}\"}\n            end)\n\n          :error ->\n            IO.warn(\n              \"\"\"\n              use #{inspect(controller_module)} must receive the :formats option with \\\n              the formats you intend to render. To keep compatibility within your app, \\\n              you can list it as:\n\n                  formats: [html: \"View\", json: \"View\", ...]\n\n              Listing all formats your application renders.\n              \"\"\",\n              []\n            )\n\n            :\"#{base}View\"\n        end\n\n      layouts =\n        case Keyword.fetch(opts, :layouts) do\n          {:ok, formats} when is_list(formats) ->\n            # TODO: Deprecate passing :layouts altogether in Phoenix v1.9,\n            # use Phoenix.Controller should only set views\n            Enum.map(formats, fn\n              {format, mod} when is_atom(mod) ->\n                {format, {mod, :app}}\n\n              {format, {mod, template}} when is_atom(mod) and is_atom(template) ->\n                {format, {mod, template}}\n\n              other ->\n                raise ArgumentError, \"\"\"\n                expected :layouts to be a list of format module pairs of the form: [html: DemoWeb.Layouts] or [html: {DemoWeb.Layouts, :app}]\n\n                Got: #{inspect(other)}\n                \"\"\"\n            end)\n\n          :error ->\n            cond do\n              namespace = Keyword.get(opts, :namespace) ->\n                layout = Module.concat(namespace, \"LayoutView\")\n\n                IO.warn(\n                  \"\"\"\n                  the :namespace option given to #{inspect(controller_module)} is deprecated.\n                  Set \"plug :put_layout, html: #{inspect(layout)}\" instead\\\n                  \"\"\",\n                  []\n                )\n\n                {layout, :app}\n\n              Keyword.has_key?(opts, :formats) ->\n                []\n\n              true ->\n                layout =\n                  controller_module\n                  |> Atom.to_string()\n                  |> String.split(\".\")\n                  |> Enum.drop(-1)\n                  |> Enum.take(2)\n                  |> Kernel.++([\"LayoutView\"])\n                  |> Module.concat()\n\n                {layout, :app}\n            end\n        end\n\n      {layouts, view}\n    else\n      IO.warn(\n        \"\"\"\n        the :put_default_views option given to #{inspect(controller_module)} is deprecated.\n        Set formats: [] instead\\\n        \"\"\",\n        []\n      )\n\n      false\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/debug.ex",
    "content": "defmodule Phoenix.Debug do\n  @moduledoc \"\"\"\n  Functions for runtime introspection and debugging of Phoenix applications.\n\n  This module provides utilities for inspecting and debugging Phoenix applications.\n  At the moment, it only includes functions related to `Phoenix.Socket` and `Phoenix.Channel`\n  processes.\n\n  It allows you to:\n\n    * List all currently connected `Phoenix.Socket` transport processes.\n    * List all channels for a given `Phoenix.Socket` process.\n    * Get the socket of a channel process.\n    * Check if a process is a `Phoenix.Socket` or `Phoenix.Channel`.\n\n  \"\"\"\n\n  @doc \"\"\"\n  Returns a list of all currently connected `Phoenix.Socket` transport processes.\n\n  Note that custom sockets implementing the `Phoenix.Socket.Transport` behaviour\n  are not listed.\n\n  Each process corresponds to one connection that can have multiple channels.\n\n  For example, when using Phoenix LiveView, the browser establishes a socket\n  connection when initially navigating to the page, and each live navigation\n  retains the same socket connection. Nested LiveViews also share the same\n  connection, each being a different channel. See `Phoenix.Debug.channels/1`.\n\n  ## Examples\n\n      iex> Phoenix.Debug.list_sockets()\n      [%{pid: #PID<0.123.0>, module: Phoenix.LiveView.Socket, id: nil}]\n\n  \"\"\"\n  def list_sockets do\n    for pid <- Process.list(), dict = socket_process_dict(pid), not is_nil(dict) do\n      {Phoenix.Socket, mod, id} = keyfind(dict, :\"$process_label\")\n      %{pid: pid, module: mod, id: id}\n    end\n  end\n\n  defp keyfind(list, key) do\n    case List.keyfind(list, key, 0) do\n      {^key, value} -> value\n      _ -> nil\n    end\n  end\n\n  defp socket_process_dict(pid) do\n    # Phoenix.Socket sets the \"$process_label\" to {Phoenix.Socket, handler_module, id}\n    with info when is_list(info) <- Process.info(pid, [:dictionary]),\n         dictionary when not is_nil(dictionary) <- keyfind(info, :dictionary),\n         label when not is_nil(label) <- keyfind(dictionary, :\"$process_label\"),\n         {Phoenix.Socket, mod, id} when is_atom(mod) and (is_binary(id) or is_nil(id)) <- label do\n      dictionary\n    else\n      _ -> nil\n    end\n  end\n\n  @doc \"\"\"\n  Returns true if the given pid is a `Phoenix.Socket` transport process.\n\n  It returns `false` for custom sockets implementing the `Phoenix.Socket.Transport` behaviour.\n\n  ## Examples\n\n      iex> Phoenix.Debug.list_sockets() |> Enum.at(0) |> Map.fetch!(:pid) |> socket_process?()\n      true\n\n      iex> socket_process?(pid(0,456,0))\n      false\n\n  \"\"\"\n  def socket_process?(pid) do\n    not is_nil(socket_process_dict(pid))\n  end\n\n  @doc \"\"\"\n  Checks if the given pid is a `Phoenix.Channel` process.\n\n  Note: this function returns false for [custom channels](https://hexdocs.pm/phoenix/Phoenix.Socket.html#module-custom-channels).\n  \"\"\"\n  def channel_process?(pid) do\n    # Phoenix.Channel sets the \"$process_label\" to {Phoenix.Socket, handler_module, id}\n    with info when is_list(info) <- Process.info(pid, [:dictionary]),\n         dictionary when not is_nil(dictionary) <- keyfind(info, :dictionary),\n         label when not is_nil(label) <- keyfind(dictionary, :\"$process_label\"),\n         {Phoenix.Channel, mod, topic} when is_atom(mod) and is_binary(topic) <- label do\n      true\n    else\n      _ -> false\n    end\n  end\n\n  @doc \"\"\"\n  Returns a list of all currently connected channels for the given `Phoenix.Socket` pid.\n\n  Each channel is represented as a map with the following keys:\n\n    - `:pid` - the pid of the channel process\n    - `:status` - the status of the channel\n    - `:topic` - the topic of the channel\n\n  Note that this list also contains [custom channels](https://hexdocs.pm/phoenix/Phoenix.Socket.html#module-custom-channels)\n  like LiveViews. You can check if a channel is a custom channel by using the `channel?/1`\n  function, which returns `false` for custom channels.\n\n  ## Examples\n\n      iex> pid = Phoenix.Debug.list_sockets() |> Enum.at(0) |> Map.fetch!(:pid)\n      iex> Phoenix.Debug.list_channels(pid)\n      {:ok,\n       [\n         %{pid: #PID<0.1702.0>, status: :joined, topic: \"lv:phx-GDp9a9UZPiTxcgnE\"},\n         %{pid: #PID<0.1727.0>, status: :joined, topic: \"lv:sidebar\"}\n       ]}\n\n      iex> Phoenix.Debug.list_channels(pid(0,456,0))\n      {:error, :not_alive}\n\n  \"\"\"\n  def list_channels(socket_pid) do\n    ref = make_ref()\n\n    if Process.alive?(socket_pid) and socket_process?(socket_pid) do\n      send(socket_pid, {:debug_channels, ref, self()})\n\n      receive do\n        {:debug_channels, ^ref, channels} ->\n          {:ok, channels}\n      after\n        5_000 -> {:error, :timeout}\n      end\n    else\n      {:error, :not_alive}\n    end\n  end\n\n  @doc \"\"\"\n  Returns the socket of the channel process.\n\n  Note: this only works for channels defined with `use Phoenix.Channel`.\n  For LiveViews, use the functions defined in `Phoenix.LiveView.Debug` instead.\n\n  ## Examples\n\n      iex> pid = Phoenix.Debug.list_sockets() |> Enum.at(0) |> Map.fetch!(:pid)\n      iex> {:ok, channels} = Phoenix.Debug.list_channels(pid)\n      iex> channels |> Enum.at(0) |> Map.fetch!(:pid) |> socket()\n      {:ok, %Phoenix.Socket{...}}\n\n      iex> socket(pid(0,456,0))\n      {:error, :not_alive_or_not_a_channel}\n\n  \"\"\"\n  def socket(channel_pid) do\n    if channel_process?(channel_pid) do\n      {:ok, Phoenix.Channel.Server.socket(channel_pid)}\n    else\n      {:error, :not_alive_or_not_a_channel}\n    end\n  catch\n    :exit, _ -> {:error, :not_alive_or_not_a_channel}\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/digester/compressor.ex",
    "content": "defmodule Phoenix.Digester.Compressor do\n  @moduledoc ~S\"\"\"\n  Defines the `Phoenix.Digester.Compressor` behaviour for\n  implementing static file compressors.\n\n  A custom compressor expects 2 functions to be implemented.\n\n  By default, Phoenix uses only `Phoenix.Digester.Gzip` to compress\n  static files, but additional compressors can be defined and added\n  to the digest process.\n\n  ## Example\n\n  If you wanted to compress files using an external brotli compression\n  library, you could define a new module implementing the behaviour and add the\n  module to the list of configured Phoenix static compressors.\n\n      defmodule MyApp.BrotliCompressor do\n        @behaviour Phoenix.Digester.Compressor\n\n        def compress_file(file_path, content) do\n          valid_extension = Path.extname(file_path) in Application.fetch_env!(:phoenix, :gzippable_exts)\n          {:ok, compressed_content} = :brotli.encode(content)\n\n          if valid_extension && byte_size(compressed_content) < byte_size(content) do\n            {:ok, compressed_content}\n          else\n            :error\n          end\n        end\n\n        def file_extensions do\n          [\".br\"]\n        end\n      end\n\n      # config/config.exs\n      config :phoenix,\n        static_compressors: [Phoenix.Digester.Gzip, MyApp.BrotliCompressor],\n        # ...\n  \"\"\"\n  @callback compress_file(Path.t(), binary()) :: {:ok, binary()} | :error\n  @callback file_extensions() :: nonempty_list(String.t())\nend\n"
  },
  {
    "path": "lib/phoenix/digester/gzip.ex",
    "content": "defmodule Phoenix.Digester.Gzip do\n  @moduledoc ~S\"\"\"\n  Gzip compressor for Phoenix.Digester\n  \"\"\"\n  @behaviour Phoenix.Digester.Compressor\n\n  def compress_file(file_path, content) do\n    if Path.extname(file_path) in Application.fetch_env!(:phoenix, :gzippable_exts) do\n      {:ok, :zlib.gzip(content)}\n    else\n      :error\n    end\n  end\n\n  def file_extensions do\n    [\".gz\"]\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/digester.ex",
    "content": "defmodule Phoenix.Digester do\n  @manifest_version 1\n  @empty_manifest %{\n    \"version\" => @manifest_version,\n    \"digests\" => %{},\n    \"latest\" => %{}\n  }\n\n  defp now() do\n    :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())\n  end\n\n  @moduledoc false\n\n  @doc \"\"\"\n  Digests and compresses the static files in the given `input_path`\n  and saves them in the given `output_path`.\n  \"\"\"\n  @spec compile(String.t(), String.t(), boolean()) :: :ok | {:error, :invalid_path}\n  def compile(input_path, output_path, with_vsn?) do\n    if File.exists?(input_path) do\n      File.mkdir_p!(output_path)\n\n      files = filter_files(input_path)\n      files = fixup_sourcemaps(files)\n      latest = generate_latest(files)\n      digests = load_compile_digests(output_path)\n      digested_files = Enum.map(files, &digested_contents(&1, latest, with_vsn?))\n\n      save_manifest(digested_files, latest, digests, output_path)\n\n      digested_files\n      |> Task.async_stream(&write_to_disk(&1, output_path), ordered: false, timeout: :infinity)\n      |> Stream.run()\n    else\n      {:error, :invalid_path}\n    end\n  end\n\n  defp filter_files(input_path) do\n    input_path\n    |> Path.join(\"**\")\n    |> Path.wildcard()\n    |> Enum.filter(&(not (File.dir?(&1) or compiled_file?(&1))))\n    |> Enum.map(&map_file(&1, input_path))\n  end\n\n  defp fixup_sourcemaps(files) when is_list(files) do\n    Enum.map(files, &maybe_fixup_sourcemap(&1, files))\n  end\n\n  defp maybe_fixup_sourcemap(sourcemap, files) do\n    if Path.extname(sourcemap.filename) == \".map\" do\n      fixup_sourcemap(sourcemap, files)\n    else\n      sourcemap\n    end\n  end\n\n  defp fixup_sourcemap(%{} = sourcemap, files) do\n    asset_path = Path.rootname(sourcemap.absolute_path, \".map\")\n    asset = Enum.find(files, fn file -> file.absolute_path == asset_path end)\n\n    if asset do\n      new_digested_filename = asset.digested_filename <> \".map\"\n      %{sourcemap | digest: asset.digest, digested_filename: new_digested_filename}\n    else\n      sourcemap\n    end\n  end\n\n  defp generate_latest(files) do\n    Map.new(\n      files,\n      &{\n        manifest_join(&1.relative_path, &1.filename),\n        manifest_join(&1.relative_path, &1.digested_filename)\n      }\n    )\n  end\n\n  defp load_compile_digests(output_path) do\n    manifest = load_manifest(output_path)\n    manifest[\"digests\"]\n  end\n\n  defp load_manifest(output_path) do\n    manifest_path = Path.join(output_path, \"cache_manifest.json\")\n\n    if File.exists?(manifest_path) do\n      manifest_path\n      |> File.read!()\n      |> Phoenix.json_library().decode!()\n      |> migrate_manifest(output_path)\n    else\n      @empty_manifest\n    end\n  end\n\n  defp migrate_manifest(%{\"version\" => @manifest_version} = manifest, _output_path), do: manifest\n  defp migrate_manifest(_latest, _output_path), do: @empty_manifest\n\n  defp save_manifest(files, latest, old_digests, output_path) do\n    old_digests_that_still_exist =\n      old_digests\n      |> Enum.filter(fn {file, _} -> File.exists?(Path.join(output_path, file)) end)\n      |> Map.new()\n\n    digests = Map.merge(old_digests_that_still_exist, generate_digests(files))\n    write_manifest(latest, digests, output_path)\n  end\n\n  @comment \"This file was auto-generated by `mix phx.digest`. Remove it and all generated artefacts with `mix phx.digest.clean --all`\"\n\n  defp write_manifest(latest, digests, output_path) do\n    encoder = Phoenix.json_library()\n\n    json = \"\"\"\n    {\n      \"!comment!\":#{encoder.encode!(@comment)},\n      \"version\":#{encoder.encode!(@manifest_version)},\n      \"latest\":#{encoder.encode!(latest)},\n      \"digests\":#{encoder.encode!(digests)}\n    }\n    \"\"\"\n\n    File.write!(Path.join(output_path, \"cache_manifest.json\"), json)\n  end\n\n  defp remove_manifest(output_path) do\n    File.rm(Path.join(output_path, \"cache_manifest.json\"))\n  end\n\n  defp generate_digests(files) do\n    Map.new(\n      files,\n      &{\n        manifest_join(&1.relative_path, &1.digested_filename),\n        build_digest(&1)\n      }\n    )\n  end\n\n  defp build_digest(file) do\n    %{\n      logical_path: manifest_join(file.relative_path, file.filename),\n      mtime: now(),\n      size: file.size,\n      digest: file.digest,\n      sha512: Base.encode64(:crypto.hash(:sha512, file.digested_content))\n    }\n  end\n\n  defp manifest_join(\".\", filename), do: filename\n  defp manifest_join(path, filename), do: Path.join(path, filename)\n\n  defp compiled_file?(file_path) do\n    digested_file_regex = ~r/(-[a-fA-F\\d]{32})/\n\n    Regex.match?(digested_file_regex, Path.basename(file_path)) ||\n      Path.extname(file_path) in compressed_extensions() ||\n      Path.basename(file_path) == \"cache_manifest.json\"\n  end\n\n  defp compressed_extensions do\n    compressors = Application.fetch_env!(:phoenix, :static_compressors)\n    Enum.flat_map(compressors, & &1.file_extensions())\n  end\n\n  defp map_file(file_path, input_path) do\n    stats = File.stat!(file_path)\n    content = File.read!(file_path)\n\n    basename = Path.basename(file_path)\n    rootname = Path.rootname(basename)\n    extension = Path.extname(basename)\n    digest = Base.encode16(:erlang.md5(content), case: :lower)\n\n    %{\n      absolute_path: file_path,\n      relative_path: file_path |> Path.relative_to(input_path) |> Path.dirname(),\n      filename: basename,\n      size: stats.size,\n      content: content,\n      digest: digest,\n      digested_content: nil,\n      digested_filename: \"#{rootname}-#{digest}#{extension}\"\n    }\n  end\n\n  defp write_to_disk(file, output_path) do\n    path = Path.join(output_path, file.relative_path)\n    File.mkdir_p!(path)\n\n    compressors = Application.fetch_env!(:phoenix, :static_compressors)\n\n    Enum.each(compressors, fn compressor ->\n      [file_extension | _] = compressor.file_extensions()\n\n      compressed_digested_result =\n        compressor.compress_file(file.digested_filename, file.digested_content)\n\n      with {:ok, compressed_digested} <- compressed_digested_result do\n        File.write!(\n          Path.join(path, file.digested_filename <> file_extension),\n          compressed_digested\n        )\n      end\n\n      compress_result =\n        if file.digested_content == file.content,\n          do: compressed_digested_result,\n          else: compressor.compress_file(file.filename, file.content)\n\n      with {:ok, compressed} <- compress_result do\n        File.write!(\n          Path.join(path, file.filename <> file_extension),\n          compressed\n        )\n      end\n    end)\n\n    # uncompressed files\n    File.write!(Path.join(path, file.digested_filename), file.digested_content)\n    File.write!(Path.join(path, file.filename), file.content)\n\n    file\n  end\n\n  defp digested_contents(file, latest, with_vsn?) do\n    ext = Path.extname(file.filename)\n\n    digested_content =\n      case ext do\n        \".css\" -> digest_stylesheet_asset_references(file, latest, with_vsn?)\n        \".js\" -> digest_javascript_asset_references(file, latest)\n        \".map\" -> digest_javascript_map_asset_references(file, latest)\n        _ -> file.content\n      end\n\n    %{file | digested_content: digested_content}\n  end\n\n  defp digest_stylesheet_asset_references(file, latest, with_vsn?) do\n    stylesheet_url_regex = ~r{(url\\(\\s*)(\\S+?)(\\s*\\))}\n    quoted_text_regex = ~r{\\A(['\"])(.+)\\1\\z}\n\n    Regex.replace(stylesheet_url_regex, file.content, fn _, open, url, close ->\n      case Regex.run(quoted_text_regex, url) do\n        [_, quote_symbol, url] ->\n          open <>\n            quote_symbol <> digested_url(url, file, latest, with_vsn?) <> quote_symbol <> close\n\n        nil ->\n          open <> digested_url(url, file, latest, with_vsn?) <> close\n      end\n    end)\n  end\n\n  defp digest_javascript_asset_references(file, latest) do\n    javascript_source_map_regex = ~r{(//#\\s*sourceMappingURL=\\s*)(\\S+)}\n\n    Regex.replace(javascript_source_map_regex, file.content, fn _, source_map_text, url ->\n      source_map_text <> digested_url(url, file, latest, false)\n    end)\n  end\n\n  defp digest_javascript_map_asset_references(file, latest) do\n    javascript_map_file_regex = ~r{(['\"]file['\"]:['\"])([^,\"']+)(['\"])}\n\n    Regex.replace(javascript_map_file_regex, file.content, fn _, open_text, url, close_text ->\n      open_text <> digested_url(url, file, latest, false) <> close_text\n    end)\n  end\n\n  defp digested_url(\"/\" <> relative_path, _file, latest, with_vsn?) do\n    case Map.fetch(latest, relative_path) do\n      {:ok, digested_path} -> relative_digested_path(digested_path, with_vsn?)\n      :error -> \"/\" <> relative_path\n    end\n  end\n\n  defp digested_url(url, file, latest, with_vsn?) do\n    case URI.parse(url) do\n      %URI{scheme: nil, host: nil} ->\n        manifest_path =\n          file.relative_path\n          |> Path.join(url)\n          |> Path.expand()\n          |> Path.relative_to_cwd()\n\n        case Map.fetch(latest, manifest_path) do\n          {:ok, digested_path} ->\n            absolute_digested_url(url, digested_path, with_vsn?)\n\n          :error ->\n            url\n        end\n\n      _ ->\n        url\n    end\n  end\n\n  defp relative_digested_path(digested_path, true),\n    do: relative_digested_path(digested_path) <> \"?vsn=d\"\n\n  defp relative_digested_path(digested_path, false),\n    do: relative_digested_path(digested_path)\n\n  defp relative_digested_path(digested_path),\n    do: \"/\" <> digested_path\n\n  defp absolute_digested_url(url, digested_path, true),\n    do: absolute_digested_url(url, digested_path) <> \"?vsn=d\"\n\n  defp absolute_digested_url(url, digested_path, false),\n    do: absolute_digested_url(url, digested_path)\n\n  defp absolute_digested_url(url, digested_path),\n    do: url |> Path.dirname() |> Path.join(Path.basename(digested_path))\n\n  @doc \"\"\"\n  Deletes compiled/compressed asset files that are no longer in use based on\n  the specified criteria.\n\n  ## Arguments\n\n    * `path` - The path where the compiled/compressed files are saved\n    * `age` - The max age of assets to keep in seconds\n    * `keep` - The number of old versions to keep\n\n  \"\"\"\n  @spec clean(String.t(), integer, integer, integer) :: :ok | {:error, :invalid_path}\n  def clean(path, age, keep, now \\\\ now()) do\n    if File.exists?(path) do\n      %{\"latest\" => latest, \"digests\" => digests} = load_manifest(path)\n      files = files_to_clean(latest, digests, now - age, keep)\n      remove_files(files, path)\n      write_manifest(latest, Map.drop(digests, files), path)\n      :ok\n    else\n      {:error, :invalid_path}\n    end\n  end\n\n  @doc \"\"\"\n  Deletes compiled/compressed asset files, including the cache manifest.\n\n  ## Arguments\n\n    * `path` - The path where the compiled/compressed files are saved\n\n  \"\"\"\n  @spec clean_all(String.t()) :: :ok | {:error, :invalid_path}\n  def clean_all(path) do\n    if File.exists?(path) do\n      %{\"digests\" => digests} = load_manifest(path)\n      grouped_digests = group_by_logical_path(digests)\n      logical_paths = Map.keys(grouped_digests)\n\n      files =\n        for {_, versions} <- grouped_digests,\n            file <- Enum.map(versions, fn {path, _attrs} -> path end),\n            do: file\n\n      remove_files(files, path)\n      remove_compressed_files(logical_paths, path)\n      remove_manifest(path)\n      :ok\n    else\n      {:error, :invalid_path}\n    end\n  end\n\n  defp files_to_clean(latest, digests, max_age, keep) do\n    digests = Map.drop(digests, Map.values(latest))\n\n    for {_, versions} <- group_by_logical_path(digests),\n        file <- versions_to_clean(versions, max_age, keep),\n        do: file\n  end\n\n  defp versions_to_clean(versions, max_age, keep) do\n    versions\n    |> Enum.map(fn {path, attrs} -> Map.put(attrs, \"path\", path) end)\n    |> Enum.sort_by(& &1[\"mtime\"], &>/2)\n    |> Enum.with_index(1)\n    |> Enum.filter(fn {version, index} -> max_age > version[\"mtime\"] || index > keep end)\n    |> Enum.map(fn {version, _index} -> version[\"path\"] end)\n  end\n\n  defp group_by_logical_path(digests) do\n    Enum.group_by(digests, fn {_, attrs} -> attrs[\"logical_path\"] end)\n  end\n\n  defp remove_files(files, output_path) do\n    for file <- files do\n      output_path\n      |> Path.join(file)\n      |> File.rm()\n\n      remove_compressed_file(file, output_path)\n    end\n  end\n\n  defp remove_compressed_files(files, output_path) do\n    for file <- files, do: remove_compressed_file(file, output_path)\n  end\n\n  defp remove_compressed_file(file, output_path) do\n    compressed_extensions()\n    |> Enum.map(fn extension -> Path.join(output_path, file <> extension) end)\n    |> Enum.each(&File.rm/1)\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint/cowboy2_adapter.ex",
    "content": "defmodule Phoenix.Endpoint.Cowboy2Adapter do\n  @moduledoc \"\"\"\n  The Cowboy2 adapter for Phoenix.\n\n  ## Endpoint configuration\n\n  This adapter uses the following endpoint configuration:\n\n    * `:http` - the configuration for the HTTP server. It accepts all options\n      as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/). Defaults\n      to `false`\n\n    * `:https` - the configuration for the HTTPS server. It accepts all options\n      as defined by [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/). Defaults\n      to `false`\n\n    * `:drainer` - a drainer process that triggers when your application is\n      shutting down to wait for any on-going request to finish. It accepts all\n      options as defined by [`Plug.Cowboy.Drainer`](https://hexdocs.pm/plug_cowboy/Plug.Cowboy.Drainer.html).\n      Defaults to `[]`, which will start a drainer process for each configured endpoint,\n      but can be disabled by setting it to `false`.\n\n  ## Custom dispatch options\n\n  You can provide custom dispatch options in order to use Phoenix's\n  builtin Cowboy server with custom handlers. For example, to handle\n  raw WebSockets [as shown in Cowboy's docs](https://github.com/ninenines/cowboy/tree/master/examples)).\n\n  The options are passed to both `:http` and `:https` keys in the\n  endpoint configuration. However, once you pass your custom dispatch\n  options, you will need to manually wire the Phoenix endpoint by\n  adding the following rule:\n\n      {:_, Plug.Cowboy.Handler, {MyAppWeb.Endpoint, []}}\n\n  For example:\n\n      config :myapp, MyAppWeb.Endpoint,\n        http: [dispatch: [\n                {:_, [\n                    {\"/foo\", MyAppWeb.CustomHandler, []},\n                    {:_, Plug.Cowboy.Handler, {MyAppWeb.Endpoint, []}}\n                  ]}]]\n\n  It is also important to specify your handlers first, otherwise\n  Phoenix will intercept the requests before they get to your handler.\n  \"\"\"\n\n  require Logger\n\n  @doc false\n  def child_specs(endpoint, config) do\n    otp_app = Keyword.fetch!(config, :otp_app)\n\n    refs_and_specs =\n      for {scheme, port} <- [http: 4000, https: 4040], opts = config[scheme] do\n        port = :proplists.get_value(:port, opts, port)\n\n        unless port do\n          Logger.error(\":port for #{scheme} config is nil, cannot start server\")\n          raise \"aborting due to nil port\"\n        end\n\n        # Ranch options are read from the top, so we keep the user opts first.\n        opts = :proplists.delete(:port, opts) ++ [port: port_to_integer(port), otp_app: otp_app]\n        child_spec(scheme, endpoint, opts, config[:code_reloader])\n      end\n\n    {refs, child_specs} = Enum.unzip(refs_and_specs)\n\n    if drainer = refs != [] && Keyword.get(config, :drainer, []) do\n      child_specs ++ [{Plug.Cowboy.Drainer, Keyword.put_new(drainer, :refs, refs)}]\n    else\n      child_specs\n    end\n  end\n\n  defp child_spec(scheme, endpoint, config, code_reloader?) do\n    if scheme == :https do\n      Application.ensure_all_started(:ssl)\n    end\n\n    ref = make_ref(endpoint, scheme)\n\n    plug =\n      if code_reloader? do\n        {Phoenix.Endpoint.SyncCodeReloadPlug, {endpoint, []}}\n      else\n        {endpoint, []}\n      end\n\n    spec = Plug.Cowboy.child_spec(ref: ref, scheme: scheme, plug: plug, options: config)\n    spec = update_in(spec.start, &{__MODULE__, :start_link, [scheme, endpoint, &1]})\n    {ref, spec}\n  end\n\n  @doc false\n  def start_link(scheme, endpoint, {m, f, [ref | _] = a}) do\n    # ref is used by Ranch to identify its listeners, defaulting\n    # to plug.HTTP and plug.HTTPS and overridable by users.\n    case apply(m, f, a) do\n      {:ok, pid} ->\n        Logger.info(info(scheme, endpoint, ref))\n        {:ok, pid}\n\n      {:error, {:shutdown, {_, _, {:listen_error, _, :eaddrinuse}}}} = error ->\n        Logger.error([info(scheme, endpoint, ref), \" failed, port already in use\"])\n        error\n\n      {:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->\n        Logger.error([info(scheme, endpoint, ref), \" failed, port already in use\"])\n        error\n\n      {:error, _} = error ->\n        error\n    end\n  end\n\n  defp info(scheme, endpoint, ref) do\n    server = \"cowboy #{Application.spec(:cowboy)[:vsn]}\"\n    \"Running #{inspect(endpoint)} with #{server} at #{bound_address(scheme, ref)}\"\n  end\n\n  defp bound_address(scheme, ref) do\n    case :ranch.get_addr(ref) do\n      {:local, unix_path} ->\n        \"#{unix_path} (#{scheme}+unix)\"\n\n      {addr, port} ->\n        \"#{:inet.ntoa(addr)}:#{port} (#{scheme})\"\n    end\n  rescue\n    _ -> scheme\n  end\n\n  # TODO: Remove this once {:system, env_var} deprecation is removed\n  defp port_to_integer({:system, env_var}), do: port_to_integer(System.get_env(env_var))\n  defp port_to_integer(port) when is_binary(port), do: String.to_integer(port)\n  defp port_to_integer(port) when is_integer(port), do: port\n\n  def server_info(endpoint, scheme) do\n    address =\n      endpoint\n      |> make_ref(scheme)\n      |> :ranch.get_addr()\n\n    {:ok, address}\n  rescue\n    e -> {:error, Exception.message(e)}\n  end\n\n  defp make_ref(endpoint, scheme) do\n    Module.concat(endpoint, scheme |> Atom.to_string() |> String.upcase())\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint/render_errors.ex",
    "content": "defmodule Phoenix.Endpoint.RenderErrors do\n  # This module is used to catch failures and render them using a view.\n  #\n  # This module is automatically used in `Phoenix.Endpoint` where it\n  # overrides `call/2` to provide rendering. Once the error is\n  # rendered, the error is reraised unless it is a NoRouteError.\n  #\n  # ## Options\n  #\n  #   * `:formats` - the format to use when none is available from the request\n  #   * `:log` - the `t:Logger.level/0` or `false` to disable logging rendered errors\n  #\n  @moduledoc false\n\n  import Plug.Conn\n\n  require Logger\n\n  alias Phoenix.Router.NoRouteError\n  alias Phoenix.Controller\n\n  @already_sent {:plug_conn, :sent}\n\n  @doc false\n  defmacro __using__(opts) do\n    quote do\n      @before_compile Phoenix.Endpoint.RenderErrors\n      @phoenix_render_errors unquote(opts)\n    end\n  end\n\n  @doc false\n  defmacro __before_compile__(_) do\n    quote location: :keep do\n      defoverridable call: 2\n\n      def call(conn, opts) do\n        try do\n          super(conn, opts)\n        rescue\n          e in Plug.Conn.WrapperError ->\n            %{conn: conn, kind: kind, reason: reason, stack: stack} = e\n            unquote(__MODULE__).__catch__(conn, kind, reason, stack, @phoenix_render_errors)\n        catch\n          kind, reason ->\n            stack = __STACKTRACE__\n            unquote(__MODULE__).__catch__(conn, kind, reason, stack, @phoenix_render_errors)\n        end\n      end\n    end\n  end\n\n  @doc false\n  def __catch__(%Plug.Conn{} = conn, kind, reason, stack, opts) do\n    conn =\n      receive do\n        @already_sent ->\n          send(self(), @already_sent)\n          %{conn | state: :sent}\n      after\n        0 ->\n          instrument_render_and_send(conn, kind, reason, stack, opts)\n      end\n\n    maybe_raise(kind, reason, stack)\n    conn\n  end\n\n  defp instrument_render_and_send(conn, kind, reason, stack, opts) do\n    level = Keyword.get(opts, :log, :debug)\n    status = status(kind, reason)\n    conn = error_conn(conn, kind, reason)\n    start = System.monotonic_time()\n\n    metadata = %{\n      conn: conn,\n      status: status,\n      kind: kind,\n      reason: reason,\n      stacktrace: stack,\n      log: level\n    }\n\n    try do\n      render(conn, status, kind, reason, stack, opts)\n    after\n      duration = System.monotonic_time() - start\n      :telemetry.execute([:phoenix, :error_rendered], %{duration: duration}, metadata)\n    end\n  end\n\n  defp error_conn(_conn, :error, %NoRouteError{conn: conn}), do: conn\n  defp error_conn(conn, _kind, _reason), do: conn\n\n  defp maybe_raise(:error, %NoRouteError{}, _stack), do: :ok\n  defp maybe_raise(kind, reason, stack), do: :erlang.raise(kind, reason, stack)\n\n  ## Rendering\n\n  @doc false\n  def __debugger_banner__(_conn, _status, _kind, %NoRouteError{router: router}, _stack) do\n    \"\"\"\n    <h3>Available routes</h3>\n    <pre>#{Phoenix.Router.ConsoleFormatter.format(router)}</pre>\n    \"\"\"\n  end\n\n  def __debugger_banner__(_conn, _status, _kind, _reason, _stack), do: nil\n\n  defp render(conn, status, kind, reason, stack, opts) do\n    conn =\n      conn\n      |> maybe_fetch_query_params()\n      |> fetch_view_format(opts)\n      |> Plug.Conn.put_status(status)\n      |> Controller.put_root_layout(opts[:root_layout] || false)\n      |> Controller.put_layout(opts[:layout] || false)\n\n    format = Controller.get_format(conn)\n    reason = Exception.normalize(kind, reason, stack)\n    template = \"#{conn.status}.#{format}\"\n    assigns = %{kind: kind, reason: reason, stack: stack, status: conn.status, __changed__: nil}\n\n    Controller.render(conn, template, assigns)\n  end\n\n  defp maybe_fetch_query_params(%Plug.Conn{} = conn) do\n    fetch_query_params(conn)\n  rescue\n    Plug.Conn.InvalidQueryError ->\n      case conn.params do\n        %Plug.Conn.Unfetched{} -> %{conn | query_params: %{}, params: %{}}\n        params -> %{conn | query_params: %{}, params: params}\n      end\n  end\n\n  defp fetch_view_format(conn, opts) do\n    # We ignore params[\"_format\"] although we respect any already stored.\n    view = opts[:view]\n    formats = opts[:formats]\n    accepts = opts[:accepts]\n\n    cond do\n      formats ->\n        put_formats(conn, Enum.map(formats, fn {k, v} -> {Atom.to_string(k), v} end))\n\n      view && accepts ->\n        put_formats(conn, Enum.map(accepts, &{&1, view}))\n\n      true ->\n        raise ArgumentError,\n              \"expected :render_errors to have :formats or :view/:accepts, but got: #{inspect(opts)}\"\n    end\n  end\n\n  defp put_formats(conn, formats) do\n    [{fallback_format, fallback_view} | _] = formats\n\n    try do\n      conn =\n        case conn.private do\n          %{phoenix_format: format} when is_binary(format) -> conn\n          _ -> Controller.accepts(conn, Enum.map(formats, &elem(&1, 0)))\n        end\n\n      format = Phoenix.Controller.get_format(conn)\n\n      case List.keyfind(formats, format, 0) do\n        {_, view} ->\n          Controller.put_view(conn, view)\n\n        nil ->\n          conn\n          |> Controller.put_format(fallback_format)\n          |> Controller.put_view(fallback_view)\n      end\n    rescue\n      e in Phoenix.NotAcceptableError ->\n        Logger.debug(\n          \"Could not render errors due to #{Exception.message(e)}. \" <>\n            \"Errors will be rendered using the first accepted format #{inspect(fallback_format)} as fallback. \" <>\n            \"Please customize the :formats option under the :render_errors configuration \" <>\n            \"in your endpoint if you want to support other formats or choose another fallback\"\n        )\n\n        conn\n        |> Controller.put_format(fallback_format)\n        |> Controller.put_view(fallback_view)\n    end\n  end\n\n  defp status(:error, error), do: Plug.Exception.status(error)\n  defp status(:throw, _throw), do: 500\n  defp status(:exit, _exit), do: 500\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint/supervisor.ex",
    "content": "defmodule Phoenix.Endpoint.Supervisor do\n  # This module contains the logic used by most functions in Phoenix.Endpoint\n  # as well the supervisor for sockets, adapters, watchers, etc.\n  @moduledoc false\n\n  require Logger\n  use Supervisor\n\n  @doc \"\"\"\n  Starts the endpoint supervision tree.\n  \"\"\"\n  def start_link(otp_app, mod, opts \\\\ []) do\n    with {:ok, pid} = ok <- Supervisor.start_link(__MODULE__, {otp_app, mod, opts}, name: mod) do\n      # We don't use the defaults in the checks below\n      conf = Keyword.merge(Application.get_env(otp_app, mod, []), opts)\n      log_access_url(mod, conf)\n      browser_open(mod, conf)\n\n      measurements = %{system_time: System.system_time()}\n      metadata = %{pid: pid, config: conf, module: mod, otp_app: otp_app}\n      :telemetry.execute([:phoenix, :endpoint, :init], measurements, metadata)\n\n      ok\n    end\n  end\n\n  @doc false\n  def init({otp_app, mod, opts}) do\n    default_conf = Phoenix.Config.merge(defaults(otp_app, mod), opts)\n    env_conf = Phoenix.Config.from_env(otp_app, mod, default_conf)\n\n    secret_conf =\n      cond do\n        Code.ensure_loaded?(mod) and function_exported?(mod, :init, 2) ->\n          IO.warn(\n            \"\"\"\n            your #{inspect(mod)} defines a init/2 callback, which is now deprecated. \\\n            This callback is invoked when your endpoint is initialized as part of your supervision tree. \\\n            Instead, you should either:\n\n            1. Move all dynamic configuration to config/runtime.exs (preferred). For example:\n\n                # config/runtime.exs\n                import Config\n\n                if config_env() == :prod do\n                  config #{inspect(otp_app)}, #{inspect(mod)},\n                    http: [:inet6, port: System.fetch_env!(\"PORT\")]\n                end\n\n            2. Pass the configuration you returned from the `init/2` callback \\\n            as additional options when starting the endpoint in your supervision tree. \\\n            For example: {#{inspect(mod)}, some_extra_options: true}\n            \"\"\",\n            []\n          )\n\n          {:ok, init_conf} = mod.init(:supervisor, env_conf)\n          init_conf\n\n        is_nil(Application.get_env(otp_app, mod)) ->\n          Logger.warning(\n            \"no configuration found for otp_app #{inspect(otp_app)} and module #{inspect(mod)}\"\n          )\n\n          env_conf\n\n        true ->\n          env_conf\n      end\n\n    extra_conf = [\n      endpoint_id: :crypto.strong_rand_bytes(16) |> Base.encode64(padding: false),\n      # TODO: Remove this once :pubsub is removed\n      pubsub_server: secret_conf[:pubsub_server] || secret_conf[:pubsub][:name]\n    ]\n\n    secret_conf = extra_conf ++ secret_conf\n    default_conf = extra_conf ++ default_conf\n\n    # Drop all secrets from secret_conf before passing it around\n    conf = Keyword.drop(secret_conf, [:secret_key_base])\n    server? = server?(conf)\n\n    if conf[:instrumenters] do\n      Logger.warning(\n        \":instrumenters configuration for #{inspect(mod)} is deprecated and has no effect\"\n      )\n    end\n\n    if server? and conf[:code_reloader] do\n      Phoenix.CodeReloader.Server.check_symlinks()\n    end\n\n    # TODO: Remove this once {:system, env_var} tuples are removed\n    warn_on_deprecated_system_env_tuples(otp_app, mod, conf, :http)\n    warn_on_deprecated_system_env_tuples(otp_app, mod, conf, :https)\n    warn_on_deprecated_system_env_tuples(otp_app, mod, conf, :url)\n    warn_on_deprecated_system_env_tuples(otp_app, mod, conf, :static_url)\n\n    children =\n      config_children(mod, secret_conf, default_conf) ++\n        warmup_children(mod) ++\n        pubsub_children(mod, conf) ++\n        socket_children(mod, conf, :child_spec) ++\n        server_children(mod, conf, server?) ++\n        socket_children(mod, conf, :drainer_spec) ++\n        watcher_children(mod, conf, server?)\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\n\n  defp pubsub_children(mod, conf) do\n    pub_conf = conf[:pubsub]\n\n    if pub_conf do\n      Logger.warning(\"\"\"\n      The :pubsub key in your #{inspect(mod)} is deprecated.\n\n      You must now start the pubsub in your application supervision tree.\n      Go to lib/my_app/application.ex and add the following:\n\n          {Phoenix.PubSub, #{inspect(pub_conf)}}\n\n      Now, back in your config files in config/*, you can remove the :pubsub\n      key and add the :pubsub_server key, with the PubSub name:\n\n          pubsub_server: #{inspect(pub_conf[:name])}\n      \"\"\")\n    end\n\n    if pub_conf[:adapter] do\n      [{Phoenix.PubSub, pub_conf}]\n    else\n      []\n    end\n  end\n\n  defp socket_children(endpoint, conf, fun) do\n    for {_, socket, opts} <- Enum.uniq_by(endpoint.__sockets__(), &elem(&1, 1)),\n        _ = check_origin_or_csrf_checked!(conf, opts),\n        spec = apply_or_ignore(socket, fun, [[endpoint: endpoint] ++ opts]),\n        spec != :ignore do\n      spec\n    end\n  end\n\n  defp apply_or_ignore(socket, fun, args) do\n    # If the module is not loaded, we want to invoke and crash\n    if not Code.ensure_loaded?(socket) or function_exported?(socket, fun, length(args)) do\n      apply(socket, fun, args)\n    else\n      :ignore\n    end\n  end\n\n  defp check_origin_or_csrf_checked!(endpoint_conf, socket_opts) do\n    check_origin = endpoint_conf[:check_origin]\n\n    for {transport, transport_opts} <- socket_opts, is_list(transport_opts) do\n      check_origin = Keyword.get(transport_opts, :check_origin, check_origin)\n\n      check_csrf = transport_opts[:check_csrf]\n\n      if check_origin == false and check_csrf == false do\n        raise ArgumentError,\n              \"one of :check_origin and :check_csrf must be set to non-false value for \" <>\n                \"transport #{inspect(transport)}\"\n      end\n    end\n  end\n\n  defp config_children(mod, conf, default_conf) do\n    args = {mod, conf, default_conf, name: Module.concat(mod, \"Config\")}\n    [{Phoenix.Config, args}]\n  end\n\n  defp warmup_children(mod) do\n    [%{id: :warmup, start: {__MODULE__, :warmup, [mod]}}]\n  end\n\n  defp server_children(mod, config, server?) do\n    cond do\n      server? ->\n        adapter = config[:adapter]\n        adapter.child_specs(mod, config)\n\n      config[:http] || config[:https] ->\n        if System.get_env(\"RELEASE_NAME\") do\n          Logger.info(\n            \"Configuration :server was not enabled for #{inspect(mod)}, http/https services won't start\"\n          )\n        end\n\n        []\n\n      true ->\n        []\n    end\n  end\n\n  defp watcher_children(_mod, conf, server?) do\n    watchers = conf[:watchers] || []\n\n    if server? || conf[:force_watchers] do\n      Enum.map(watchers, &{Phoenix.Endpoint.Watcher, &1})\n    else\n      []\n    end\n  end\n\n  @doc \"\"\"\n  Checks if Endpoint's web server has been configured to start.\n  \"\"\"\n  def server?(otp_app, endpoint) when is_atom(otp_app) and is_atom(endpoint) do\n    server?(Application.get_env(otp_app, endpoint, []))\n  end\n\n  defp server?(conf) when is_list(conf) do\n    Keyword.get_lazy(conf, :server, fn ->\n      Application.get_env(:phoenix, :serve_endpoints, false)\n    end)\n  end\n\n  defp defaults(otp_app, module) do\n    [\n      otp_app: otp_app,\n\n      # Compile-time config\n      code_reloader: false,\n      debug_errors: false,\n      render_errors: [view: render_errors(module), accepts: ~w(html), layout: false],\n\n      # Runtime config\n\n      # Even though Bandit is the default in apps generated via the installer,\n      # we continue to use Cowboy as the default if not explicitly specified for\n      # backwards compatibility. TODO: Change this to default to Bandit in 2.0\n      adapter: Phoenix.Endpoint.Cowboy2Adapter,\n      cache_static_manifest: nil,\n      check_origin: true,\n      http: false,\n      https: false,\n      reloadable_apps: nil,\n      # TODO: Gettext had a compiler in earlier versions,\n      # but not since v0.20, so we can remove it here eventually.\n      reloadable_compilers: [:phoenix_live_view, :gettext, :elixir, :app],\n      secret_key_base: nil,\n      static_url: nil,\n      url: [host: \"localhost\", path: \"/\"],\n      cache_manifest_skip_vsn: false,\n\n      # Supervisor config\n      watchers: [],\n      force_watchers: false\n    ]\n  end\n\n  defp render_errors(module) do\n    module\n    |> Module.split()\n    |> Enum.at(0)\n    |> Module.concat(\"ErrorView\")\n  end\n\n  @doc \"\"\"\n  Callback that changes the configuration from the app callback.\n  \"\"\"\n  def config_change(endpoint, changed, removed) do\n    res = Phoenix.Config.config_change(endpoint, changed, removed)\n    warmup(endpoint)\n    res\n  end\n\n  @doc \"\"\"\n  Returns a two item tuple with the first element containing the\n  static path of a file in the static root directory\n  and the second element containing the sha512 of that file (for SRI).\n\n  When the file exists, it includes a timestamp. When it doesn't exist,\n  just the static path is returned.\n\n  The result is wrapped in a `{:cache | :nocache, value}` tuple so\n  the `Phoenix.Config` layer knows how to cache it.\n  \"\"\"\n  @invalid_local_url_chars [\"\\\\\"]\n\n  def static_lookup(_endpoint, \"//\" <> _ = path) do\n    raise_invalid_path(path)\n  end\n\n  def static_lookup(_endpoint, \"/\" <> _ = path) do\n    if String.contains?(path, @invalid_local_url_chars) do\n      raise ArgumentError, \"unsafe characters detected for path #{inspect(path)}\"\n    else\n      {:nocache, {path, nil}}\n    end\n  end\n\n  def static_lookup(_endpoint, path) when is_binary(path) do\n    raise_invalid_path(path)\n  end\n\n  defp raise_invalid_path(path) do\n    raise ArgumentError, \"expected a path starting with a single / but got #{inspect(path)}\"\n  end\n\n  # TODO: Remove the first function clause once {:system, env_var} tuples are removed\n  defp host_to_binary({:system, env_var}), do: host_to_binary(System.get_env(env_var))\n  defp host_to_binary(host), do: host\n\n  # TODO: Remove the first function clause once {:system, env_var} tuples are removed\n  defp port_to_integer({:system, env_var}), do: port_to_integer(System.get_env(env_var))\n  defp port_to_integer(port) when is_binary(port), do: String.to_integer(port)\n  defp port_to_integer(port) when is_integer(port), do: port\n\n  defp warn_on_deprecated_system_env_tuples(otp_app, mod, conf, key) do\n    deprecated_configs = Enum.filter(conf[key] || [], &match?({_, {:system, _}}, &1))\n\n    if Enum.any?(deprecated_configs) do\n      deprecated_config_lines = for {k, v} <- deprecated_configs, do: \"#{k}: #{inspect(v)}\"\n\n      runtime_exs_config_lines =\n        for {key, {:system, env_var}} <- deprecated_configs,\n            do: ~s|#{key}: System.get_env(\"#{env_var}\")|\n\n      Logger.warning(\"\"\"\n      #{inspect(key)} configuration containing {:system, env_var} tuples for #{inspect(mod)} is deprecated.\n\n      Configuration with deprecated values:\n\n          config #{inspect(otp_app)}, #{inspect(mod)},\n            #{key}: [\n              #{deprecated_config_lines |> Enum.join(\",\\r\\n        \")}\n            ]\n\n      Move this configuration into config/runtime.exs and replace the {:system, env_var} tuples\n      with System.get_env/1 function calls:\n\n          config #{inspect(otp_app)}, #{inspect(mod)},\n            #{key}: [\n              #{runtime_exs_config_lines |> Enum.join(\",\\r\\n        \")}\n            ]\n      \"\"\")\n    end\n  end\n\n  @doc \"\"\"\n  Invoked to warm up caches on start and config change.\n  \"\"\"\n  def warmup(endpoint) do\n    warmup_persistent(endpoint)\n\n    try do\n      if manifest = cache_static_manifest(endpoint) do\n        warmup_static(endpoint, manifest)\n      end\n    rescue\n      e -> Logger.error(\"Could not warm up static assets: #{Exception.message(e)}\")\n    end\n\n    # To prevent a race condition where the socket listener is already started\n    # but the config not warmed up, we run warmup/1 as a child in the supervision\n    # tree. As we don't actually want to start a process, we return :ignore here.\n    :ignore\n  end\n\n  defp warmup_persistent(endpoint) do\n    url_config = endpoint.config(:url)\n    static_url_config = endpoint.config(:static_url) || url_config\n\n    struct_url = build_url(endpoint, url_config)\n    host = host_to_binary(url_config[:host] || \"localhost\")\n    path = empty_string_if_root(url_config[:path] || \"/\")\n    script_name = String.split(path, \"/\", trim: true)\n\n    static_url = build_url(endpoint, static_url_config) |> String.Chars.URI.to_string()\n    static_path = empty_string_if_root(static_url_config[:path] || \"/\")\n\n    :persistent_term.put({Phoenix.Endpoint, endpoint}, %{\n      struct_url: struct_url,\n      url: String.Chars.URI.to_string(struct_url),\n      host: host,\n      path: path,\n      script_name: script_name,\n      static_path: static_path,\n      static_url: static_url\n    })\n  end\n\n  defp empty_string_if_root(\"/\"), do: \"\"\n  defp empty_string_if_root(other), do: other\n\n  defp build_url(endpoint, url) do\n    https = endpoint.config(:https)\n    http = endpoint.config(:http)\n\n    {scheme, port} =\n      cond do\n        https -> {\"https\", https[:port] || 443}\n        http -> {\"http\", http[:port] || 80}\n        true -> {\"http\", 80}\n      end\n\n    scheme = url[:scheme] || scheme\n    host = host_to_binary(url[:host] || \"localhost\")\n    port = port_to_integer(url[:port] || port)\n\n    if host =~ ~r\"[^:]:\\d\" do\n      Logger.warning(\n        \"url: [host: ...] configuration value #{inspect(host)} for #{inspect(endpoint)} is invalid\"\n      )\n    end\n\n    %URI{scheme: scheme, port: port, host: host}\n  end\n\n  defp warmup_static(endpoint, %{\"latest\" => latest, \"digests\" => digests}) do\n    Phoenix.Config.put(endpoint, :cache_static_manifest_latest, latest)\n    with_vsn? = !endpoint.config(:cache_manifest_skip_vsn)\n\n    Enum.each(latest, fn {key, _} ->\n      Phoenix.Config.cache(endpoint, {:__phoenix_static__, \"/\" <> key}, fn _ ->\n        {:cache, static_cache(digests, Map.get(latest, key), with_vsn?)}\n      end)\n    end)\n  end\n\n  defp warmup_static(_endpoint, _manifest) do\n    raise ArgumentError, \"expected cache manifest to include 'latest' and 'digests' keys\"\n  end\n\n  defp static_cache(digests, value, true) do\n    {\"/#{value}?vsn=d\", static_integrity(digests[value][\"sha512\"])}\n  end\n\n  defp static_cache(digests, value, false) do\n    {\"/#{value}\", static_integrity(digests[value][\"sha512\"])}\n  end\n\n  defp static_integrity(nil), do: nil\n  defp static_integrity(sha), do: \"sha512-#{sha}\"\n\n  defp cache_static_manifest(endpoint) do\n    if inner = endpoint.config(:cache_static_manifest) do\n      {app, inner} =\n        case inner do\n          {_, _} = inner -> inner\n          inner when is_binary(inner) -> {endpoint.config(:otp_app), inner}\n          _ -> raise ArgumentError, \":cache_static_manifest must be a binary or a tuple\"\n        end\n\n      outer = Application.app_dir(app, inner)\n\n      if File.exists?(outer) do\n        outer |> File.read!() |> Phoenix.json_library().decode!()\n      else\n        raise ArgumentError,\n              \"could not find static manifest at #{inspect(outer)}. \" <>\n                \"Run \\\"mix phx.digest\\\" after building your static files \" <>\n                \"or remove the \\\"cache_static_manifest\\\" configuration from your config files.\"\n      end\n    else\n      nil\n    end\n  end\n\n  defp log_access_url(endpoint, conf) do\n    if Keyword.get(conf, :log_access_url, true) && server?(conf) do\n      Logger.info(\"Access #{inspect(endpoint)} at #{endpoint.url()}\")\n    end\n  end\n\n  defp browser_open(endpoint, conf) do\n    if Application.get_env(:phoenix, :browser_open, false) && server?(conf) do\n      url = endpoint.url()\n\n      {cmd, args} =\n        case :os.type() do\n          {:win32, _} -> {\"cmd\", [\"/c\", \"start\", url]}\n          {:unix, :darwin} -> {\"open\", [url]}\n          {:unix, _} -> {\"xdg-open\", [url]}\n        end\n\n      System.cmd(cmd, args)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint/sync_code_reload_plug.ex",
    "content": "defmodule Phoenix.Endpoint.SyncCodeReloadPlug do\n  @moduledoc ~S\"\"\"\n  Wraps an Endpoint, attempting to sync with Phoenix's code reloader if \n  an exception is raised which indicates that we may be in the middle of a reload.\n\n  We detect this by looking at the raised exception and seeing if it indicates\n  that the endpoint is not defined. This indicates that the code reloader may be \n  midway through a compile, and that we should attempt to retry the request\n  after the compile has completed. This is also why this must be implemented in\n  a separate module (one that is not recompiled in a typical code reload cycle),\n  since otherwise it may be the case that the endpoint itself is not defined.\n  \"\"\"\n\n  @behaviour Plug\n\n  def init({endpoint, opts}), do: {endpoint, endpoint.init(opts)}\n\n  def call(conn, {endpoint, opts}), do: do_call(conn, endpoint, opts, true)\n\n  defp do_call(conn, endpoint, opts, retry?) do\n    try do\n      endpoint.call(conn, opts)\n    rescue\n      exception in [UndefinedFunctionError] ->\n        case exception do\n          %UndefinedFunctionError{module: ^endpoint} when retry? ->\n            # Sync with the code reloader and retry once\n            Phoenix.CodeReloader.sync()\n            do_call(conn, endpoint, opts, false)\n\n          exception ->\n            reraise(exception, __STACKTRACE__)\n        end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint/watcher.ex",
    "content": "defmodule Phoenix.Endpoint.Watcher do\n  @moduledoc false\n  require Logger\n\n  def child_spec(args) do\n    %{\n      id: make_ref(),\n      start: {__MODULE__, :start_link, [args]},\n      restart: :transient\n    }\n  end\n\n  def start_link({cmd, args}) do\n    Task.start_link(__MODULE__, :watch, [to_string(cmd), args])\n  end\n\n  def watch(_cmd, {mod, fun, args}) do\n    try do\n      apply(mod, fun, args)\n    catch\n      kind, reason ->\n        # The function returned a non-zero exit code.\n        # Sleep for a couple seconds before exiting to\n        # ensure this doesn't hit the supervisor's\n        # max_restarts/max_seconds limit.\n        Process.sleep(2000)\n        :erlang.raise(kind, reason, __STACKTRACE__)\n    end\n  end\n\n  def watch(cmd, args) when is_list(args) do\n    {args, opts} = Enum.split_while(args, &is_binary(&1))\n    opts = Keyword.merge([into: IO.stream(:stdio, :line), stderr_to_stdout: true], opts)\n\n    try do\n      System.cmd(cmd, args, opts)\n    catch\n      :error, :enoent ->\n        relative = Path.relative_to_cwd(cmd)\n\n        Logger.error(\n          \"Could not start watcher #{inspect(relative)} from #{inspect(cd(opts))}, executable does not exist\"\n        )\n\n        exit(:shutdown)\n    else\n      {_, 0} ->\n        :ok\n\n      {_, _} ->\n        # System.cmd returned a non-zero exit code\n        # sleep for a couple seconds before exiting to ensure this doesn't\n        # hit the supervisor's max_restarts / max_seconds limit\n        Process.sleep(2000)\n        exit(:watcher_command_error)\n    end\n  end\n\n  defp cd(opts), do: opts[:cd] || File.cwd!()\nend\n"
  },
  {
    "path": "lib/phoenix/endpoint.ex",
    "content": "defmodule Phoenix.Endpoint do\n  @moduledoc ~S\"\"\"\n  Defines a Phoenix endpoint.\n\n  The endpoint is the boundary where all requests to your\n  web application start. It is also the interface your\n  application provides to the underlying web servers.\n\n  Overall, an endpoint has three responsibilities:\n\n    * to provide a wrapper for starting and stopping the\n      endpoint as part of a supervision tree\n\n    * to define an initial plug pipeline for requests\n      to pass through\n\n    * to host web specific configuration for your\n      application\n\n  ## Endpoints\n\n  An endpoint is simply a module defined with the help\n  of `Phoenix.Endpoint`. If you have used the `mix phx.new`\n  generator, an endpoint was automatically generated as\n  part of your application:\n\n      defmodule YourAppWeb.Endpoint do\n        use Phoenix.Endpoint, otp_app: :your_app\n\n        # plug ...\n        # plug ...\n\n        plug YourApp.Router\n      end\n\n  Endpoints must be explicitly started as part of your application\n  supervision tree. Endpoints are added by default\n  to the supervision tree in generated applications. Endpoints can be\n  added to the supervision tree as follows:\n\n      children = [\n        YourAppWeb.Endpoint\n      ]\n\n  ## Endpoint configuration\n\n  All endpoints are configured in your application environment.\n  For example:\n\n      config :your_app, YourAppWeb.Endpoint,\n        secret_key_base: \"kjoy3o1zeidquwy1398juxzldjlksahdk3\"\n\n  Endpoint configuration is split into two categories. Compile-time\n  configuration means the configuration is read during compilation\n  and changing it at runtime has no effect. The compile-time\n  configuration is mostly related to error handling.\n\n  Runtime configuration, instead, is accessed during or\n  after your application is started and can be read through the\n  `c:config/2` function:\n\n      YourAppWeb.Endpoint.config(:port)\n      YourAppWeb.Endpoint.config(:some_config, :default_value)\n\n  ### Compile-time configuration\n\n  Compile-time configuration may be set on `config/dev.exs`, `config/prod.exs`\n  and so on, but has no effect on `config/runtime.exs`:\n\n    * `:code_reloader` - when `true`, enables code reloading functionality.\n      For the list of code reloader configuration options see\n      `Phoenix.CodeReloader.reload/1`. Keep in mind code reloading is\n      based on the file-system, therefore it is not possible to run two\n      instances of the same app at the same time with code reloading in\n      development, as they will race each other and only one will effectively\n      recompile the files. In such cases, tweak your config files so code\n      reloading is enabled in only one of the apps or set the `MIX_BUILD_PATH`\n      environment variable to give them distinct build directories\n\n    * `:debug_errors` - when `true`, uses `Plug.Debugger` functionality for\n      debugging failures in the application. Recommended to be set to `true`\n      only in development as it allows listing of the application source\n      code during debugging. Defaults to `false`\n\n    * `:force_ssl` - ensures no data is ever sent via HTTP, always redirecting\n      to HTTPS. It expects a list of options which are forwarded to `Plug.SSL`.\n      By default it sets the \"strict-transport-security\" header in HTTPS requests,\n      forcing browsers to always use HTTPS. If an unsafe request (HTTP) is sent,\n      it redirects to the HTTPS version using the `:host` specified in the `:url`\n      configuration. To dynamically redirect to the `host` of the current request,\n      set `:host` in the `:force_ssl` configuration to `nil`\n\n  ### Runtime configuration\n\n  The configuration below may be set on `config/dev.exs`, `config/prod.exs`\n  and so on, as well as on `config/runtime.exs`. Typically, if you need to\n  configure them with system environment variables, you set them in\n  `config/runtime.exs`. These options may also be set when starting the\n  endpoint in your supervision tree, such as `{MyApp.Endpoint, options}`.\n\n    * `:adapter` - which webserver adapter to use for serving web requests.\n      See the \"Adapter configuration\" section below\n\n    * `:cache_static_manifest` - a path to a json manifest file that contains\n      static files and their digested version. This is typically set to\n      \"priv/static/cache_manifest.json\" which is the file automatically generated\n      by `mix phx.digest`. It can be either: a string containing a file system path\n      or a tuple containing the application name and the path within that application.\n\n    * `:cache_static_manifest_latest` - a map of the static files pointing to their\n      digest version. This is automatically loaded from `cache_static_manifest` on\n      boot. However, if you have your own static handling mechanism, you may want to\n      set this value explicitly. This is used by projects such as `LiveView` to\n      detect if the client is running on the latest version of all assets.\n\n    * `:cache_manifest_skip_vsn` - when true, skips the appended query string\n      \"?vsn=d\" when generating paths to static assets. This query string is used\n      by `Plug.Static` to set long expiry dates, therefore, you should set this\n      option to true only if you are not using `Plug.Static` to serve assets,\n      for example, if you are using a CDN. If you are setting this option, you\n      should also consider passing `--no-vsn` to `mix phx.digest`. Defaults to\n      `false`.\n\n    * `:check_origin` - configure the default `:check_origin` setting for\n      transports. See `socket/3` for options. Defaults to `true`.\n\n    * `:secret_key_base` - a secret key used as a base to generate secrets\n      for encrypting and signing data. For example, cookies and tokens\n      are signed by default, but they may also be encrypted if desired.\n      Defaults to `nil` as it must be set per application\n\n    * `:server` - when `true`, starts the web server when the endpoint\n      supervision tree starts. Defaults to `false`. The `mix phx.server`\n      task automatically sets this to `true`\n\n    * `:url` - configuration for generating URLs throughout the app.\n      Accepts the `:host`, `:scheme`, `:path` and `:port` options. All\n      keys except `:path` can be changed at runtime. Defaults to:\n\n          [host: \"localhost\", path: \"/\"]\n\n      The `:port` option requires either an integer or string. The `:host`\n      option requires a string.\n\n      The `:scheme` option accepts `\"http\"` and `\"https\"` values. Default value\n      is inferred from top level `:http` or `:https` option. It is useful\n      when hosting Phoenix behind a load balancer or reverse proxy and\n      terminating SSL there.\n\n      The `:path` option can be used to override root path. Useful when hosting\n      Phoenix behind a reverse proxy with URL rewrite rules\n\n    * `:static_url` - configuration for generating URLs for static files.\n      It will fallback to `url` if no option is provided. Accepts the same\n      options as `url`\n\n    * `:watchers` - a set of watchers to run alongside your server. It\n      expects a list of tuples containing the executable and its arguments.\n      Watchers are guaranteed to run in the application directory, but only\n      when the server is enabled (unless `:force_watchers` configuration is\n      set to `true`). For example, the watcher below will run the \"watch\" mode\n      of the webpack build tool when the server starts. You can configure it\n      to whatever build tool or command you want:\n\n          [\n            node: [\n              \"node_modules/webpack/bin/webpack.js\",\n              \"--mode\",\n              \"development\",\n              \"--watch\",\n              \"--watch-options-stdin\"\n            ]\n          ]\n\n      The `:cd` and `:env` options can be given at the end of the list to customize\n      the watcher:\n\n          [node: [..., cd: \"assets\", env: [{\"TAILWIND_MODE\", \"watch\"}]]]\n\n      A watcher can also be a module-function-args tuple that will be invoked accordingly:\n\n          [another: {Mod, :fun, [arg1, arg2]}]\n\n      When `false`, watchers can be disabled.\n\n    * `:force_watchers` - when `true`, forces your watchers to start\n      even when the `:server` option is set to `false`.\n\n    * `:live_reload` - configuration for the live reload option.\n      Configuration requires a `:patterns` option which should be a list of\n      file patterns to watch. When these files change, it will trigger a reload.\n\n          live_reload: [\n            url: \"ws://localhost:4000\",\n            patterns: [\n              ~r\"priv/static/(?!uploads/).*(js|css|png|jpeg|jpg|gif|svg)$\",\n              ~r\"lib/app_web/(live|views)/.*(ex)$\",\n              ~r\"lib/app_web/templates/.*(eex)$\"\n            ]\n          ]\n\n    * `:pubsub_server` - the name of the pubsub server to use in channels\n      and via the Endpoint broadcast functions. The PubSub server is typically\n      started in your supervision tree.\n\n    * `:render_errors` - responsible for rendering templates whenever there\n      is a failure in the application. For example, if the application crashes\n      with a 500 error during a HTML request, `render(\"500.html\", assigns)`\n      will be called in the view given to `:render_errors`.\n      A `:formats` list can be provided to specify a module per format to handle\n      error rendering. Example:\n\n          [formats: [html: MyApp.ErrorHTML], layout: false, log: :debug]\n\n    * `:log_access_url` - log the access url once the server boots\n\n  Note that you can also store your own configurations in the Phoenix.Endpoint.\n  For example, [Phoenix LiveView](https://hexdocs.pm/phoenix_live_view) expects\n  its own configuration under the `:live_view` key. In such cases, you should\n  consult the documentation of the respective projects.\n\n  ### Adapter configuration\n\n  Phoenix allows you to choose which webserver adapter to use. Newly generated\n  applications created via the `phx.new` Mix task use the\n  [`Bandit`](https://github.com/mtrudel/bandit) webserver via the\n  `Bandit.PhoenixAdapter` adapter. If not otherwise specified via the `adapter`\n  option Phoenix will fall back to the `Phoenix.Endpoint.Cowboy2Adapter` for\n  backwards compatibility with applications generated prior to Phoenix 1.7.8.\n\n  Both adapters can be configured in a similar manner using the following two\n  top-level options:\n\n    * `:http` - the configuration for the HTTP server. It accepts all options\n      as defined by either [`Bandit`](https://hexdocs.pm/bandit/Bandit.html#t:options/0)\n      or [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/) depending on your\n      choice of adapter. Defaults to `false`\n\n    * `:https` - the configuration for the HTTPS server. It accepts all options\n      as defined by either [`Bandit`](https://hexdocs.pm/bandit/Bandit.html#t:options/0)\n      or [`Plug.Cowboy`](https://hexdocs.pm/plug_cowboy/) depending on your\n      choice of adapter. Defaults to `false`\n\n  In addition, the connection draining can be configured for the Cowboy webserver via the following\n  top-level option (this is not required for Bandit as it has connection draining built-in):\n\n    * `:drainer` - a drainer process waits for any on-going request to finish\n      during application shutdown. It accepts the `:shutdown` and\n      `:check_interval` options as defined by `Plug.Cowboy.Drainer`.\n      Note the draining does not terminate any existing connection, it simply\n      waits for them to finish. Socket connections run their own drainer\n      before this one is invoked. That's because sockets are stateful and\n      can be gracefully notified, which allows us to stagger them over a\n      longer period of time. See the documentation for `socket/3` for more\n      information\n\n  ## Endpoint API\n\n  In the previous section, we have used the `c:config/2` function that is\n  automatically generated in your endpoint. Here's a list of all the functions\n  that are automatically defined in your endpoint:\n\n    * for handling paths and URLs: `c:struct_url/0`, `c:url/0`, `c:path/1`,\n      `c:static_url/0`,`c:static_path/1`, and `c:static_integrity/1`\n\n    * for gathering runtime information about the address and port the\n      endpoint is running on: `c:server_info/1`\n\n    * for broadcasting to channels: `c:broadcast/3`, `c:broadcast!/3`,\n      `c:broadcast_from/4`, `c:broadcast_from!/4`, `c:local_broadcast/3`,\n      and `c:local_broadcast_from/4`\n\n    * for configuration: `c:start_link/1`, `c:config/2`, and `c:config_change/2`\n\n    * as required by the `Plug` behaviour: `c:Plug.init/1` and `c:Plug.call/2`\n\n  \"\"\"\n\n  @type topic :: String.t()\n  @type event :: String.t()\n  @type msg :: map | {:binary, binary}\n\n  # Configuration\n\n  @doc \"\"\"\n  Starts the endpoint supervision tree.\n\n  Starts endpoint's configuration cache and possibly the servers for\n  handling requests.\n  \"\"\"\n  @callback start_link(keyword) :: Supervisor.on_start()\n\n  @doc \"\"\"\n  Access the endpoint configuration given by key.\n  \"\"\"\n  @callback config(key :: atom, default :: term) :: term\n\n  @doc \"\"\"\n  Reload the endpoint configuration on application upgrades.\n  \"\"\"\n  @callback config_change(changed :: term, removed :: term) :: term\n\n  # Paths and URLs\n\n  @doc \"\"\"\n  Generates the endpoint base URL, but as a `URI` struct.\n  \"\"\"\n  @callback struct_url() :: URI.t()\n\n  @doc \"\"\"\n  Generates the endpoint base URL without any path information.\n  \"\"\"\n  @callback url() :: String.t()\n\n  @doc \"\"\"\n  Generates the path information when routing to this endpoint.\n  \"\"\"\n  @callback path(path :: String.t()) :: String.t()\n\n  @doc \"\"\"\n  Generates the static URL without any path information.\n  \"\"\"\n  @callback static_url() :: String.t()\n\n  @doc \"\"\"\n  Generates a route to a static file in `priv/static`.\n  \"\"\"\n  @callback static_path(path :: String.t()) :: String.t()\n\n  @doc \"\"\"\n  Generates an integrity hash to a static file in `priv/static`.\n  \"\"\"\n  @callback static_integrity(path :: String.t()) :: String.t() | nil\n\n  @doc \"\"\"\n  Generates a two item tuple containing the `static_path` and `static_integrity`.\n  \"\"\"\n  @callback static_lookup(path :: String.t()) :: {String.t(), String.t()} | {String.t(), nil}\n\n  @doc \"\"\"\n  Returns the script name from the :url configuration.\n  \"\"\"\n  @callback script_name() :: [String.t()]\n\n  @doc \"\"\"\n  Returns the host from the :url configuration.\n  \"\"\"\n  @callback host() :: String.t()\n\n  # Server information\n\n  @doc \"\"\"\n  Returns the address and port that the server is running on\n  \"\"\"\n  @callback server_info(Plug.Conn.scheme()) ::\n              {:ok, {:inet.ip_address(), :inet.port_number()} | :inet.returned_non_ip_address()}\n              | {:error, term()}\n\n  # Channels\n\n  @doc \"\"\"\n  Subscribes the caller to the given topic.\n\n  See `Phoenix.PubSub.subscribe/3` for options.\n  \"\"\"\n  @callback subscribe(topic, opts :: Keyword.t()) :: :ok | {:error, term}\n\n  @doc \"\"\"\n  Unsubscribes the caller from the given topic.\n  \"\"\"\n  @callback unsubscribe(topic) :: :ok | {:error, term}\n\n  @doc \"\"\"\n  Broadcasts a `msg` as `event` in the given `topic` to all nodes.\n  \"\"\"\n  @callback broadcast(topic, event, msg) :: :ok | {:error, term}\n\n  @doc \"\"\"\n  Broadcasts a `msg` as `event` in the given `topic` to all nodes.\n\n  Raises in case of failures.\n  \"\"\"\n  @callback broadcast!(topic, event, msg) :: :ok\n\n  @doc \"\"\"\n  Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes.\n  \"\"\"\n  @callback broadcast_from(from :: pid, topic, event, msg) :: :ok | {:error, term}\n\n  @doc \"\"\"\n  Broadcasts a `msg` from the given `from` as `event` in the given `topic` to all nodes.\n\n  Raises in case of failures.\n  \"\"\"\n  @callback broadcast_from!(from :: pid, topic, event, msg) :: :ok\n\n  @doc \"\"\"\n  Broadcasts a `msg` as `event` in the given `topic` within the current node.\n  \"\"\"\n  @callback local_broadcast(topic, event, msg) :: :ok\n\n  @doc \"\"\"\n  Broadcasts a `msg` from the given `from` as `event` in the given `topic` within the current node.\n  \"\"\"\n  @callback local_broadcast_from(from :: pid, topic, event, msg) :: :ok\n\n  @doc false\n  defmacro __using__(opts) do\n    quote do\n      @behaviour Phoenix.Endpoint\n\n      unquote(config(opts))\n      unquote(pubsub())\n      unquote(plug())\n      unquote(server())\n    end\n  end\n\n  defp config(opts) do\n    quote do\n      @otp_app unquote(opts)[:otp_app] || raise(\"endpoint expects :otp_app to be given\")\n\n      # Compile-time configuration checking\n      # This ensures that if a compile-time configuration is overwritten at runtime the application won't boot.\n      var!(code_reloading?) =\n        Application.compile_env(@otp_app, [__MODULE__, :code_reloader], false)\n\n      var!(debug_errors?) = Application.compile_env(@otp_app, [__MODULE__, :debug_errors], false)\n      var!(force_ssl) = Application.compile_env(@otp_app, [__MODULE__, :force_ssl])\n\n      # Avoid unused variable warnings\n      _ = var!(code_reloading?)\n      _ = var!(force_ssl)\n    end\n  end\n\n  defp pubsub() do\n    quote generated: true do\n      def subscribe(topic, opts \\\\ []) when is_binary(topic) do\n        Phoenix.PubSub.subscribe(pubsub_server!(), topic, opts)\n      end\n\n      def unsubscribe(topic) do\n        Phoenix.PubSub.unsubscribe(pubsub_server!(), topic)\n      end\n\n      def broadcast_from(from, topic, event, msg) do\n        Phoenix.Channel.Server.broadcast_from(pubsub_server!(), from, topic, event, msg)\n      end\n\n      def broadcast_from!(from, topic, event, msg) do\n        Phoenix.Channel.Server.broadcast_from!(pubsub_server!(), from, topic, event, msg)\n      end\n\n      def broadcast(topic, event, msg) do\n        Phoenix.Channel.Server.broadcast(pubsub_server!(), topic, event, msg)\n      end\n\n      def broadcast!(topic, event, msg) do\n        Phoenix.Channel.Server.broadcast!(pubsub_server!(), topic, event, msg)\n      end\n\n      def local_broadcast(topic, event, msg) do\n        Phoenix.Channel.Server.local_broadcast(pubsub_server!(), topic, event, msg)\n      end\n\n      def local_broadcast_from(from, topic, event, msg) do\n        Phoenix.Channel.Server.local_broadcast_from(pubsub_server!(), from, topic, event, msg)\n      end\n\n      defp pubsub_server! do\n        config(:pubsub_server) ||\n          raise ArgumentError, \"no :pubsub_server configured for #{inspect(__MODULE__)}\"\n      end\n    end\n  end\n\n  defp plug() do\n    quote location: :keep do\n      use Plug.Builder, init_mode: Phoenix.plug_init_mode()\n      import Phoenix.Endpoint\n\n      Module.register_attribute(__MODULE__, :phoenix_sockets, accumulate: true)\n\n      if force_ssl = Phoenix.Endpoint.__force_ssl__(__MODULE__, var!(force_ssl)) do\n        plug Plug.SSL, force_ssl\n      end\n\n      if var!(debug_errors?) do\n        logo =\n          \"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K\"\n\n        use Plug.Debugger,\n          otp_app: @otp_app,\n          banner: {Phoenix.Endpoint.RenderErrors, :__debugger_banner__, []},\n          style: [\n            primary: \"#EB532D\",\n            dark: [\n              primary: \"#FF6B4A\",\n              logo: logo\n            ],\n            logo: logo\n          ]\n      end\n\n      plug :socket_dispatch\n\n      # Compile after the debugger so we properly wrap it.\n      @before_compile Phoenix.Endpoint\n    end\n  end\n\n  defp server() do\n    quote location: :keep, unquote: false do\n      @doc \"\"\"\n      Returns the child specification to start the endpoint\n      under a supervision tree.\n      \"\"\"\n      def child_spec(opts) do\n        %{\n          id: __MODULE__,\n          start: {__MODULE__, :start_link, [opts]},\n          type: :supervisor\n        }\n      end\n\n      @doc \"\"\"\n      Starts the endpoint supervision tree.\n\n      All other options are merged into the endpoint configuration.\n      \"\"\"\n      def start_link(opts \\\\ []) do\n        Phoenix.Endpoint.Supervisor.start_link(@otp_app, __MODULE__, opts)\n      end\n\n      @doc \"\"\"\n      Returns the endpoint configuration for `key`\n\n      Returns `default` if the key does not exist.\n      \"\"\"\n      def config(key, default \\\\ nil) do\n        case :ets.lookup(__MODULE__, key) do\n          [{^key, val}] -> val\n          [] -> default\n        end\n      end\n\n      @doc \"\"\"\n      Reloads the configuration given the application environment changes.\n      \"\"\"\n      def config_change(changed, removed) do\n        Phoenix.Endpoint.Supervisor.config_change(__MODULE__, changed, removed)\n      end\n\n      defp persistent!() do\n        :persistent_term.get({Phoenix.Endpoint, __MODULE__}, nil) ||\n          raise \"could not find persistent term for endpoint #{inspect(__MODULE__)}. Make sure your endpoint is started and note you cannot access endpoint functions at compile-time\"\n      end\n\n      @doc \"\"\"\n      Generates the endpoint base URL without any path information.\n\n      It uses the configuration under `:url` to generate such.\n      \"\"\"\n      def url, do: persistent!().url\n\n      @doc \"\"\"\n      Generates the static URL without any path information.\n\n      It uses the configuration under `:static_url` to generate\n      such. It falls back to `:url` if `:static_url` is not set.\n      \"\"\"\n      def static_url, do: persistent!().static_url\n\n      @doc \"\"\"\n      Generates the endpoint base URL but as a `URI` struct.\n\n      It uses the configuration under `:url` to generate such.\n      Useful for manipulating the URL data and passing it to\n      URL helpers.\n      \"\"\"\n      def struct_url, do: persistent!().struct_url\n\n      @doc \"\"\"\n      Returns the host for the given endpoint.\n      \"\"\"\n      def host, do: persistent!().host\n\n      @doc \"\"\"\n      Generates the path information when routing to this endpoint.\n      \"\"\"\n      def path(path), do: persistent!().path <> path\n\n      @doc \"\"\"\n      Generates the script name.\n      \"\"\"\n      def script_name, do: persistent!().script_name\n\n      @doc \"\"\"\n      Generates a route to a static file in `priv/static`.\n      \"\"\"\n      def static_path(path) do\n        prefix = persistent!().static_path\n\n        case :binary.split(path, \"#\") do\n          [path, fragment] -> prefix <> elem(static_lookup(path), 0) <> \"#\" <> fragment\n          [path] -> prefix <> elem(static_lookup(path), 0)\n        end\n      end\n\n      @doc \"\"\"\n      Generates a base64-encoded cryptographic hash (sha512) to a static file\n      in `priv/static`. Meant to be used for Subresource Integrity with CDNs.\n      \"\"\"\n      def static_integrity(path), do: elem(static_lookup(path), 1)\n\n      @doc \"\"\"\n      Returns a two item tuple with the first item being the `static_path`\n      and the second item being the `static_integrity`.\n      \"\"\"\n      def static_lookup(path) do\n        Phoenix.Config.cache(\n          __MODULE__,\n          {:__phoenix_static__, path},\n          &Phoenix.Endpoint.Supervisor.static_lookup(&1, path)\n        )\n      end\n\n      @doc \"\"\"\n      Returns the address and port that the server is running on\n      \"\"\"\n      def server_info(scheme), do: config(:adapter).server_info(__MODULE__, scheme)\n    end\n  end\n\n  @doc false\n  def __force_ssl__(module, force_ssl) do\n    if force_ssl do\n      Keyword.put_new(force_ssl, :host, {module, :host, []})\n    end\n  end\n\n  @doc false\n  defmacro __before_compile__(%{module: module}) do\n    sockets = Module.get_attribute(module, :phoenix_sockets)\n\n    dispatches =\n      for {path, socket, socket_opts} <- sockets,\n          {path, plug, conn_ast, plug_opts} <- socket_paths(module, path, socket, socket_opts) do\n        quote do\n          defp do_socket_dispatch(unquote(path), conn) do\n            halt(unquote(plug).call(unquote(conn_ast), unquote(Macro.escape(plug_opts))))\n          end\n        end\n      end\n\n    quote do\n      defoverridable call: 2\n\n      # Inline render errors so we set the endpoint before calling it.\n      def call(conn, opts) do\n        conn = %{conn | script_name: script_name(), secret_key_base: config(:secret_key_base)}\n        conn = Plug.Conn.put_private(conn, :phoenix_endpoint, __MODULE__)\n\n        try do\n          super(conn, opts)\n        rescue\n          e in Plug.Conn.WrapperError ->\n            %{conn: conn, kind: kind, reason: reason, stack: stack} = e\n\n            Phoenix.Endpoint.RenderErrors.__catch__(\n              conn,\n              kind,\n              reason,\n              stack,\n              config(:render_errors)\n            )\n        catch\n          kind, reason ->\n            stack = __STACKTRACE__\n\n            Phoenix.Endpoint.RenderErrors.__catch__(\n              conn,\n              kind,\n              reason,\n              stack,\n              config(:render_errors)\n            )\n        end\n      end\n\n      @doc false\n      def __sockets__, do: unquote(Macro.escape(sockets))\n\n      @doc false\n      def socket_dispatch(%{path_info: path} = conn, _opts), do: do_socket_dispatch(path, conn)\n      unquote(dispatches)\n      defp do_socket_dispatch(_path, conn), do: conn\n    end\n  end\n\n  defp socket_paths(endpoint, path, socket, opts) do\n    paths = []\n\n    common_config = [\n      :path,\n      :serializer,\n      :transport_log,\n      :check_origin,\n      :check_csrf,\n      :code_reloader,\n      :connect_info,\n      :auth_token,\n      :log\n    ]\n\n    websocket =\n      opts\n      |> Keyword.get(:websocket, true)\n      |> maybe_validate_keys(\n        common_config ++\n          [\n            :timeout,\n            :max_frame_size,\n            :fullsweep_after,\n            :compress,\n            :subprotocols,\n            :error_handler\n          ]\n      )\n\n    longpoll =\n      opts\n      |> Keyword.get(:longpoll, false)\n      |> maybe_validate_keys(\n        common_config ++\n          [\n            :window_ms,\n            :pubsub_timeout_ms,\n            :crypto\n          ]\n      )\n\n    paths =\n      if websocket do\n        websocket = put_auth_token(websocket, opts[:auth_token])\n        config = Phoenix.Socket.Transport.load_config(websocket, Phoenix.Transports.WebSocket)\n        plug_init = {endpoint, socket, config}\n        {conn_ast, match_path} = socket_path(path, config)\n        [{match_path, Phoenix.Transports.WebSocket, conn_ast, plug_init} | paths]\n      else\n        paths\n      end\n\n    paths =\n      if longpoll do\n        longpoll = put_auth_token(longpoll, opts[:auth_token])\n        config = Phoenix.Socket.Transport.load_config(longpoll, Phoenix.Transports.LongPoll)\n        plug_init = {endpoint, socket, config}\n        {conn_ast, match_path} = socket_path(path, config)\n        [{match_path, Phoenix.Transports.LongPoll, conn_ast, plug_init} | paths]\n      else\n        paths\n      end\n\n    paths\n  end\n\n  defp put_auth_token(true, enabled), do: [auth_token: enabled]\n  defp put_auth_token(opts, enabled), do: Keyword.put(opts, :auth_token, enabled)\n\n  defp socket_path(path, config) do\n    end_path_fragment = Keyword.fetch!(config, :path)\n\n    {vars, path} =\n      String.split(path <> \"/\" <> end_path_fragment, \"/\", trim: true)\n      |> Enum.join(\"/\")\n      |> Plug.Router.Utils.build_path_match()\n\n    conn_ast =\n      if vars == [] do\n        quote do\n          conn\n        end\n      else\n        params =\n          for var <- vars,\n              param = Atom.to_string(var),\n              not match?(\"_\" <> _, param),\n              do: {param, Macro.var(var, nil)}\n\n        quote do\n          params = %{unquote_splicing(params)}\n          %{conn | path_params: params, params: params}\n        end\n      end\n\n    {conn_ast, path}\n  end\n\n  defp maybe_validate_keys(opts, keys) when is_list(opts), do: Keyword.validate!(opts, keys)\n  defp maybe_validate_keys(other, _), do: other\n\n  ## API\n\n  @doc \"\"\"\n  Defines a websocket/longpoll mount-point for a `socket`.\n\n  It expects a `path`, a `socket` module, and a set of options.\n  The socket module is typically defined with `Phoenix.Socket`.\n\n  Both websocket and longpolling connections are supported out\n  of the box.\n\n  ## Options\n\n    * `:websocket` - controls the websocket configuration.\n      Defaults to `true`. May be false or a keyword list\n      of options. See [\"Common configuration\"](#socket/3-common-configuration)\n      and [\"WebSocket configuration\"](#socket/3-websocket-configuration)\n      for the whole list\n\n    * `:longpoll` - controls the longpoll configuration.\n      Defaults to `false`. May be true or a keyword list\n      of options. See [\"Common configuration\"](#socket/3-common-configuration)\n      and [\"Longpoll configuration\"](#socket/3-longpoll-configuration)\n      for the whole list\n\n    * `:drainer` - a keyword list or a custom MFA function returning a keyword list, for example:\n\n          {MyAppWeb.Socket, :drainer_configuration, []}\n\n      configuring how to drain sockets on application shutdown.\n      The goal is to notify all channels (and\n      LiveViews) clients to reconnect. The supported options are:\n\n      * `:batch_size` - How many clients to notify at once in a given batch.\n        Defaults to 10000.\n      * `:batch_interval` - The amount of time in milliseconds given for a\n        batch to terminate. Defaults to 2000ms.\n      * `:shutdown` - The maximum amount of time in milliseconds allowed\n        to drain all batches. Defaults to 30000ms.\n      * `:log` - the log level for drain actions. Defaults the `:log` option\n        passed to `use Phoenix.Socket` or `:info`. Set it to `false` to disable logging.\n\n      For example, if you have 150k connections, the default values will\n      split them into 15 batches of 10k connections. Each batch takes\n      2000ms before the next batch starts. In this case, we will do everything\n      right under the maximum shutdown time of 30000ms. Therefore, as\n      you increase the number of connections, remember to adjust the shutdown\n      accordingly. Finally, after the socket drainer runs, the lower level\n      HTTP/HTTPS connection drainer will still run, and apply to all connections.\n      Set it to `false` to disable draining.\n\n    * `auth_token` - a boolean that enables the use of the channels client's auth_token option.\n      The exact token exchange mechanism depends on the transport:\n\n        * the websocket transport, this enables a token to be passed through the `Sec-WebSocket-Protocol` header.\n        * the longpoll transport, this allows the token to be passed through the `Authorization` header.\n\n      The token is available in the `connect_info` as `:auth_token`.\n\n      Custom transports might implement their own mechanism.\n\n  You can also pass the options below on `use Phoenix.Socket`.\n  The values specified here override the value in `use Phoenix.Socket`.\n\n  ## Examples\n\n      socket \"/ws\", MyApp.UserSocket\n\n      socket \"/ws/admin\", MyApp.AdminUserSocket,\n        longpoll: true,\n        websocket: [compress: true]\n\n  ## Path params\n\n  It is possible to include variables in the path, these will be\n  available in the `params` that are passed to the socket.\n\n      socket \"/ws/:user_id\", MyApp.UserSocket,\n        websocket: [path: \"/project/:project_id\"]\n\n  ## Common configuration\n\n  The configuration below can be given to both `:websocket` and\n  `:longpoll` keys:\n\n    * `:path` - the path to use for the transport. Will default\n       to the transport name (\"/websocket\" or \"/longpoll\")\n\n    * `:serializer` - a list of serializers for messages. See\n      `Phoenix.Socket` for more information\n\n    * `:transport_log` - if the transport layer itself should log and,\n      if so, the level\n\n    * `:check_origin` - if the transport should check the origin of requests when\n      the `origin` header is present. May be `true`, `false`, a list of URIs that\n      are allowed, or a function provided as MFA tuple. Defaults to `:check_origin`\n      setting at endpoint configuration.\n\n      If `true`, the header is checked against `:host` in `YourAppWeb.Endpoint.config(:url)[:host]`.\n\n      If `false` and you do not validate the session in your socket, your app\n      is vulnerable to Cross-Site WebSocket Hijacking (CSWSH) attacks.\n      Only use in development, when the host is truly unknown or when\n      serving clients that do not send the `origin` header, such as mobile apps.\n\n      You can also specify a list of explicitly allowed origins. Each origin may include\n      scheme, host, and port. Wildcards are supported.\n\n          check_origin: [\n            \"https://example.com\",\n            \"//another.com:888\",\n            \"//*.other.com\"\n          ]\n\n      Or to accept any origin matching the request connection's host, port, and scheme:\n\n          check_origin: :conn\n\n      Or a custom MFA function:\n\n          check_origin: {MyAppWeb.Auth, :my_check_origin?, []}\n\n      The MFA is invoked with the request `%URI{}` as the first argument,\n      followed by arguments in the MFA list, and must return a boolean.\n\n    * `:check_csrf` - if the transport should perform CSRF check. To avoid\n      \"Cross-Site WebSocket Hijacking\", you must have at least one of\n      `check_origin` and `check_csrf` enabled. If you set both to `false`,\n      Phoenix will raise, but it is still possible to disable both by passing\n      a custom MFA to `check_origin`. In such cases, it is your responsibility\n      to ensure at least one of them is enabled. Defaults to `true`\n\n    * `:code_reloader` - enable or disable the code reloader. Defaults to your\n      endpoint configuration\n\n    * `:connect_info` - a list of keys that represent data to be copied from\n      the transport to be made available in the user socket `connect/3` callback.\n      See the \"Connect info\" subsection for valid keys\n\n  ### Connect info\n\n  The valid keys are:\n\n    * `:peer_data` - the result of `Plug.Conn.get_peer_data/1`\n\n    * `:trace_context_headers` - a list of all trace context headers. Supported\n      headers are defined by the [W3C Trace Context Specification](https://www.w3.org/TR/trace-context-1/).\n      These headers are necessary for libraries such as [OpenTelemetry](https://opentelemetry.io/)\n      to extract trace propagation information to know this request is part of a\n      larger trace in progress.\n\n    * `:x_headers` - all request headers that have an \"x-\" prefix\n\n    * `:uri` - a `%URI{}` with information from the conn\n\n    * `:user_agent` - the value of the \"user-agent\" request header\n\n    * `{:session, session_config}` - the session information from `Plug.Conn`.\n      The `session_config` is typically an exact copy of the arguments given\n      to `Plug.Session`. In order to validate the session, the \"_csrf_token\"\n      must be given as request parameter when connecting the socket with the\n      value of `URI.encode_www_form(Plug.CSRFProtection.get_csrf_token())`.\n      The CSRF token request parameter can be modified via the `:csrf_token_key`\n      option.\n\n      Additionally, `session_config` may be a MFA, such as\n      `{MyAppWeb.Auth, :get_session_config, []}`, to allow loading config in\n      runtime.\n\n  Arbitrary keywords may also appear following the above valid keys, which\n  is useful for passing custom connection information to the socket.\n\n  For example:\n\n  ```\n    socket \"/socket\", AppWeb.UserSocket,\n        websocket: [\n          connect_info: [:peer_data, :trace_context_headers, :x_headers, :uri, session: [store: :cookie]]\n        ]\n  ```\n\n  With arbitrary keywords:\n\n  ```\n    socket \"/socket\", AppWeb.UserSocket,\n        websocket: [\n          connect_info: [:uri, custom_value: \"abcdef\"]\n        ]\n  ```\n\n  > #### Where are my headers? {: .tip}\n  >\n  > Phoenix only gives you limited access to the connection headers for security\n  > reasons. WebSockets are cross-domain, which means that, when a user \"John Doe\"\n  > visits a malicious website, the malicious website can open up a WebSocket\n  > connection to your application, and the browser will gladly submit John Doe's\n  > authentication/cookie information. If you were to accept this information as is,\n  > the malicious website would have full control of a WebSocket connection to your\n  > application, authenticated on John Doe's behalf.\n  >\n  > To safe-guard your application, Phoenix limits and validates the connection\n  > information your socket can access. This means your application is safe from\n  > these attacks, but you can't access cookies and other headers in your socket.\n  > You may access the session stored in the connection via the `:connect_info`\n  > option, provided you also pass a csrf token when connecting over WebSocket.\n\n  ## Websocket configuration\n\n  The following configuration applies only to `:websocket`.\n\n    * `:timeout` - the timeout for keeping websocket connections\n      open after it last received data, defaults to 60_000ms\n\n    * `:max_frame_size` - the maximum allowed frame size in bytes,\n      defaults to \"infinity\"\n\n    * `:fullsweep_after` - the maximum number of garbage collections\n      before forcing a fullsweep for the socket process. You can set\n      it to `0` to force more frequent cleanups of your websocket\n      transport processes. Setting this option requires Erlang/OTP 24\n\n    * `:compress` - whether to enable per message compression on\n      all data frames, defaults to false\n\n    * `:subprotocols` - a list of supported websocket subprotocols.\n      Used for handshake `Sec-WebSocket-Protocol` response header, defaults to nil.\n\n      For example:\n\n          subprotocols: [\"sip\", \"mqtt\"]\n\n    * `:error_handler` - custom error handler for connection errors.\n      If `c:Phoenix.Socket.connect/3` returns an `{:error, reason}` tuple,\n      the error handler will be called with the error reason. For WebSockets,\n      the error handler must be a MFA tuple that receives a `Plug.Conn`, the\n      error reason, and returns a `Plug.Conn` with a response. For example:\n\n          socket \"/socket\", MySocket,\n              websocket: [\n                error_handler: {MySocket, :handle_error, []}\n              ]\n\n      and a `{:error, :rate_limit}` return may be handled on `MySocket` as:\n\n          def handle_error(conn, :rate_limit), do: Plug.Conn.send_resp(conn, 429, \"Too many requests\")\n\n  ## Longpoll configuration\n\n  The following configuration applies only to `:longpoll`:\n\n    * `:window_ms` - how long the client can wait for new messages\n      in its poll request in milliseconds (ms). Defaults to `10_000`.\n\n    * `:pubsub_timeout_ms` - how long a request can wait for the\n      pubsub layer to respond in milliseconds (ms). Defaults to `2000`.\n\n    * `:crypto` - options for verifying and signing the token, accepted\n      by `Phoenix.Token`. By default tokens are valid for 2 weeks\n\n  \"\"\"\n  defmacro socket(path, module, opts \\\\ []) do\n    module = Macro.expand(module, %{__CALLER__ | function: {:socket_dispatch, 2}})\n\n    quote do\n      @phoenix_sockets {unquote(path), unquote(module), unquote(opts)}\n    end\n  end\n\n  @doc false\n  @deprecated \"Phoenix.Endpoint.instrument/4 is deprecated and has no effect. Use :telemetry instead\"\n  defmacro instrument(_endpoint_or_conn_or_socket, _event, _runtime, _fun) do\n    :ok\n  end\n\n  @doc \"\"\"\n  Checks if Endpoint's web server has been configured to start.\n\n    * `otp_app` - The OTP app running the endpoint, for example `:my_app`\n    * `endpoint` - The endpoint module, for example `MyAppWeb.Endpoint`\n\n  ## Examples\n\n      iex> Phoenix.Endpoint.server?(:my_app, MyAppWeb.Endpoint)\n      true\n\n  \"\"\"\n  def server?(otp_app, endpoint) when is_atom(otp_app) and is_atom(endpoint) do\n    Phoenix.Endpoint.Supervisor.server?(otp_app, endpoint)\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/exceptions.ex",
    "content": "defmodule Phoenix.NotAcceptableError do\n  @moduledoc \"\"\"\n  Raised when one of the `accept*` headers is not accepted by the server.\n\n  This exception is commonly raised by `Phoenix.Controller.accepts/2`\n  which negotiates the media types the server is able to serve with\n  the contents the client is able to render.\n\n  If you are seeing this error, you should check if you are listing\n  the desired formats in your `:accepts` plug or if you are setting\n  the proper accept header in the client. The exception contains the\n  acceptable mime types in the `accepts` field.\n  \"\"\"\n\n  defexception message: nil, accepts: [], plug_status: 406\nend\n\ndefmodule Phoenix.MissingParamError do\n  @moduledoc \"\"\"\n  Raised when a key is expected to be present in the request parameters,\n  but is not.\n\n  This exception is raised by `Phoenix.Controller.scrub_params/2` which:\n\n    * Checks to see if the required_key is present (can be empty)\n    * Changes all empty parameters to nils (\"\" -> nil)\n\n  If you are seeing this error, you should handle the error and surface it\n  to the end user. It means that there is a parameter missing from the request.\n  \"\"\"\n\n  defexception [:message, plug_status: 400]\n\n  def exception([key: value]) do\n    msg = \"expected key #{inspect value} to be present in params, \" <>\n          \"please send the expected key or adapt your scrub_params/2 call\"\n    %Phoenix.MissingParamError{message: msg}\n  end\nend\n\ndefmodule Phoenix.ActionClauseError do\n  exception_keys =\n    FunctionClauseError.__struct__()\n    |> Map.keys()\n    |> Kernel.--([:__exception__, :__struct__])\n\n  defexception exception_keys\n\n  @impl true\n  def message(exception) do\n    exception\n    |> Map.put(:__struct__, FunctionClauseError)\n    |> FunctionClauseError.message()\n  end\n\n  @impl true\n  def blame(exception, stacktrace) do\n    {exception, stacktrace} =\n      exception\n      |> Map.put(:__struct__, FunctionClauseError)\n      |> FunctionClauseError.blame(stacktrace)\n\n    exception = Map.put(exception, :__struct__, __MODULE__)\n\n    {exception, stacktrace}\n  end\nend\n\ndefimpl Plug.Exception, for: Phoenix.ActionClauseError do\n  def status(_), do: 400\n  def actions(_), do: []\nend\n"
  },
  {
    "path": "lib/phoenix/flash.ex",
    "content": "defmodule Phoenix.Flash do\n  @moduledoc \"\"\"\n  Provides shared flash access.\n  \"\"\"\n\n  @doc \"\"\"\n  Gets the key from the map of flash data.\n\n  ## Examples\n\n  ```heex\n  <div id=\"info\"><%= Phoenix.Flash.get(@flash, :info) %></div>\n  <div id=\"error\"><%= Phoenix.Flash.get(@flash, :error) %></div>\n  ```\n  \"\"\"\n  def get(%mod{}, key) when is_atom(key) or is_binary(key) do\n    raise ArgumentError, \"\"\"\n    expected a map of flash data, but got a %#{inspect(mod)}{}\n\n    Use the @flash assign set by the :fetch_flash plug instead:\n\n        <%= Phoenix.Flash.get(@flash, :#{key}) %>\n    \"\"\"\n  end\n\n  def get(%{} = flash, key) when is_atom(key) or is_binary(key) do\n    Map.get(flash, to_string(key))\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/logger.ex",
    "content": "defmodule Phoenix.Logger do\n  @moduledoc \"\"\"\n  Instrumenter to handle logging of various instrumentation events.\n\n  ## Instrumentation\n\n  Phoenix uses the `:telemetry` library for instrumentation. The following events\n  are published by Phoenix with the following measurements and metadata:\n\n    * `[:phoenix, :endpoint, :init]` - dispatched by `Phoenix.Endpoint` after your\n      Endpoint supervision tree successfully starts\n      * Measurement: `%{system_time: system_time}`\n      * Metadata: `%{pid: pid(), config: Keyword.t(), module: module(), otp_app: atom()}`\n      * Disable logging: This event is not logged\n\n    * `[:phoenix, :endpoint, :start]` - dispatched by `Plug.Telemetry` in your endpoint,\n      usually after code reloading\n      * Measurement: `%{system_time: system_time}`\n      * Metadata: `%{conn: Plug.Conn.t, options: Keyword.t}`\n      * Options: `%{log: Logger.level | false}`\n      * Disable logging: In your endpoint `plug Plug.Telemetry, ..., log: Logger.level | false`\n      * Configure log level dynamically: `plug Plug.Telemetry, ..., log: {Mod, Fun, Args}`\n\n    * `[:phoenix, :endpoint, :stop]` - dispatched by `Plug.Telemetry` in your\n      endpoint whenever the response is sent\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{conn: Plug.Conn.t, options: Keyword.t}`\n      * Options: `%{log: Logger.level | false}`\n      * Disable logging: In your endpoint `plug Plug.Telemetry, ..., log: Logger.level | false`\n      * Configure log level dynamically: `plug Plug.Telemetry, ..., log: {Mod, Fun, Args}`\n\n    * `[:phoenix, :router_dispatch, :start]` - dispatched by `Phoenix.Router`\n      before dispatching to a matched route\n      * Measurement: `%{system_time: System.system_time}`\n      * Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom], log: Logger.level | false}`\n      * Disable logging: Pass `log: false` to the router macro, for example: `get(\"/page\", PageController, :index, log: false)`\n      * Configure log level dynamically: `get(\"/page\", PageController, :index, log: {Mod, Fun, Args})`\n\n    * `[:phoenix, :router_dispatch, :exception]` - dispatched by `Phoenix.Router`\n      after exceptions on dispatching a route\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{conn: Plug.Conn.t, kind: :throw | :error | :exit, reason: term(), stacktrace: Exception.stacktrace()}`\n      * Disable logging: This event is not logged\n\n    * `[:phoenix, :router_dispatch, :stop]` - dispatched by `Phoenix.Router`\n      after successfully dispatching a matched route\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{conn: Plug.Conn.t, route: binary, plug: module, plug_opts: term, path_params: map, pipe_through: [atom], log: Logger.level | false}`\n      * Disable logging: This event is not logged\n\n    * `[:phoenix, :error_rendered]` - dispatched at the end of an error view being rendered\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{conn: Plug.Conn.t, status: Plug.Conn.status, kind: Exception.kind, reason: term, stacktrace: Exception.stacktrace}`\n      * Disable logging: Set `render_errors: [log: false]` on your endpoint configuration\n\n    * `[:phoenix, :socket_connected]` - dispatched by `Phoenix.Socket`, at the end of a socket connection\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{endpoint: atom, transport: atom, params: term, connect_info: map, vsn: binary, user_socket: atom, result: :ok | :error, serializer: atom, log: Logger.level | false}`\n      * Disable logging: `use Phoenix.Socket, log: false` or `socket \"/foo\", MySocket, websocket: [log: false]` in your endpoint\n\n    * `[:phoenix, :socket_drain]` - dispatched by `Phoenix.Socket` when using the `:drainer` option\n      * Measurement: `%{count: integer, total: integer, index: integer, rounds: integer}`\n      * Metadata: `%{endpoint: atom, socket: atom, intervasl: integer, log: Logger.level | false}`\n      * Disable logging: `use Phoenix.Socket, log: false` in your endpoint or pass `:log` option in the `:drainer` option\n\n    * `[:phoenix, :channel_joined]` - dispatched at the end of a channel join\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{result: :ok | :error, params: term, socket: Phoenix.Socket.t}`\n      * Disable logging: This event cannot be disabled\n\n    * `[:phoenix, :channel_handled_in]` - dispatched at the end of a channel handle in\n      * Measurement: `%{duration: native_time}`\n      * Metadata: `%{event: binary, params: term, socket: Phoenix.Socket.t}`\n      * Disable logging: This event cannot be disabled\n\n  To see an example of how Phoenix LiveDashboard uses these events to create\n  metrics, visit <https://hexdocs.pm/phoenix_live_dashboard/metrics.html>.\n\n  ## Parameter filtering\n\n  When logging parameters, Phoenix can filter out sensitive parameters\n  such as passwords and tokens. Parameters to be filtered can be\n  added via the `:filter_parameters` option:\n\n      config :phoenix, :filter_parameters, [\"password\", \"secret\"]\n\n  With the configuration above, Phoenix will filter any parameter\n  that contains the terms `password` or `secret`. The match is\n  case sensitive.\n\n  Phoenix's default is `[\"password\"]`.\n\n  Phoenix can filter all parameters by default and selectively keep\n  parameters. This can be configured like so:\n\n      config :phoenix, :filter_parameters, {:keep, [\"id\", \"order\"]}\n\n  With the configuration above, Phoenix will filter all parameters,\n  except those that match exactly `id` or `order`. If a kept parameter\n  matches, all parameters nested under that one will also be kept.\n\n  ## Dynamic log level\n\n  In some cases you may wish to set the log level dynamically\n  on a per-request basis. To do so, set the `:log` option to\n  a tuple, `{Mod, Fun, Args}`. The `Plug.Conn.t()` for the\n  request will be prepended to the provided list of arguments.\n\n  When invoked, your function must return a\n  [`Logger.level()`](`t:Logger.level()/0`) or `false` to\n  disable logging for the request.\n\n  For example, in your Endpoint you might do something like this:\n\n        # lib/my_app_web/endpoint.ex\n        plug Plug.Telemetry,\n          event_prefix: [:phoenix, :endpoint],\n          log: {__MODULE__, :log_level, []}\n\n        # Disables logging for routes like /status/*\n        def log_level(%{path_info: [\"status\" | _]}), do: false\n        def log_level(_), do: :info\n\n  ## Disabling\n\n  When you are using custom logging system it is not always desirable to enable\n  `#{inspect(__MODULE__)}` by default. You can always disable this in general by:\n\n      config :phoenix, :logger, false\n  \"\"\"\n\n  require Logger\n\n  @doc false\n  def install do\n    handlers = %{\n      [:phoenix, :endpoint, :start] => &__MODULE__.phoenix_endpoint_start/4,\n      [:phoenix, :endpoint, :stop] => &__MODULE__.phoenix_endpoint_stop/4,\n      [:phoenix, :router_dispatch, :start] => &__MODULE__.phoenix_router_dispatch_start/4,\n      [:phoenix, :error_rendered] => &__MODULE__.phoenix_error_rendered/4,\n      [:phoenix, :socket_connected] => &__MODULE__.phoenix_socket_connected/4,\n      [:phoenix, :socket_drain] => &__MODULE__.phoenix_socket_drain/4,\n      [:phoenix, :channel_joined] => &__MODULE__.phoenix_channel_joined/4,\n      [:phoenix, :channel_handled_in] => &__MODULE__.phoenix_channel_handled_in/4\n    }\n\n    for {key, fun} <- handlers do\n      :telemetry.attach({__MODULE__, key}, key, fun, :ok)\n    end\n  end\n\n  @doc false\n  def duration(duration) do\n    duration = System.convert_time_unit(duration, :native, :microsecond)\n\n    if duration > 1000 do\n      [duration |> div(1000) |> Integer.to_string(), \"ms\"]\n    else\n      [Integer.to_string(duration), \"µs\"]\n    end\n  end\n\n  @doc false\n  def compile_filter({:compiled, _key, _value} = filter), do: filter\n  def compile_filter({:discard, params}), do: compile_discard(params)\n  def compile_filter({:keep, params}), do: {:keep, params}\n  def compile_filter(params), do: compile_discard(params)\n\n  defp compile_discard([]) do\n    {:compiled, [], []}\n  end\n\n  defp compile_discard(params) when is_list(params) or is_binary(params) do\n    key_match = :binary.compile_pattern(params)\n    value_match = params |> List.wrap() |> Enum.map(&(&1 <> \"=\")) |> :binary.compile_pattern()\n    {:compiled, key_match, value_match}\n  end\n\n  @doc false\n  def filter_values(values, filter \\\\ Application.get_env(:phoenix, :filter_parameters, [])) do\n    case compile_filter(filter) do\n      {:compiled, key_match, value_match} -> discard_values(values, key_match, value_match)\n      {:keep, match} -> keep_values(values, match)\n    end\n  end\n\n  defp discard_values(%{__struct__: mod} = struct, _key_match, _value_match) when is_atom(mod) do\n    struct\n  end\n\n  defp discard_values(%{} = map, key_match, value_match) do\n    Enum.into(map, %{}, fn {k, v} ->\n      cond do\n        is_binary(k) and String.contains?(k, key_match) ->\n          {k, \"[FILTERED]\"}\n\n        is_binary(v) and String.contains?(v, value_match) ->\n          {k, \"[FILTERED]\"}\n\n        true ->\n          {k, discard_values(v, key_match, value_match)}\n      end\n    end)\n  end\n\n  defp discard_values([_ | _] = list, key_match, value_match) do\n    Enum.map(list, &discard_values(&1, key_match, value_match))\n  end\n\n  defp discard_values(other, _key_match, _value_match), do: other\n\n  defp keep_values(%{__struct__: mod}, _match) when is_atom(mod), do: \"[FILTERED]\"\n\n  defp keep_values(%{} = map, match) do\n    Enum.into(map, %{}, fn {k, v} ->\n      if is_binary(k) and k in match do\n        {k, v}\n      else\n        {k, keep_values(v, match)}\n      end\n    end)\n  end\n\n  defp keep_values([_ | _] = list, match) do\n    Enum.map(list, &keep_values(&1, match))\n  end\n\n  defp keep_values(_other, _match), do: \"[FILTERED]\"\n\n  defp log_level(nil, _conn), do: :info\n  defp log_level(level, _conn) when is_atom(level), do: level\n\n  defp log_level({mod, fun, args}, conn) when is_atom(mod) and is_atom(fun) and is_list(args) do\n    apply(mod, fun, [conn | args])\n  end\n\n  ## Event: [:phoenix, :endpoint, *]\n\n  @doc false\n  def phoenix_endpoint_start(_, _, %{conn: conn} = metadata, _) do\n    case log_level(metadata[:options][:log], conn) do\n      false ->\n        :ok\n\n      level ->\n        Logger.log(level, fn ->\n          %{method: method, request_path: request_path} = conn\n          [method, ?\\s, request_path]\n        end)\n    end\n  end\n\n  @doc false\n  def phoenix_endpoint_stop(_, %{duration: duration}, %{conn: conn} = metadata, _) do\n    case log_level(metadata[:options][:log], conn) do\n      false ->\n        :ok\n\n      level ->\n        Logger.log(level, fn ->\n          %{status: status, state: state} = conn\n          status = status_to_string(status)\n          [connection_type(state), ?\\s, status, \" in \", duration(duration)]\n        end)\n    end\n  end\n\n  defp connection_type(:set_chunked), do: \"Chunked\"\n  defp connection_type(_), do: \"Sent\"\n\n  ## Event: [:phoenix, :error_rendered]\n\n  @doc false\n  def phoenix_error_rendered(_, _, %{log: false}, _), do: :ok\n\n  def phoenix_error_rendered(_, _, %{log: level, status: status, kind: kind, reason: reason}, _) do\n    Logger.log(level, fn ->\n      [\n        \"Converted \",\n        Atom.to_string(kind),\n        ?\\s,\n        error_banner(kind, reason),\n        \" to \",\n        status_to_string(status),\n        \" response\"\n      ]\n    end)\n  end\n\n  defp status_to_string(status) do\n    status |> Plug.Conn.Status.code() |> Integer.to_string()\n  end\n\n  defp error_banner(:error, %type{}), do: inspect(type)\n  defp error_banner(_kind, reason), do: inspect(reason)\n\n  ## Event: [:phoenix, :router_dispatch, :start]\n\n  @doc false\n  def phoenix_router_dispatch_start(_, _, %{log: false}, _), do: :ok\n\n  def phoenix_router_dispatch_start(_, _, metadata, _) do\n    %{log: level, conn: conn, plug: plug} = metadata\n    level = log_level(level, conn)\n\n    Logger.log(level, fn ->\n      %{\n        pipe_through: pipe_through,\n        plug_opts: plug_opts\n      } = metadata\n\n      log_mfa =\n        case metadata[:mfa] do\n          {mod, fun, arity} -> mfa(mod, fun, arity)\n          _ when is_atom(plug_opts) -> mfa(plug, plug_opts, 2)\n          _ -> inspect(plug)\n        end\n\n      [\n        \"Processing with \",\n        log_mfa,\n        ?\\n,\n        \"  Parameters: \",\n        params(conn.params),\n        ?\\n,\n        \"  Pipelines: \",\n        inspect(pipe_through)\n      ]\n    end)\n  end\n\n  defp mfa(mod, fun, arity),\n    do: [inspect(mod), ?., Atom.to_string(fun), ?/, arity + ?0]\n\n  defp params(%Plug.Conn.Unfetched{}), do: \"[UNFETCHED]\"\n  defp params(params), do: params |> filter_values() |> inspect()\n\n  ## Event: [:phoenix, :socket_connected]\n\n  @doc false\n  def phoenix_socket_connected(_, _, %{log: false}, _), do: :ok\n\n  def phoenix_socket_connected(_, %{duration: duration}, %{log: level} = meta, _) do\n    Logger.log(level, fn ->\n      %{\n        transport: transport,\n        params: params,\n        user_socket: user_socket,\n        result: result,\n        serializer: serializer\n      } = meta\n\n      [\n        connect_result(result),\n        inspect(user_socket),\n        \" in \",\n        duration(duration),\n        \"\\n  Transport: \",\n        inspect(transport),\n        \"\\n  Serializer: \",\n        inspect(serializer),\n        \"\\n  Parameters: \",\n        inspect(filter_values(params))\n      ]\n    end)\n  end\n\n  defp connect_result(:ok), do: \"CONNECTED TO \"\n  defp connect_result(:error), do: \"REFUSED CONNECTION TO \"\n\n  @doc false\n  def phoenix_socket_drain(_, _, %{log: false}, _), do: :ok\n\n  def phoenix_socket_drain(\n        _,\n        %{count: count, total: total, index: index, rounds: rounds},\n        %{log: level} = meta,\n        _\n      ) do\n    Logger.log(level, fn ->\n      %{socket: socket, interval: interval} = meta\n\n      [\n        \"DRAINING #{count} of #{total} total connection(s) for socket \",\n        inspect(socket),\n        \" every #{interval}ms - \",\n        \"round #{index} of #{rounds}\"\n      ]\n    end)\n  end\n\n  ## Event: [:phoenix, :channel_joined]\n\n  @doc false\n  def phoenix_channel_joined(_, %{duration: duration}, %{socket: socket} = metadata, _) do\n    channel_log(:log_join, socket, fn ->\n      %{result: result, params: params} = metadata\n\n      [\n        join_result(result),\n        socket.topic,\n        \" in \",\n        duration(duration),\n        \"\\n  Parameters: \",\n        inspect(filter_values(params))\n      ]\n    end)\n  end\n\n  defp join_result(:ok), do: \"JOINED \"\n  defp join_result(:error), do: \"REFUSED JOIN \"\n\n  ## Event: [:phoenix, :channel_handle_in]\n\n  @doc false\n  def phoenix_channel_handled_in(_, %{duration: duration}, %{socket: socket} = metadata, _) do\n    channel_log(:log_handle_in, socket, fn ->\n      %{event: event, params: params} = metadata\n\n      [\n        \"HANDLED \",\n        event,\n        \" INCOMING ON \",\n        socket.topic,\n        \" (\",\n        inspect(socket.channel),\n        \") in \",\n        duration(duration),\n        \"\\n  Parameters: \",\n        inspect(filter_values(params))\n      ]\n    end)\n  end\n\n  defp channel_log(_log_option, %{topic: \"phoenix\" <> _}, _fun), do: :ok\n\n  defp channel_log(log_option, %{private: private}, fun) do\n    if level = Map.get(private, log_option) do\n      Logger.log(level, fun)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/naming.ex",
    "content": "defmodule Phoenix.Naming do\n  @moduledoc \"\"\"\n  Conveniences for inflecting and working with names in Phoenix.\n  \"\"\"\n\n  @doc \"\"\"\n  Extracts the resource name from an alias.\n\n  ## Examples\n\n      iex> Phoenix.Naming.resource_name(MyApp.User)\n      \"user\"\n\n      iex> Phoenix.Naming.resource_name(MyApp.UserView, \"View\")\n      \"user\"\n\n  \"\"\"\n  @spec resource_name(String.Chars.t, String.t) :: String.t\n  def resource_name(alias, suffix \\\\ \"\") do\n    alias\n    |> to_string()\n    |> Module.split()\n    |> List.last()\n    |> unsuffix(suffix)\n    |> underscore()\n  end\n\n  @doc \"\"\"\n  Removes the given suffix from the name if it exists.\n\n  ## Examples\n\n      iex> Phoenix.Naming.unsuffix(\"MyApp.User\", \"View\")\n      \"MyApp.User\"\n\n      iex> Phoenix.Naming.unsuffix(\"MyApp.UserView\", \"View\")\n      \"MyApp.User\"\n\n  \"\"\"\n  @spec unsuffix(String.t, String.t) :: String.t\n  def unsuffix(value, suffix) do\n    string = to_string(value)\n    suffix_size = byte_size(suffix)\n    prefix_size = byte_size(string) - suffix_size\n    case string do\n      <<prefix::binary-size(^prefix_size), ^suffix::binary>> -> prefix\n      _ -> string\n    end\n  end\n\n  @doc \"\"\"\n  Converts a string to underscore case.\n\n  ## Examples\n\n      iex> Phoenix.Naming.underscore(\"MyApp\")\n      \"my_app\"\n\n  In general, `underscore` can be thought of as the reverse of\n  `camelize`, however, in some cases formatting may be lost:\n\n      Phoenix.Naming.underscore \"SAPExample\"  #=> \"sap_example\"\n      Phoenix.Naming.camelize   \"sap_example\" #=> \"SapExample\"\n\n  \"\"\"\n  @spec underscore(String.t) :: String.t\n\n  def underscore(value), do: Macro.underscore(value)\n\n  defp to_lower_char(char) when char in ?A..?Z, do: char + 32\n  defp to_lower_char(char), do: char\n\n  @doc \"\"\"\n  Converts a string to camel case.\n\n  Takes an optional `:lower` flag to return lowerCamelCase.\n\n  ## Examples\n\n      iex> Phoenix.Naming.camelize(\"my_app\")\n      \"MyApp\"\n\n      iex> Phoenix.Naming.camelize(\"my_app\", :lower)\n      \"myApp\"\n\n  In general, `camelize` can be thought of as the reverse of\n  `underscore`, however, in some cases formatting may be lost:\n\n      Phoenix.Naming.underscore \"SAPExample\"  #=> \"sap_example\"\n      Phoenix.Naming.camelize   \"sap_example\" #=> \"SapExample\"\n\n  \"\"\"\n  @spec camelize(String.t) :: String.t\n  def camelize(value), do: Macro.camelize(value)\n\n  @spec camelize(String.t, :lower) :: String.t\n  def camelize(\"\", :lower), do: \"\"\n  def camelize(<<?_, t :: binary>>, :lower) do\n    camelize(t, :lower)\n  end\n  def camelize(<<h, _t :: binary>> = value, :lower) do\n    <<_first, rest :: binary>> = camelize(value)\n    <<to_lower_char(h)>> <> rest\n  end\n\n  @doc \"\"\"\n  Converts an attribute/form field into its humanize version.\n\n  ## Examples\n\n      iex> Phoenix.Naming.humanize(:username)\n      \"Username\"\n      iex> Phoenix.Naming.humanize(:created_at)\n      \"Created at\"\n      iex> Phoenix.Naming.humanize(\"user_id\")\n      \"User\"\n\n  \"\"\"\n  @spec humanize(atom | String.t) :: String.t\n  def humanize(atom) when is_atom(atom),\n    do: humanize(Atom.to_string(atom))\n  def humanize(bin) when is_binary(bin) do\n    bin =\n      if String.ends_with?(bin, \"_id\") do\n        binary_part(bin, 0, byte_size(bin) - 3)\n      else\n        bin\n      end\n\n    bin |> String.replace(\"_\", \" \") |> String.capitalize\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/param.ex",
    "content": "defprotocol Phoenix.Param do\n  @moduledoc ~S\"\"\"\n  A protocol that converts data structures into URL parameters.\n\n  This protocol is used by `Phoenix.VerifiedRoutes` and other parts of the\n  Phoenix stack. For example, when you write:\n\n      ~p\"/user/#{@user}/edit\"\n\n  Phoenix knows how to extract the `:id` from `@user` thanks\n  to this protocol.\n\n  (Deprecated URL helpers, e.g. `user_path(conn, :edit, @user)`, work the\n  same way.)\n\n  By default, Phoenix implements this protocol for integers, binaries, atoms,\n  and structs. For structs, a key `:id` is assumed, but you may provide a\n  specific implementation.\n\n  The term `nil` cannot be converted to param.\n\n  ## Custom parameters\n\n  In order to customize the parameter for any struct,\n  one can simply implement this protocol. For example for a `Date` struct:\n\n      defimpl Phoenix.Param, for: Date do\n        def to_param(date) do\n          Date.to_string(date)\n        end\n      end\n\n  However, for convenience, this protocol can also be\n  derivable. For example:\n\n      defmodule User do\n        @derive Phoenix.Param\n        defstruct [:id, :username]\n      end\n\n  By default, the derived implementation will also use\n  the `:id` key. In case the user does not contain an\n  `:id` key, the key can be specified with an option:\n\n      defmodule User do\n        @derive {Phoenix.Param, key: :username}\n        defstruct [:username]\n      end\n\n  will automatically use `:username` in URLs.\n\n  When using Ecto, you must call `@derive` before\n  your `schema` call:\n\n      @derive {Phoenix.Param, key: :username}\n      schema \"users\" do\n\n  \"\"\"\n\n  @fallback_to_any true\n\n  @spec to_param(term) :: String.t()\n  def to_param(term)\nend\n\ndefimpl Phoenix.Param, for: Integer do\n  def to_param(int), do: Integer.to_string(int)\nend\n\ndefimpl Phoenix.Param, for: Float do\n  def to_param(float), do: Float.to_string(float)\nend\n\ndefimpl Phoenix.Param, for: BitString do\n  def to_param(bin) when is_binary(bin), do: bin\nend\n\ndefimpl Phoenix.Param, for: Atom do\n  def to_param(nil) do\n    raise ArgumentError, \"cannot convert nil to param\"\n  end\n\n  def to_param(atom) do\n    Atom.to_string(atom)\n  end\nend\n\ndefimpl Phoenix.Param, for: Map do\n  def to_param(map) do\n    raise ArgumentError,\n          \"maps cannot be converted to_param. A struct was expected, got: #{inspect(map)}\"\n  end\nend\n\ndefimpl Phoenix.Param, for: Any do\n  defmacro __deriving__(module, struct, options) do\n    key = Keyword.get(options, :key, :id)\n\n    unless Map.has_key?(struct, key) do\n      raise ArgumentError,\n            \"cannot derive Phoenix.Param for struct #{inspect(module)} \" <>\n              \"because it does not have key #{inspect(key)}. Please pass \" <>\n              \"the :key option when deriving\"\n    end\n\n    quote do\n      defimpl Phoenix.Param, for: unquote(module) do\n        def to_param(%{unquote(key) => nil}) do\n          raise ArgumentError,\n                \"cannot convert #{inspect(unquote(module))} to param, \" <>\n                  \"key #{inspect(unquote(key))} contains a nil value\"\n        end\n\n        def to_param(%{unquote(key) => key}) when is_integer(key), do: Integer.to_string(key)\n        def to_param(%{unquote(key) => key}) when is_binary(key), do: key\n        def to_param(%{unquote(key) => key}), do: Phoenix.Param.to_param(key)\n      end\n    end\n  end\n\n  def to_param(%{id: nil}) do\n    raise ArgumentError, \"cannot convert struct to param, key :id contains a nil value\"\n  end\n\n  def to_param(%{id: id}) when is_integer(id), do: Integer.to_string(id)\n  def to_param(%{id: id}) when is_binary(id), do: id\n  def to_param(%{id: id}), do: Phoenix.Param.to_param(id)\n\n  def to_param(map) when is_map(map) do\n    raise ArgumentError,\n          \"structs expect an :id key when converting to_param or a custom implementation \" <>\n            \"of the Phoenix.Param protocol (read Phoenix.Param docs for more information), \" <>\n            \"got: #{inspect(map)}\"\n  end\n\n  def to_param(data) do\n    raise Protocol.UndefinedError, protocol: @protocol, value: data\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/presence.ex",
    "content": "defmodule Phoenix.Presence do\n  @moduledoc \"\"\"\n  Provides Presence tracking to processes and channels.\n\n  This behaviour provides presence features such as fetching\n  presences for a given topic, as well as handling diffs of\n  join and leave events as they occur in real-time. Using this\n  module defines a supervisor and a module that implements the\n  `Phoenix.Tracker` behaviour that uses `Phoenix.PubSub` to\n  broadcast presence updates.\n\n  In case you want to use only a subset of the functionality\n  provided by `Phoenix.Presence`, such as tracking processes\n  but without broadcasting updates, we recommend that you look\n  at the `Phoenix.Tracker` functionality from the `phoenix_pubsub`\n  project.\n\n  ## Example Usage\n\n  Start by defining a presence module within your application\n  which uses `Phoenix.Presence` and provide the `:otp_app` which\n  holds your configuration, as well as the `:pubsub_server`.\n\n      defmodule MyAppWeb.Presence do\n        use Phoenix.Presence,\n          otp_app: :my_app,\n          pubsub_server: MyApp.PubSub\n      end\n\n  The `:pubsub_server` must point to an existing pubsub server\n  running in your application, which is included by default as\n  `MyApp.PubSub` for new applications.\n\n  Next, add the new supervisor to your supervision tree in\n  `lib/my_app/application.ex`. It must be after the PubSub child\n  and before the endpoint:\n\n      children = [\n        ...\n        {Phoenix.PubSub, name: MyApp.PubSub},\n        MyAppWeb.Presence,\n        MyAppWeb.Endpoint\n      ]\n\n  Once added, presences can be tracked in your channel after joining:\n\n      defmodule MyAppWeb.MyChannel do\n        use MyAppWeb, :channel\n        alias MyAppWeb.Presence\n\n        def join(\"some:topic\", _params, socket) do\n          send(self(), :after_join)\n          {:ok, assign(socket, :user_id, ...)}\n        end\n\n        def handle_info(:after_join, socket) do\n          {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{\n            online_at: inspect(System.system_time(:second))\n          })\n\n          push(socket, \"presence_state\", Presence.list(socket))\n          {:noreply, socket}\n        end\n      end\n\n  In the example above, `Presence.track` is used to register this channel's process as a\n  presence for the socket's user ID, with a map of metadata.\n  Next, the current presence information for\n  the socket's topic is pushed to the client as a `\"presence_state\"` event.\n\n  Finally, a diff of presence join and leave events will be sent to the\n  client as they happen in real-time with the \"presence_diff\" event.\n  The diff structure will be a map of `:joins` and `:leaves` of the form:\n\n      %{\n        joins: %{\"123\" => %{metas: [%{status: \"away\", phx_ref: ...}]}},\n        leaves: %{\"456\" => %{metas: [%{status: \"online\", phx_ref: ...}]}}\n      },\n\n  See `c:list/1` for more information on the presence data structure.\n\n  ## Custom dispatcher\n\n  It's possible to customize the dispatcher module used to broadcast.\n  By default, `Phoenix.Channel.Server` is used, which is the same dispatcher\n  used by channels. To customize the dispatcher, pass the `:dispatcher` option\n  when using `Phoenix.Presence`:\n\n      use Phoenix.Presence,\n        otp_app: :my_app,\n        pubsub_server: MyApp.PubSub,\n        dispatcher: MyApp.CustomDispatcher\n\n  See `m:Phoenix.PubSub#module-custom-dispatching` for more information on\n  custom dispatchers.\n\n  ## Fetching Presence Information\n\n  Presence metadata should be minimized and used to store small,\n  ephemeral state, such as a user's \"online\" or \"away\" status.\n  More detailed information, such as user details that need to be fetched\n  from the database, can be achieved by overriding the `c:fetch/2` function.\n\n  The `c:fetch/2` callback is triggered when using `c:list/1` and on\n  every update, and it serves as a mechanism to fetch presence information\n  a single time, before broadcasting the information to all channel subscribers.\n  This prevents N query problems and gives you a single place to group\n  isolated data fetching to extend presence metadata.\n\n  The function must return a map of data matching the outlined Presence\n  data structure, including the `:metas` key, but can extend the map of\n  information to include any additional information. For example:\n\n      def fetch(_topic, presences) do\n        users = presences |> Map.keys() |> Accounts.get_users_map()\n\n        for {key, %{metas: metas}} <- presences, into: %{} do\n          {key, %{metas: metas, user: users[String.to_integer(key)]}}\n        end\n      end\n\n  Where `Account.get_users_map/1` could be implemented like:\n\n      def get_users_map(ids) do\n        query =\n          from u in User,\n            where: u.id in ^ids,\n            select: {u.id, u}\n\n        query |> Repo.all() |> Enum.into(%{})\n      end\n\n  The `fetch/2` function above fetches all users from the database who\n  have registered presences for the given topic. The presences\n  information is then extended with a `:user` key of the user's\n  information, while maintaining the required `:metas` field from the\n  original presence data.\n\n  ## Using Elixir as a Presence Client\n\n  Presence is great for external clients, such as JavaScript applications, but\n  it can also be used from an Elixir client process to keep track of presence\n  changes as they happen on the server. This can be accomplished by implementing\n  the optional [`init/1`](`c:init/1`) and [`handle_metas/4`](`c:handle_metas/4`)\n  callbacks on your presence module. For example, the following callback\n  receives presence metadata changes, and broadcasts to other Elixir processes\n  about users joining and leaving:\n\n      defmodule MyApp.Presence do\n        use Phoenix.Presence,\n          otp_app: :my_app,\n          pubsub_server: MyApp.PubSub\n\n        def init(_opts) do\n          {:ok, %{}} # user-land state\n        end\n\n        def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do\n          # fetch existing presence information for the joined users and broadcast the\n          # event to all subscribers\n          for {user_id, presence} <- joins do\n            user_data = %{user: presence.user, metas: Map.fetch!(presences, user_id)}\n            msg = {MyApp.PresenceClient, {:join, user_data}}\n            Phoenix.PubSub.local_broadcast(MyApp.PubSub, topic, msg)\n          end\n\n          # fetch existing presence information for the left users and broadcast the\n          # event to all subscribers\n          for {user_id, presence} <- leaves do\n            metas =\n              case Map.fetch(presences, user_id) do\n                {:ok, presence_metas} -> presence_metas\n                :error -> []\n              end\n\n            user_data = %{user: presence.user, metas: metas}\n            msg = {MyApp.PresenceClient, {:leave, user_data}}\n            Phoenix.PubSub.local_broadcast(MyApp.PubSub, topic, msg)\n          end\n\n          {:ok, state}\n        end\n      end\n\n  The `handle_metas/4` callback receives the topic, presence diff, current presences\n  for the topic with their metadata, and any user-land state accumulated from init and\n  subsequent `handle_metas/4` calls. In our example implementation, we walk the `:joins` and\n  `:leaves` in the diff, and populate a complete presence from our known presence information.\n  Then we broadcast to the local node subscribers about user joins and leaves.\n\n  ## Testing with Presence\n\n  Every time the `fetch` callback is invoked, it is done from a separate\n  process. Given those processes run asynchronously, it is often necessary\n  to guarantee they have been shutdown at the end of every test. This can\n  be done by using ExUnit's `on_exit` hook plus `fetchers_pids` function:\n\n      on_exit(fn ->\n        for pid <- MyAppWeb.Presence.fetchers_pids() do\n          ref = Process.monitor(pid)\n          assert_receive {:DOWN, ^ref, _, _, _}, 1000\n        end\n      end)\n\n  \"\"\"\n\n  @type presences :: %{String.t() => %{metas: [map()]}}\n  @type presence :: %{key: String.t(), meta: map()}\n  @type topic :: String.t()\n\n  @doc \"\"\"\n  Track a channel's process as a presence.\n\n  Tracked presences are grouped by `key`, cast as a string. For example, to\n  group each user's channels together, use user IDs as keys. Each presence can\n  be associated with a map of metadata to store small, ephemeral state, such as\n  a user's online status. To store detailed information, see `c:fetch/2`.\n\n  ## Example\n\n      alias MyApp.Presence\n      def handle_info(:after_join, socket) do\n        {:ok, _} = Presence.track(socket, socket.assigns.user_id, %{\n          online_at: inspect(System.system_time(:second))\n        })\n        {:noreply, socket}\n      end\n\n  \"\"\"\n  @callback track(socket :: Phoenix.Socket.t(), key :: String.t(), meta :: map()) ::\n              {:ok, ref :: binary()}\n              | {:error, reason :: term()}\n\n  @doc \"\"\"\n  Track an arbitrary process as a presence.\n\n  Same with `track/3`, except track any process by `topic` and `key`.\n  \"\"\"\n  @callback track(pid, topic, key :: String.t(), meta :: map()) ::\n              {:ok, ref :: binary()}\n              | {:error, reason :: term()}\n\n  @doc \"\"\"\n  Stop tracking a channel's process.\n  \"\"\"\n  @callback untrack(socket :: Phoenix.Socket.t(), key :: String.t()) :: :ok\n\n  @doc \"\"\"\n  Stop tracking a process.\n  \"\"\"\n  @callback untrack(pid, topic, key :: String.t()) :: :ok\n\n  @doc \"\"\"\n  Update a channel presence's metadata.\n\n  Replace a presence's metadata by passing a new map or a function that takes\n  the current map and returns a new one.\n  \"\"\"\n  @callback update(\n              socket :: Phoenix.Socket.t(),\n              key :: String.t(),\n              meta :: map() | (map() -> map())\n            ) ::\n              {:ok, ref :: binary()}\n              | {:error, reason :: term()}\n\n  @doc \"\"\"\n  Update a process presence's metadata.\n\n  Same as `update/3`, but with an arbitrary process.\n  \"\"\"\n  @callback update(pid, topic, key :: String.t(), meta :: map() | (map() -> map())) ::\n              {:ok, ref :: binary()}\n              | {:error, reason :: term()}\n\n  @doc \"\"\"\n  Returns presences for a socket/topic.\n\n  ## Presence data structure\n\n  The presence information is returned as a map with presences grouped\n  by key, cast as a string, and accumulated metadata, with the following form:\n\n      %{key => %{metas: [%{phx_ref: ..., ...}, ...]}}\n\n  For example, imagine a user with id `123` online from two\n  different devices, as well as a user with id `456` online from\n  just one device. The following presence information might be returned:\n\n      %{\"123\" => %{metas: [%{status: \"away\", phx_ref: ...},\n                           %{status: \"online\", phx_ref: ...}]},\n        \"456\" => %{metas: [%{status: \"online\", phx_ref: ...}]}}\n\n  The keys of the map will usually point to a resource ID. The value\n  will contain a map with a `:metas` key containing a list of metadata\n  for each resource. Additionally, every metadata entry will contain a\n  `:phx_ref` key which can be used to uniquely identify metadata for a\n  given key. In the event that the metadata was previously updated,\n  a `:phx_ref_prev` key will be present containing the previous\n  `:phx_ref` value.\n  \"\"\"\n  @callback list(socket_or_topic :: Phoenix.Socket.t() | topic) :: presences\n\n  @doc \"\"\"\n  Returns the map of presence metadata for a socket/topic-key pair.\n\n  ## Examples\n\n  Uses the same data format as each presence in `c:list/1`, but only\n  returns metadata for the presences under a topic and key pair. For example,\n  a user with key `\"user1\"`, connected to the same chat room `\"room:1\"` from two\n  devices, could return:\n\n      iex> MyPresence.get_by_key(\"room:1\", \"user1\")\n      [%{name: \"User 1\", metas: [%{device: \"Desktop\"}, %{device: \"Mobile\"}]}]\n\n  Like `c:list/1`, the presence metadata is passed to the `fetch`\n  callback of your presence module to fetch any additional information.\n  \"\"\"\n  @callback get_by_key(Phoenix.Socket.t() | topic, key :: String.t()) :: [presence]\n\n  @doc \"\"\"\n  Extend presence information with additional data.\n\n  When `c:list/1` is used to list all presences of the given `topic`, this\n  callback is triggered once to modify the result before it is broadcasted to\n  all channel subscribers. This avoids N query problems and provides a single\n  place to extend presence metadata. You must return a map of data matching the\n  original result, including the `:metas` key, but can extend the map to include\n  any additional information.\n\n  The default implementation simply passes `presences` through unchanged.\n\n  ## Example\n\n      def fetch(_topic, presences) do\n        query =\n          from u in User,\n            where: u.id in ^Map.keys(presences),\n            select: {u.id, u}\n\n        users = query |> Repo.all() |> Enum.into(%{})\n        for {key, %{metas: metas}} <- presences, into: %{} do\n          {key, %{metas: metas, user: users[key]}}\n        end\n      end\n\n  \"\"\"\n  @callback fetch(topic, presences) :: presences\n\n  @doc \"\"\"\n  Initializes the presence client state.\n\n  Invoked when your presence module starts, allows dynamically\n  providing initial state for handling presence metadata.\n  \"\"\"\n  @callback init(state :: term) :: {:ok, new_state :: term}\n\n  @doc \"\"\"\n  Receives presence metadata changes.\n  \"\"\"\n  @callback handle_metas(topic :: String.t(), diff :: map(), presences :: map(), state :: term) ::\n              {:ok, term}\n\n  @optional_callbacks init: 1, handle_metas: 4\n\n  defmacro __using__(opts) do\n    quote location: :keep, bind_quoted: [opts: opts] do\n      @behaviour Phoenix.Presence\n      @opts opts\n      @task_supervisor Module.concat(__MODULE__, \"TaskSupervisor\")\n\n      _ = opts[:otp_app] || raise \"use Phoenix.Presence expects :otp_app to be given\"\n\n      # User defined\n      def fetch(_topic, presences), do: presences\n      defoverridable fetch: 2\n\n      # Private\n\n      def child_spec(opts) do\n        opts = Keyword.merge(@opts, opts)\n\n        %{\n          id: __MODULE__,\n          start: {Phoenix.Presence, :start_link, [__MODULE__, @task_supervisor, opts]},\n          type: :supervisor\n        }\n      end\n\n      # API\n\n      def track(%Phoenix.Socket{} = socket, key, meta) do\n        track(socket.channel_pid, socket.topic, key, meta)\n      end\n\n      def track(pid, topic, key, meta) do\n        Phoenix.Tracker.track(__MODULE__, pid, topic, key, meta)\n      end\n\n      def untrack(%Phoenix.Socket{} = socket, key) do\n        untrack(socket.channel_pid, socket.topic, key)\n      end\n\n      def untrack(pid, topic, key) do\n        Phoenix.Tracker.untrack(__MODULE__, pid, topic, key)\n      end\n\n      def update(%Phoenix.Socket{} = socket, key, meta) do\n        update(socket.channel_pid, socket.topic, key, meta)\n      end\n\n      def update(pid, topic, key, meta) do\n        Phoenix.Tracker.update(__MODULE__, pid, topic, key, meta)\n      end\n\n      def list(%Phoenix.Socket{topic: topic}), do: list(topic)\n      def list(topic), do: Phoenix.Presence.list(__MODULE__, topic)\n\n      def get_by_key(%Phoenix.Socket{topic: topic}, key), do: get_by_key(topic, key)\n      def get_by_key(topic, key), do: Phoenix.Presence.get_by_key(__MODULE__, topic, key)\n\n      def fetchers_pids(), do: Task.Supervisor.children(@task_supervisor)\n    end\n  end\n\n  defmodule Tracker do\n    @moduledoc false\n    use Phoenix.Tracker\n\n    def start_link({module, task_supervisor, opts}) do\n      pubsub_server =\n        opts[:pubsub_server] || raise \"use Phoenix.Presence expects :pubsub_server to be given\"\n\n      dispatcher = opts[:dispatcher] || Phoenix.Channel.Server\n\n      Phoenix.Tracker.start_link(\n        __MODULE__,\n        {module, task_supervisor, pubsub_server, dispatcher},\n        opts\n      )\n    end\n\n    def init(state), do: Phoenix.Presence.init(state)\n\n    def handle_diff(diff, state), do: Phoenix.Presence.handle_diff(diff, state)\n\n    def handle_info(msg, state),\n      do: Phoenix.Presence.handle_info(msg, state)\n  end\n\n  @doc false\n  def start_link(module, task_supervisor, opts) do\n    otp_app = opts[:otp_app]\n\n    opts =\n      opts\n      |> Keyword.merge(Application.get_env(otp_app, module, []))\n      |> Keyword.put(:name, module)\n\n    children = [\n      {Task.Supervisor, name: task_supervisor},\n      {Tracker, {module, task_supervisor, opts}}\n    ]\n\n    sup_opts = [\n      strategy: :rest_for_one,\n      name: Module.concat(module, \"Supervisor\")\n    ]\n\n    Supervisor.start_link(children, sup_opts)\n  end\n\n  @doc false\n  def init({module, task_supervisor, pubsub_server, dispatcher}) do\n    state = %{\n      module: module,\n      task_supervisor: task_supervisor,\n      pubsub_server: pubsub_server,\n      topics: %{},\n      tasks: :queue.new(),\n      current_task: nil,\n      client_state: nil,\n      dispatcher: dispatcher\n    }\n\n    client_state =\n      if function_exported?(module, :handle_metas, 4) do\n        unless function_exported?(module, :init, 1) do\n          raise ArgumentError, \"\"\"\n          missing #{inspect(module)}.init/1 callback for client state\n\n          When you implement the handle_metas/4 callback, you must also\n          implement init/1. For example, add the following to\n          #{inspect(module)}:\n\n          def init(_opts), do: {:ok, %{}}\n\n          \"\"\"\n        end\n\n        case module.init(%{}) do\n          {:ok, client_state} ->\n            client_state\n\n          other ->\n            raise ArgumentError, \"\"\"\n            expected #{inspect(module)}.init/1 to return {:ok, state}, got: #{inspect(other)}\n            \"\"\"\n        end\n      end\n\n    {:ok, %{state | client_state: client_state}}\n  end\n\n  @doc false\n  def handle_diff(diff, state) do\n    {:ok, async_merge(state, diff)}\n  end\n\n  @doc false\n  def handle_info({task_ref, {:phoenix, ref, computed_diffs}}, state) do\n    %{current_task: current_task} = state\n    {^ref, %Task{ref: ^task_ref} = task} = current_task\n    Task.shutdown(task)\n\n    Enum.each(computed_diffs, fn {topic, presence_diff} ->\n      broadcast = %Phoenix.Socket.Broadcast{\n        topic: topic,\n        event: \"presence_diff\",\n        payload: presence_diff\n      }\n\n      Phoenix.PubSub.local_broadcast(state.pubsub_server, topic, broadcast, state.dispatcher)\n    end)\n\n    new_state =\n      if function_exported?(state.module, :handle_metas, 4) do\n        do_handle_metas(state, computed_diffs)\n      else\n        state\n      end\n\n    {:noreply, next_task(new_state)}\n  end\n\n  @doc false\n  def list(module, topic) do\n    grouped =\n      module\n      |> Phoenix.Tracker.list(topic)\n      |> group()\n\n    module.fetch(topic, grouped)\n  end\n\n  @doc false\n  def get_by_key(module, topic, key) do\n    string_key = to_string(key)\n\n    case Phoenix.Tracker.get_by_key(module, topic, key) do\n      [] ->\n        []\n\n      [_ | _] = pid_metas ->\n        metas = Enum.map(pid_metas, fn {_pid, meta} -> meta end)\n        %{^string_key => fetched_metas} = module.fetch(topic, %{string_key => %{metas: metas}})\n        fetched_metas\n    end\n  end\n\n  @doc false\n  def group(presences) do\n    presences\n    |> Enum.reverse()\n    |> Enum.reduce(%{}, fn {key, meta}, acc ->\n      Map.update(acc, to_string(key), %{metas: [meta]}, fn %{metas: metas} ->\n        %{metas: [meta | metas]}\n      end)\n    end)\n  end\n\n  defp send_continue(%Task{} = task, ref), do: send(task.pid, {ref, :continue})\n\n  defp next_task(state) do\n    case :queue.out(state.tasks) do\n      {{:value, {ref, %Task{} = next}}, remaining_tasks} ->\n        send_continue(next, ref)\n        %{state | current_task: {ref, next}, tasks: remaining_tasks}\n\n      {:empty, _} ->\n        %{state | current_task: nil, tasks: :queue.new()}\n    end\n  end\n\n  defp do_handle_metas(state, computed_diffs) do\n    Enum.reduce(computed_diffs, state, fn {topic, presence_diff}, acc ->\n      updated_topics = merge_diff(acc.topics, topic, presence_diff)\n\n      topic_presences =\n        case Map.fetch(updated_topics, topic) do\n          {:ok, presences} -> presences\n          :error -> %{}\n        end\n\n      case acc.module.handle_metas(topic, presence_diff, topic_presences, acc.client_state) do\n        {:ok, updated_client_state} ->\n          %{acc | topics: updated_topics, client_state: updated_client_state}\n\n        other ->\n          raise ArgumentError, \"\"\"\n          expected #{inspect(acc.module)}.handle_metas/4 to return {:ok, new_state}.\n\n            got: #{inspect(other)}\n          \"\"\"\n      end\n    end)\n  end\n\n  defp async_merge(state, diff) do\n    %{module: module} = state\n    ref = make_ref()\n\n    new_task =\n      Task.Supervisor.async(state.task_supervisor, fn ->\n        computed_diffs =\n          Enum.map(diff, fn {topic, {joins, leaves}} ->\n            joins = module.fetch(topic, Phoenix.Presence.group(joins))\n            leaves = module.fetch(topic, Phoenix.Presence.group(leaves))\n            {topic, %{joins: joins, leaves: leaves}}\n          end)\n\n        receive do\n          {^ref, :continue} -> {:phoenix, ref, computed_diffs}\n        end\n      end)\n\n    if state.current_task do\n      %{state | tasks: :queue.in({ref, new_task}, state.tasks)}\n    else\n      send_continue(new_task, ref)\n      %{state | current_task: {ref, new_task}}\n    end\n  end\n\n  defp merge_diff(topics, topic, %{leaves: leaves, joins: joins} = _diff) do\n    # add new topic if needed\n    updated_topics =\n      if Map.has_key?(topics, topic) do\n        topics\n      else\n        add_new_topic(topics, topic)\n      end\n\n    # merge diff into topics\n    {updated_topics, _topic} = Enum.reduce(joins, {updated_topics, topic}, &handle_join/2)\n    {updated_topics, _topic} = Enum.reduce(leaves, {updated_topics, topic}, &handle_leave/2)\n\n    # if no more presences for given topic, remove topic\n    if topic_presences_count(updated_topics, topic) == 0 do\n      remove_topic(updated_topics, topic)\n    else\n      updated_topics\n    end\n  end\n\n  defp handle_join({joined_key, presence}, {topics, topic}) do\n    joined_metas = Map.get(presence, :metas, [])\n    {add_new_presence_or_metas(topics, topic, joined_key, joined_metas), topic}\n  end\n\n  defp handle_leave({left_key, presence}, {topics, topic}) do\n    {remove_presence_or_metas(topics, topic, left_key, presence), topic}\n  end\n\n  defp add_new_presence_or_metas(\n         topics,\n         topic,\n         key,\n         new_metas\n       ) do\n    topic_presences = topics[topic]\n\n    updated_topic =\n      case Map.fetch(topic_presences, key) do\n        # existing presence, add new metas\n        {:ok, existing_metas} ->\n          remaining_metas = new_metas -- existing_metas\n          updated_metas = existing_metas ++ remaining_metas\n          Map.put(topic_presences, key, updated_metas)\n\n        # there are no presences for that key\n        :error ->\n          Map.put_new(topic_presences, key, new_metas)\n      end\n\n    Map.put(topics, topic, updated_topic)\n  end\n\n  defp remove_presence_or_metas(\n         topics,\n         topic,\n         key,\n         deleted_metas\n       ) do\n    topic_presences = topics[topic]\n    presence_metas = Map.get(topic_presences, key, [])\n    remaining_metas = presence_metas -- Map.get(deleted_metas, :metas, [])\n\n    updated_topic =\n      case remaining_metas do\n        [] -> Map.delete(topic_presences, key)\n        _ -> Map.put(topic_presences, key, remaining_metas)\n      end\n\n    Map.put(topics, topic, updated_topic)\n  end\n\n  defp add_new_topic(topics, topic) do\n    Map.put_new(topics, topic, %{})\n  end\n\n  defp remove_topic(topics, topic) do\n    Map.delete(topics, topic)\n  end\n\n  defp topic_presences_count(topics, topic) do\n    map_size(topics[topic])\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/router/console_formatter.ex",
    "content": "defmodule Phoenix.Router.ConsoleFormatter do\n  @moduledoc false\n\n  @doc \"\"\"\n  Format the routes for printing.\n  \"\"\"\n\n  @socket_verb \"WS\"\n\n  @longpoll_verbs [\"GET\", \"POST\"]\n\n  def format(router, endpoint \\\\ nil) do\n    routes = router.formatted_routes([])\n\n    column_widths = calculate_column_widths(router, routes, endpoint)\n\n    IO.iodata_to_binary([\n      Enum.map(routes, &format_route(&1, router, column_widths)),\n      format_endpoint(endpoint, column_widths)\n    ])\n  end\n\n  defp format_endpoint(nil, _router), do: \"\"\n\n  defp format_endpoint(endpoint, widths) do\n    case endpoint.__sockets__() do\n      [] ->\n        \"\"\n\n      sockets ->\n        Enum.map(sockets, fn socket ->\n          [format_websocket(socket, widths), format_longpoll(socket, widths)]\n        end)\n    end\n  end\n\n  defp format_websocket({_path, Phoenix.LiveReloader.Socket, _opts}, _), do: \"\"\n\n  defp format_websocket({path, module, opts}, widths) do\n    if opts[:websocket] != false do\n      {verb_len, path_len, route_name_len} = widths\n\n      String.duplicate(\" \", route_name_len) <>\n        \"  \" <>\n        String.pad_trailing(@socket_verb, verb_len) <>\n        \"  \" <>\n        String.pad_trailing(path <> \"/websocket\", path_len) <>\n        \"  \" <>\n        inspect(module) <>\n        \"\\n\"\n    else\n      \"\"\n    end\n  end\n\n  defp format_longpoll({_path, Phoenix.LiveReloader.Socket, _opts}, _), do: \"\"\n\n  defp format_longpoll({path, module, opts}, widths) do\n    if opts[:longpoll] != false do\n      for method <- @longpoll_verbs, into: \"\" do\n        {verb_len, path_len, route_name_len} = widths\n\n        String.duplicate(\" \", route_name_len) <>\n          \"  \" <>\n          String.pad_trailing(method, verb_len) <>\n          \"  \" <>\n          String.pad_trailing(path <> \"/longpoll\", path_len) <>\n          \"  \" <>\n          inspect(module) <>\n          \"\\n\"\n      end\n    else\n      \"\"\n    end\n  end\n\n  defp calculate_column_widths(router, routes, endpoint) do\n    sockets = (endpoint && endpoint.__sockets__()) || []\n\n    widths =\n      Enum.reduce(routes, {0, 0, 0}, fn route, acc ->\n        %{verb: verb, path: path, helper: helper} = route\n        verb = verb_name(verb)\n        {verb_len, path_len, route_name_len} = acc\n        route_name = route_name(router, helper)\n\n        {max(verb_len, String.length(verb)), max(path_len, String.length(path)),\n         max(route_name_len, String.length(route_name))}\n      end)\n\n    Enum.reduce(sockets, widths, fn {path, _mod, opts}, acc ->\n      {verb_len, path_len, route_name_len} = acc\n\n      verb_length =\n        socket_verbs(opts)\n        |> Enum.map(&String.length/1)\n        |> Enum.max(&>=/2, fn -> 0 end)\n\n      {max(verb_len, verb_length), max(path_len, String.length(path <> \"/websocket\")),\n       route_name_len}\n    end)\n  end\n\n  defp format_route(route, router, column_widths) do\n    %{\n      verb: verb,\n      path: path,\n      label: label\n    } = route\n\n    verb = verb_name(verb)\n    route_name = route_name(router, Map.get(route, :helper))\n    {verb_len, path_len, route_name_len} = column_widths\n\n    String.pad_leading(route_name, route_name_len) <>\n      \"  \" <>\n      String.pad_trailing(verb, verb_len) <>\n      \"  \" <>\n      String.pad_trailing(path, path_len) <>\n      \"  \" <>\n      label <> \"\\n\"\n  end\n\n  defp route_name(_router, nil), do: \"\"\n\n  defp route_name(router, name) do\n    if router.__helpers__() do\n      name <> \"_path\"\n    else\n      \"\"\n    end\n  end\n\n  defp verb_name(verb), do: verb |> to_string() |> String.upcase()\n\n  defp socket_verbs(socket_opts) do\n    if socket_opts[:longpoll] != false do\n      [@socket_verb | @longpoll_verbs]\n    else\n      [@socket_verb]\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/router/helpers.ex",
    "content": "defmodule Phoenix.Router.Helpers do\n  # Module that generates the routing helpers.\n  @moduledoc false\n\n  alias Phoenix.Router.Route\n  alias Plug.Conn\n\n  @doc \"\"\"\n  Generates the helper module for the given environment and routes.\n  \"\"\"\n  def define(env, routes) do\n    # Ignore any route without helper or forwards.\n    routes =\n      Enum.reject(routes, fn {route, _exprs} ->\n        is_nil(route.helper) or route.kind == :forward\n      end)\n\n    trailing_slash? = Enum.any?(routes, fn {route, _} -> route.trailing_slash? end)\n    groups = Enum.group_by(routes, fn {route, _exprs} -> route.helper end)\n\n    impls =\n      for {_helper, helper_routes} <- groups,\n          {_, [{route, exprs} | _]} <-\n            helper_routes\n            |> Enum.group_by(fn {route, exprs} -> [length(exprs.binding) | route.plug_opts] end)\n            |> Enum.sort(),\n          do: defhelper(route, exprs)\n\n    catch_all = Enum.map(groups, &defhelper_catch_all/1)\n\n    defhelper =\n      quote generated: true, unquote: false do\n        defhelper = fn helper, vars, opts, bins, segs, trailing_slash? ->\n          def unquote(:\"#{helper}_path\")(\n                conn_or_endpoint,\n                unquote(Macro.escape(opts)),\n                unquote_splicing(vars)\n              ) do\n            unquote(:\"#{helper}_path\")(\n              conn_or_endpoint,\n              unquote(Macro.escape(opts)),\n              unquote_splicing(vars),\n              []\n            )\n          end\n\n          def unquote(:\"#{helper}_path\")(\n                conn_or_endpoint,\n                unquote(Macro.escape(opts)),\n                unquote_splicing(vars),\n                params\n              )\n              when is_list(params) or is_map(params) do\n            path(\n              conn_or_endpoint,\n              segments(\n                unquote(segs),\n                params,\n                unquote(bins),\n                unquote(trailing_slash?),\n                {unquote(helper), unquote(Macro.escape(opts)),\n                 unquote(Enum.map(vars, &Macro.to_string/1))}\n              )\n            )\n          end\n\n          def unquote(:\"#{helper}_url\")(\n                conn_or_endpoint,\n                unquote(Macro.escape(opts)),\n                unquote_splicing(vars)\n              ) do\n            unquote(:\"#{helper}_url\")(\n              conn_or_endpoint,\n              unquote(Macro.escape(opts)),\n              unquote_splicing(vars),\n              []\n            )\n          end\n\n          def unquote(:\"#{helper}_url\")(\n                conn_or_endpoint,\n                unquote(Macro.escape(opts)),\n                unquote_splicing(vars),\n                params\n              )\n              when is_list(params) or is_map(params) do\n            url(conn_or_endpoint) <>\n              unquote(:\"#{helper}_path\")(\n                conn_or_endpoint,\n                unquote(Macro.escape(opts)),\n                unquote_splicing(vars),\n                params\n              )\n          end\n        end\n      end\n\n    defcatch_all =\n      quote generated: true, unquote: false do\n        defcatch_all = fn helper, binding_lengths, params_lengths, routes ->\n          for length <- binding_lengths do\n            binding = List.duplicate({:_, [], nil}, length)\n            arity = length + 2\n\n            def unquote(:\"#{helper}_path\")(conn_or_endpoint, action, unquote_splicing(binding)) do\n              path(conn_or_endpoint, \"/\")\n              raise_route_error(unquote(helper), :path, unquote(arity), action, [])\n            end\n\n            def unquote(:\"#{helper}_url\")(conn_or_endpoint, action, unquote_splicing(binding)) do\n              url(conn_or_endpoint)\n              raise_route_error(unquote(helper), :url, unquote(arity), action, [])\n            end\n          end\n\n          for length <- params_lengths do\n            binding = List.duplicate({:_, [], nil}, length)\n            arity = length + 2\n\n            def unquote(:\"#{helper}_path\")(\n                  conn_or_endpoint,\n                  action,\n                  unquote_splicing(binding),\n                  params\n                ) do\n              path(conn_or_endpoint, \"/\")\n              raise_route_error(unquote(helper), :path, unquote(arity + 1), action, params)\n            end\n\n            def unquote(:\"#{helper}_url\")(\n                  conn_or_endpoint,\n                  action,\n                  unquote_splicing(binding),\n                  params\n                ) do\n              url(conn_or_endpoint)\n              raise_route_error(unquote(helper), :url, unquote(arity + 1), action, params)\n            end\n          end\n\n          defp raise_route_error(unquote(helper), suffix, arity, action, params) do\n            Phoenix.Router.Helpers.raise_route_error(\n              __MODULE__,\n              \"#{unquote(helper)}_#{suffix}\",\n              arity,\n              action,\n              unquote(Macro.escape(routes)),\n              params\n            )\n          end\n        end\n      end\n\n    # It is in general bad practice to generate large chunks of code\n    # inside quoted expressions. However, we can get away with this\n    # here for two reasons:\n    #\n    # * Helper modules are quite uncommon, typically one per project.\n    #\n    # * We inline most of the code for performance, so it is specific\n    #   per helper module anyway.\n    #\n    code =\n      quote do\n        @moduledoc false\n        unquote(defhelper)\n        unquote(defcatch_all)\n        unquote_splicing(impls)\n        unquote_splicing(catch_all)\n\n        @doc \"\"\"\n        Generates the path information including any necessary prefix.\n        \"\"\"\n        def path(data, path) do\n          Phoenix.VerifiedRoutes.unverified_path(data, unquote(env.module), path)\n        end\n\n        @doc \"\"\"\n        Generates the connection/endpoint base URL without any path information.\n        \"\"\"\n        def url(data) do\n          Phoenix.VerifiedRoutes.unverified_url(data, \"\")\n        end\n\n        @doc \"\"\"\n        Generates path to a static asset given its file path.\n        \"\"\"\n        def static_path(conn_or_endpoint_ctx, path) do\n          Phoenix.VerifiedRoutes.static_path(conn_or_endpoint_ctx, path)\n        end\n\n        @doc \"\"\"\n        Generates url to a static asset given its file path.\n        \"\"\"\n        def static_url(conn_or_endpoint_ctx, path) do\n          Phoenix.VerifiedRoutes.static_url(conn_or_endpoint_ctx, path)\n        end\n\n        @doc \"\"\"\n        Generates an integrity hash to a static asset given its file path.\n        \"\"\"\n        def static_integrity(conn_or_endpoint_ctx, path) do\n          Phoenix.VerifiedRoutes.static_integrity(conn_or_endpoint_ctx, path)\n        end\n\n        # Functions used by generated helpers\n        # Those are inlined here for performance\n\n        defp to_param(int) when is_integer(int), do: Integer.to_string(int)\n        defp to_param(bin) when is_binary(bin), do: bin\n        defp to_param(false), do: \"false\"\n        defp to_param(true), do: \"true\"\n        defp to_param(data), do: Phoenix.Param.to_param(data)\n\n        defp segments(segments, [], _reserved, trailing_slash?, _opts) do\n          maybe_append_slash(segments, trailing_slash?)\n        end\n\n        defp segments(segments, query, reserved, trailing_slash?, _opts)\n             when is_list(query) or is_map(query) do\n          dict =\n            for {k, v} <- query,\n                (k = to_string(k)) not in reserved,\n                do: {k, v}\n\n          case Conn.Query.encode(dict, &to_param/1) do\n            \"\" -> maybe_append_slash(segments, trailing_slash?)\n            o -> maybe_append_slash(segments, trailing_slash?) <> \"?\" <> o\n          end\n        end\n\n        if unquote(trailing_slash?) do\n          defp maybe_append_slash(\"/\", _), do: \"/\"\n          defp maybe_append_slash(path, true), do: path <> \"/\"\n        end\n\n        defp maybe_append_slash(path, _), do: path\n      end\n\n    name = Module.concat(env.module, Helpers)\n    Module.create(name, code, line: env.line, file: env.file)\n    name\n  end\n\n  @doc \"\"\"\n  Receives a route and returns the quoted definition for its helper function.\n\n  In case a helper name was not given, or route is forwarded, returns nil.\n  \"\"\"\n  def defhelper(%Route{} = route, exprs) do\n    helper = route.helper\n    opts = route.plug_opts\n    trailing_slash? = route.trailing_slash?\n\n    {bins, vars} = :lists.unzip(exprs.binding)\n    segs = expand_segments(exprs.path)\n\n    quote do\n      defhelper.(\n        unquote(helper),\n        unquote(Macro.escape(vars)),\n        unquote(Macro.escape(opts)),\n        unquote(Macro.escape(bins)),\n        unquote(Macro.escape(segs)),\n        unquote(Macro.escape(trailing_slash?))\n      )\n    end\n  end\n\n  def defhelper_catch_all({helper, routes_and_exprs}) do\n    routes =\n      routes_and_exprs\n      |> Enum.map(fn {routes, exprs} ->\n        {routes.plug_opts, Enum.map(exprs.binding, &elem(&1, 0))}\n      end)\n      |> Enum.sort()\n\n    params_lengths =\n      routes\n      |> Enum.map(fn {_, bindings} -> length(bindings) end)\n      |> Enum.uniq()\n\n    # Each helper defines catch all like this:\n    #\n    #     def helper_path(context, action, ...binding)\n    #     def helper_path(context, action, ...binding, params)\n    #\n    # Given the helpers are ordered by binding length, the additional\n    # helper with param for a helper_path/n will always override the\n    # binding for helper_path/n+1, so we skip those here to avoid warnings.\n    binding_lengths = Enum.reject(params_lengths, &((&1 - 1) in params_lengths))\n\n    quote do\n      defcatch_all.(\n        unquote(helper),\n        unquote(binding_lengths),\n        unquote(params_lengths),\n        unquote(Macro.escape(routes))\n      )\n    end\n  end\n\n  @doc \"\"\"\n  Callback for generate router catch all.\n  \"\"\"\n  def raise_route_error(mod, fun, arity, action, routes, params) do\n    cond do\n      is_atom(action) and not Keyword.has_key?(routes, action) ->\n        \"no action #{inspect(action)} for #{inspect(mod)}.#{fun}/#{arity}\"\n        |> invalid_route_error(fun, routes)\n\n      is_list(params) or is_map(params) ->\n        \"no function clause for #{inspect(mod)}.#{fun}/#{arity} and action #{inspect(action)}\"\n        |> invalid_route_error(fun, routes)\n\n      true ->\n        invalid_param_error(mod, fun, arity, action, routes)\n    end\n  end\n\n  defp invalid_route_error(prelude, fun, routes) do\n    suggestions =\n      for {action, bindings} <- routes do\n        bindings = Enum.join([inspect(action) | bindings], \", \")\n        \"\\n    #{fun}(conn_or_endpoint, #{bindings}, params \\\\\\\\ [])\"\n      end\n\n    raise ArgumentError,\n          \"#{prelude}. The following actions/clauses are supported:\\n#{suggestions}\"\n  end\n\n  defp invalid_param_error(mod, fun, arity, action, routes) do\n    call_vars = Keyword.fetch!(routes, action)\n\n    raise ArgumentError, \"\"\"\n    #{inspect(mod)}.#{fun}/#{arity} called with invalid params.\n    The last argument to this function should be a keyword list or a map.\n    For example:\n\n        #{fun}(#{Enum.join([\"conn\", \":#{action}\" | call_vars], \", \")}, page: 5, per_page: 10)\n\n    It is possible you have called this function without defining the proper\n    number of path segments in your router.\n    \"\"\"\n  end\n\n  @doc \"\"\"\n  Callback for properly encoding parameters in routes.\n  \"\"\"\n  def encode_param(str), do: URI.encode(str, &URI.char_unreserved?/1)\n\n  defp expand_segments([]), do: \"/\"\n\n  defp expand_segments(segments) when is_list(segments) do\n    expand_segments(segments, \"\")\n  end\n\n  defp expand_segments(segments) do\n    quote(do: \"/\" <> Enum.map_join(unquote(segments), \"/\", &unquote(__MODULE__).encode_param/1))\n  end\n\n  defp expand_segments([{:|, _, [h, t]}], acc),\n    do:\n      quote(\n        do:\n          unquote(expand_segments([h], acc)) <>\n            \"/\" <> Enum.map_join(unquote(t), \"/\", &unquote(__MODULE__).encode_param/1)\n      )\n\n  defp expand_segments([h | t], acc) when is_binary(h),\n    do: expand_segments(t, quote(do: unquote(acc) <> unquote(\"/\" <> h)))\n\n  defp expand_segments([h | t], acc),\n    do:\n      expand_segments(\n        t,\n        quote(do: unquote(acc) <> \"/\" <> unquote(__MODULE__).encode_param(to_param(unquote(h))))\n      )\n\n  defp expand_segments([], acc),\n    do: acc\nend\n"
  },
  {
    "path": "lib/phoenix/router/resource.ex",
    "content": "defmodule Phoenix.Router.Resource do\n  # This module defines the Resource struct that is used\n  # throughout Phoenix's router. This struct is private\n  # as it contains internal routing information.\n  @moduledoc false\n\n  alias Phoenix.Router.Resource\n\n  @default_param_key \"id\"\n  @actions [:index, :edit, :new, :show, :create, :update, :delete]\n\n  @doc \"\"\"\n  The `Phoenix.Router.Resource` struct. It stores:\n\n    * `:path` - the path as string (not normalized)\n    * `:param` - the param to be used in routes (not normalized)\n    * `:controller` - the controller as an atom\n    * `:actions` - a list of actions as atoms\n    * `:route` - the context for resource routes\n    * `:member` - the context for member routes\n    * `:collection` - the context for collection routes\n\n  \"\"\"\n  defstruct [:path, :actions, :param, :route, :controller, :member, :collection, :singleton]\n  @type t :: %Resource{}\n\n  @doc \"\"\"\n  Builds a resource struct.\n  \"\"\"\n  def build(path, controller, options) when is_atom(controller) and is_list(options) do\n    path    = Phoenix.Router.Scope.validate_path(path)\n    alias   = Keyword.get(options, :alias)\n    param   = Keyword.get(options, :param, @default_param_key)\n    name    = Keyword.get(options, :name, Phoenix.Naming.resource_name(controller, \"Controller\"))\n    as      = Keyword.get(options, :as, name)\n    private = Keyword.get(options, :private, %{})\n    assigns = Keyword.get(options, :assigns, %{})\n\n    singleton = Keyword.get(options, :singleton, false)\n    actions   = extract_actions(options, singleton)\n\n    route       = [as: as, private: private, assigns: assigns]\n    collection  = [path: path, as: as, private: private, assigns: assigns]\n    member_path = if singleton, do: path, else: Path.join(path, \":#{name}_#{param}\")\n    member      = [path: member_path, as: as, alias: alias, private: private, assigns: assigns]\n\n    %Resource{path: path, actions: actions, param: param, route: route,\n              member: member, collection: collection, controller: controller, singleton: singleton}\n  end\n\n  defp extract_actions(opts, singleton) do\n    only = Keyword.get(opts, :only)\n    except = Keyword.get(opts, :except)\n\n    cond do\n      only ->\n        supported_actions = validate_actions(:only, singleton, only)\n        supported_actions -- (supported_actions -- only)\n\n      except ->\n        supported_actions = validate_actions(:except, singleton, except)\n        supported_actions -- except\n\n      true -> default_actions(singleton)\n    end\n  end\n\n  defp validate_actions(type, singleton, actions) do\n    supported_actions = default_actions(singleton)\n\n    unless actions -- supported_actions == [] do\n      raise ArgumentError, \"\"\"\n      invalid :#{type} action(s) passed to resources.\n\n      supported#{if singleton, do: \" singleton\", else: \"\"} actions: #{inspect(supported_actions)}\n\n      got: #{inspect(actions)}\n      \"\"\"\n    end\n\n    supported_actions\n  end\n\n  defp default_actions(true = _singleton),  do: @actions -- [:index]\n  defp default_actions(false = _singleton), do: @actions\nend\n"
  },
  {
    "path": "lib/phoenix/router/route.ex",
    "content": "defmodule Phoenix.Router.Route do\n  # This module defines the Route struct that is used\n  # throughout Phoenix's router. This struct is private\n  # as it contains internal routing information.\n  @moduledoc false\n\n  alias Phoenix.Router.Route\n\n  @doc \"\"\"\n  The `Phoenix.Router.Route` struct. It stores:\n\n    * `:verb` - the HTTP verb as an atom\n    * `:line` - the line the route was defined\n    * `:kind` - the kind of route, either `:match` or `:forward`\n    * `:path` - the normalized path as string\n    * `:hosts` - the list of request hosts or host prefixes\n    * `:plug` - the plug module\n    * `:plug_opts` - the plug options\n    * `:helper` - the name of the helper as a string (may be nil)\n    * `:private` - the private route info\n    * `:assigns` - the route info\n    * `:pipe_through` - the pipeline names as a list of atoms\n    * `:metadata` - general metadata used on telemetry events and route info\n    * `:trailing_slash?` - whether or not the helper functions append a trailing slash\n    * `:warn_on_verify?` - whether or not to warn on route verification\n  \"\"\"\n\n  defstruct [\n    :verb,\n    :line,\n    :kind,\n    :path,\n    :hosts,\n    :plug,\n    :plug_opts,\n    :helper,\n    :private,\n    :pipe_through,\n    :assigns,\n    :metadata,\n    :trailing_slash?,\n    :warn_on_verify?\n  ]\n\n  @type t :: %Route{}\n\n  @doc \"Used as a plug on forwarding\"\n  def init(opts), do: opts\n\n  @doc \"Used as a plug on forwarding\"\n  def call(%{path_info: path, script_name: script} = conn, {fwd_segments, plug, opts}) do\n    new_path = path -- fwd_segments\n    {base, ^new_path} = Enum.split(path, length(path) - length(new_path))\n    conn = %{conn | path_info: new_path, script_name: script ++ base}\n    conn = plug.call(conn, plug.init(opts))\n    %{conn | path_info: path, script_name: script}\n  end\n\n  @doc \"\"\"\n  Receives the verb, path, plug, options and helper\n  and returns a `Phoenix.Router.Route` struct.\n  \"\"\"\n  @spec build(\n          non_neg_integer,\n          :match | :forward,\n          atom,\n          String.t(),\n          String.t() | nil,\n          atom,\n          atom,\n          atom | nil,\n          list(atom),\n          map,\n          map,\n          map,\n          boolean,\n          boolean\n        ) :: t\n  def build(\n        line,\n        kind,\n        verb,\n        path,\n        hosts,\n        plug,\n        plug_opts,\n        helper,\n        pipe_through,\n        private,\n        assigns,\n        metadata,\n        trailing_slash?,\n        warn_on_verify?\n      )\n      when is_atom(verb) and is_list(hosts) and\n             is_atom(plug) and (is_binary(helper) or is_nil(helper)) and\n             is_list(pipe_through) and is_map(private) and is_map(assigns) and\n             is_map(metadata) and kind in [:match, :forward] and\n             is_boolean(trailing_slash?) do\n    %Route{\n      kind: kind,\n      verb: verb,\n      path: path,\n      hosts: hosts,\n      private: private,\n      plug: plug,\n      plug_opts: plug_opts,\n      helper: helper,\n      pipe_through: pipe_through,\n      assigns: assigns,\n      line: line,\n      metadata: metadata,\n      trailing_slash?: trailing_slash?,\n      warn_on_verify?: warn_on_verify?\n    }\n  end\n\n  @doc \"\"\"\n  Builds the compiled expressions used by the route.\n  \"\"\"\n  def exprs(route) do\n    {path, binding} = build_path_and_binding(route)\n\n    %{\n      path: path,\n      binding: binding,\n      dispatch: build_dispatch(route),\n      hosts: build_host_match(route.hosts),\n      path_params: build_path_params(binding),\n      prepare: build_prepare(route),\n      verb_match: verb_match(route.verb)\n    }\n  end\n\n  def build_host_match([]), do: [Plug.Router.Utils.build_host_match(nil)]\n\n  def build_host_match([_ | _] = hosts) do\n    for host <- hosts, do: Plug.Router.Utils.build_host_match(host)\n  end\n\n  defp verb_match(:*), do: Macro.var(:_verb, nil)\n  defp verb_match(verb), do: verb |> to_string() |> String.upcase()\n\n  defp build_path_params(binding), do: {:%{}, [], binding}\n\n  defp build_path_and_binding(%Route{path: path} = route) do\n    {_params, segments} =\n      case route.kind do\n        :forward -> Plug.Router.Utils.build_path_match(path <> \"/*_forward_path_info\")\n        :match -> Plug.Router.Utils.build_path_match(path)\n      end\n\n    rewrite_segments(segments)\n  end\n\n  # We rewrite segments to use consistent variable naming as we want to group routes later on.\n  defp rewrite_segments(segments) do\n    {segments, {binding, _counter}} =\n      Macro.prewalk(segments, {[], 0}, fn\n        {name, _meta, nil}, {binding, counter}\n        when is_atom(name) and name != :_forward_path_info ->\n          var = Macro.var(:\"arg#{counter}\", __MODULE__)\n          {var, {[{Atom.to_string(name), var} | binding], counter + 1}}\n\n        other, acc ->\n          {other, acc}\n      end)\n\n    {segments, Enum.reverse(binding)}\n  end\n\n  defp build_prepare(route) do\n    {match_params, merge_params} = build_params()\n    {match_private, merge_private} = build_prepare_expr(:private, route.private)\n    {match_assigns, merge_assigns} = build_prepare_expr(:assigns, route.assigns)\n\n    match_all = match_params ++ match_private ++ match_assigns\n    merge_all = merge_params ++ merge_private ++ merge_assigns\n\n    quote do\n      %{unquote_splicing(match_all)} = var!(conn, :conn)\n      %{var!(conn, :conn) | unquote_splicing(merge_all)}\n    end\n  end\n\n  defp build_prepare_expr(_key, data) when data == %{}, do: {[], []}\n\n  defp build_prepare_expr(key, data) do\n    var = Macro.var(key, :conn)\n    merge = quote(do: Map.merge(unquote(var), unquote(Macro.escape(data))))\n    {[{key, var}], [{key, merge}]}\n  end\n\n  defp build_dispatch(%Route{kind: :match, plug: plug, plug_opts: plug_opts}) do\n    quote do\n      {unquote(plug), unquote(Macro.escape(plug_opts))}\n    end\n  end\n\n  defp build_dispatch(%Route{\n         kind: :forward,\n         plug: plug,\n         plug_opts: plug_opts,\n         metadata: metadata\n       }) do\n    quote do\n      {\n        Phoenix.Router.Route,\n        {unquote(metadata.forward), unquote(plug), unquote(Macro.escape(plug_opts))}\n      }\n    end\n  end\n\n  defp build_params() do\n    params = Macro.var(:params, :conn)\n    path_params = Macro.var(:path_params, :conn)\n\n    merge_params =\n      quote(do: Phoenix.Router.Route.merge_params(unquote(params), unquote(path_params)))\n\n    {\n      [{:params, params}],\n      [{:params, merge_params}, {:path_params, path_params}]\n    }\n  end\n\n  @doc \"\"\"\n  Merges params from router.\n  \"\"\"\n  def merge_params(%Plug.Conn.Unfetched{}, path_params), do: path_params\n  def merge_params(params, path_params), do: Map.merge(params, path_params)\nend\n"
  },
  {
    "path": "lib/phoenix/router/scope.ex",
    "content": "defmodule Phoenix.Router.Scope do\n  alias Phoenix.Router.Scope\n  @moduledoc false\n\n  @stack :phoenix_router_scopes\n  @pipes :phoenix_pipeline_scopes\n  @top :phoenix_top_scopes\n\n  defstruct path: [],\n            alias: [],\n            as: [],\n            pipes: [],\n            hosts: [],\n            private: %{},\n            assigns: %{},\n            log: :debug,\n            trailing_slash?: false\n\n  @doc \"\"\"\n  Initializes the scope.\n  \"\"\"\n  def init(module) do\n    Module.put_attribute(module, @stack, [])\n    Module.put_attribute(module, @top, %Scope{})\n    Module.put_attribute(module, @pipes, MapSet.new())\n  end\n\n  @doc \"\"\"\n  Builds a route based on the top of the stack.\n  \"\"\"\n  def route(line, module, kind, verb, path, plug, plug_opts, opts) do\n    unless is_atom(plug) do\n      raise ArgumentError, \"routes expect a module plug as second argument, got: #{inspect(plug)}\"\n    end\n\n    top = get_top(module)\n    path = validate_path(path)\n    private = Keyword.get(opts, :private, %{})\n    assigns = Keyword.get(opts, :assigns, %{})\n    as = Keyword.get_lazy(opts, :as, fn -> Phoenix.Naming.resource_name(plug, \"Controller\") end)\n    alias? = Keyword.get(opts, :alias, true)\n    trailing_slash? = deprecated_trailing_slash(opts, top)\n    warn_on_verify? = Keyword.get(opts, :warn_on_verify, false)\n\n    if to_string(as) == \"static\" do\n      raise ArgumentError,\n            \"`static` is a reserved route prefix generated from #{inspect(plug)} or `:as` option\"\n    end\n\n    {path, alias, as, private, assigns} = join(top, path, plug, alias?, as, private, assigns)\n\n    metadata =\n      opts\n      |> Keyword.get(:metadata, %{})\n      |> Map.put(:log, Keyword.get(opts, :log, top.log))\n\n    metadata =\n      if kind == :forward do\n        Map.put(metadata, :forward, validate_forward!(path, plug))\n      else\n        metadata\n      end\n\n    Phoenix.Router.Route.build(\n      line,\n      kind,\n      verb,\n      path,\n      top.hosts,\n      alias,\n      plug_opts,\n      as,\n      top.pipes,\n      private,\n      assigns,\n      metadata,\n      trailing_slash?,\n      warn_on_verify?\n    )\n  end\n\n  defp validate_forward!(path, plug) when is_atom(plug) do\n    case Plug.Router.Utils.build_path_match(path) do\n      {[], path_segments} ->\n        path_segments\n\n      _ ->\n        raise ArgumentError,\n              \"dynamic segment \\\"#{path}\\\" not allowed when forwarding. Use a static path instead\"\n    end\n  end\n\n  defp validate_forward!(_, plug) do\n    raise ArgumentError, \"forward expects a module as the second argument, #{inspect(plug)} given\"\n  end\n\n  @doc \"\"\"\n  Validates a path is a string and contains a leading prefix.\n  \"\"\"\n  def validate_path(\"/\" <> _ = path), do: path\n\n  def validate_path(path) when is_binary(path) do\n    IO.warn(\"router paths should begin with a forward slash, got: #{inspect(path)}\")\n    \"/\" <> path\n  end\n\n  def validate_path(path) do\n    raise ArgumentError, \"router paths must be strings, got: #{inspect(path)}\"\n  end\n\n  @doc \"\"\"\n  Defines the given pipeline.\n  \"\"\"\n  def pipeline(module, pipe) when is_atom(pipe) do\n    update_pipes(module, &MapSet.put(&1, pipe))\n  end\n\n  @doc \"\"\"\n  Appends the given pipes to the current scope pipe through.\n  \"\"\"\n  def pipe_through(module, new_pipes) do\n    new_pipes = List.wrap(new_pipes)\n    %{pipes: pipes} = top = get_top(module)\n\n    if pipe = Enum.find(new_pipes, &(&1 in pipes)) do\n      raise ArgumentError,\n            \"duplicate pipe_through for #{inspect(pipe)}. \" <>\n              \"A plug may only be used once inside a scoped pipe_through\"\n    end\n\n    put_top(module, %{top | pipes: pipes ++ new_pipes})\n  end\n\n  @doc \"\"\"\n  Pushes a scope into the module stack.\n  \"\"\"\n  def push(module, path) when is_binary(path) do\n    push(module, path: path)\n  end\n\n  def push(module, opts) when is_list(opts) do\n    top = get_top(module)\n\n    path =\n      if path = Keyword.get(opts, :path) do\n        path |> validate_path() |> String.split(\"/\", trim: true)\n      else\n        []\n      end\n\n    alias = append_unless_false(top, opts, :alias, &Atom.to_string(&1))\n    as = append_unless_false(top, opts, :as, & &1)\n\n    hosts =\n      case Keyword.fetch(opts, :host) do\n        {:ok, val} -> validate_hosts!(val)\n        :error -> top.hosts\n      end\n\n    private = Keyword.get(opts, :private, %{})\n    assigns = Keyword.get(opts, :assigns, %{})\n\n    update_stack(module, fn stack -> [top | stack] end)\n\n    put_top(module, %Scope{\n      path: top.path ++ path,\n      alias: alias,\n      as: as,\n      hosts: hosts,\n      pipes: top.pipes,\n      private: Map.merge(top.private, private),\n      assigns: Map.merge(top.assigns, assigns),\n      log: Keyword.get(opts, :log, top.log),\n      trailing_slash?: deprecated_trailing_slash(opts, top)\n    })\n  end\n\n  defp deprecated_trailing_slash(opts, top) do\n    case Keyword.fetch(opts, :trailing_slash) do\n      {:ok, value} ->\n        IO.warn(\n          \"the :trailing_slash option in the router is deprecated. \" <>\n            \"If you are using Phoenix.VerifiedRoutes, it has no effect. \" <>\n            \"If you are using the generated helpers, migrate to Phoenix.VerifiedRoutes\"\n        )\n\n        value == true\n\n      :error ->\n        top.trailing_slash?\n    end\n  end\n\n  defp validate_hosts!(nil), do: []\n  defp validate_hosts!(host) when is_binary(host), do: [host]\n\n  defp validate_hosts!(hosts) when is_list(hosts) do\n    for host <- hosts do\n      unless is_binary(host), do: raise_invalid_host(host)\n\n      host\n    end\n  end\n\n  defp validate_hosts!(invalid), do: raise_invalid_host(invalid)\n\n  defp raise_invalid_host(host) do\n    raise ArgumentError,\n          \"expected router scope :host to be compile-time string or list of strings, got: #{inspect(host)}\"\n  end\n\n  defp append_unless_false(top, opts, key, fun) do\n    case opts[key] do\n      false -> []\n      nil -> Map.fetch!(top, key)\n      other -> Map.fetch!(top, key) ++ [fun.(other)]\n    end\n  end\n\n  @doc \"\"\"\n  Pops a scope from the module stack.\n  \"\"\"\n  def pop(module) do\n    update_stack(module, fn [top | stack] ->\n      put_top(module, top)\n      stack\n    end)\n  end\n\n  @doc \"\"\"\n  Expands the alias in the current router scope.\n  \"\"\"\n  def expand_alias(module, alias) do\n    join_alias(get_top(module), alias)\n  end\n\n  @doc \"\"\"\n  Returns the full path in the current router scope.\n  \"\"\"\n  def full_path(module, path) do\n    split_path = String.split(path, \"/\", trim: true)\n    prefix = get_top(module).path\n\n    cond do\n      prefix == [] -> path\n      split_path == [] -> \"/\" <> Enum.join(prefix, \"/\")\n      true -> \"/\" <> Path.join(get_top(module).path ++ split_path)\n    end\n  end\n\n  defp join(top, path, alias, alias?, as, private, assigns) do\n    joined_alias =\n      if alias? do\n        join_alias(top, alias)\n      else\n        alias\n      end\n\n    {join_path(top, path), joined_alias, join_as(top, as), Map.merge(top.private, private),\n     Map.merge(top.assigns, assigns)}\n  end\n\n  defp join_path(top, path) do\n    \"/\" <> Enum.join(top.path ++ String.split(path, \"/\", trim: true), \"/\")\n  end\n\n  defp join_alias(top, alias) when is_atom(alias) do\n    case Atom.to_string(alias) do\n      <<head, _::binary>> when head in ?a..?z -> alias\n      alias -> Module.concat(top.alias ++ [alias])\n    end\n  end\n\n  defp join_as(_top, nil), do: nil\n  defp join_as(top, as) when is_atom(as) or is_binary(as), do: Enum.join(top.as ++ [as], \"_\")\n\n  defp get_top(module) do\n    get_attribute(module, @top)\n  end\n\n  defp update_stack(module, fun) do\n    update_attribute(module, @stack, fun)\n  end\n\n  defp update_pipes(module, fun) do\n    update_attribute(module, @pipes, fun)\n  end\n\n  defp put_top(module, value) do\n    Module.put_attribute(module, @top, value)\n    value\n  end\n\n  defp get_attribute(module, attr) do\n    Module.get_attribute(module, attr) ||\n      raise \"Phoenix router scope was not initialized\"\n  end\n\n  defp update_attribute(module, attr, fun) do\n    Module.put_attribute(module, attr, fun.(get_attribute(module, attr)))\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/router.ex",
    "content": "defmodule Phoenix.Router do\n  defmodule NoRouteError do\n    @moduledoc \"\"\"\n    Exception raised when no route is found.\n    \"\"\"\n    defexception plug_status: 404, message: \"no route found\", conn: nil, router: nil\n\n    def exception(opts) do\n      conn = Keyword.fetch!(opts, :conn)\n      router = Keyword.fetch!(opts, :router)\n      path = \"/\" <> Enum.join(conn.path_info, \"/\")\n\n      %NoRouteError{\n        message: \"no route found for #{conn.method} #{path} (#{inspect(router)})\",\n        conn: conn,\n        router: router\n      }\n    end\n  end\n\n  defmodule MalformedURIError do\n    @moduledoc \"\"\"\n    Exception raised when the URI is malformed on matching.\n    \"\"\"\n    defexception [:message, plug_status: 400]\n  end\n\n  @moduledoc \"\"\"\n  Defines a Phoenix router.\n\n  The router provides a set of macros for generating routes\n  that dispatch to specific controllers and actions. Those\n  macros are named after HTTP verbs. For example:\n\n      defmodule MyAppWeb.Router do\n        use Phoenix.Router\n\n        get \"/pages/:page\", PageController, :show\n      end\n\n  The `get/3` macro above accepts a request to `/pages/hello` and dispatches\n  it to `PageController`'s `show` action with `%{\"page\" => \"hello\"}` in\n  `params`.\n\n  Phoenix's router is extremely efficient, as it relies on Elixir\n  pattern matching for matching routes and serving requests.\n\n  ## Routing\n\n  `get/3`, `post/3`, `put/3`, and other macros named after HTTP verbs are used\n  to create routes.\n\n  The route:\n\n      get \"/pages\", PageController, :index\n\n  matches a `GET` request to `/pages` and dispatches it to the `index` action in\n  `PageController`.\n\n      get \"/pages/:page\", PageController, :show\n\n  matches `/pages/hello` and dispatches to the `show` action with\n  `%{\"page\" => \"hello\"}` in `params`.\n\n      defmodule PageController do\n        def show(conn, params) do\n          # %{\"page\" => \"hello\"} == params\n        end\n      end\n\n  Partial and multiple segments can be matched. For example:\n\n      get \"/api/v:version/pages/:id\", PageController, :show\n\n  matches `/api/v1/pages/2` and puts `%{\"version\" => \"1\", \"id\" => \"2\"}` in\n  `params`. Only the trailing part of a segment can be captured.\n\n  Routes are matched from top to bottom. The second route here:\n\n      get \"/pages/:page\", PageController, :show\n      get \"/pages/hello\", PageController, :hello\n\n  will never match `/pages/hello` because `/pages/:page` matches that first.\n\n  Routes can use glob-like patterns to match trailing segments.\n\n      get \"/pages/*page\", PageController, :show\n\n  matches `/pages/hello/world` and puts the globbed segments in `params[\"page\"]`.\n\n      GET /pages/hello/world\n      %{\"page\" => [\"hello\", \"world\"]} = params\n\n  Globs cannot have prefixes nor suffixes, but can be mixed with variables:\n\n      get \"/pages/he:page/*rest\", PageController, :show\n\n  matches\n\n      GET /pages/hello\n      %{\"page\" => \"llo\", \"rest\" => []} = params\n\n      GET /pages/hey/there/world\n      %{\"page\" => \"y\", \"rest\" => [\"there\" \"world\"]} = params\n\n  > #### Why the macros? {: .info}\n  >\n  > Phoenix does its best to keep the usage of macros low. You may have noticed,\n  > however, that the `Phoenix.Router` relies heavily on macros. Why is that?\n  >\n  > We use `get`, `post`, `put`, and `delete` to define your routes. We use macros\n  > for two purposes:\n  >\n  > * They define the routing engine, used on every request, to choose which\n  >   controller to dispatch the request to. Thanks to macros, Phoenix compiles\n  >   all of your routes to a single case-statement with pattern matching rules,\n  >   which is heavily optimized by the Erlang VM\n  >\n  > * For each route you define, we also define metadata to implement `Phoenix.VerifiedRoutes`.\n  >   As we will soon learn, verified routes allows to us to reference any route\n  >   as if it is a plain looking string, except it is verified by the compiler\n  >   to be valid (making it much harder to ship broken links, forms, mails, etc\n  >   to production)\n  >\n  > In other words, the router relies on macros to build applications that are\n  > faster and safer. Also remember that macros in Elixir are compile-time only,\n  > which gives plenty of stability after the code is compiled. Phoenix also provides\n  > introspection for all defined routes via `mix phx.routes`.\n\n  ## Generating routes\n\n  For generating routes inside your application,  see the `Phoenix.VerifiedRoutes`\n  documentation for `~p` based route generation which is the preferred way to\n  generate route paths and URLs with compile-time verification.\n\n  Phoenix also supports generating function helpers, which was the default\n  mechanism in Phoenix v1.6 and earlier. We will explore it next.\n\n  ### Helpers (deprecated)\n\n  Phoenix generates a module `Helpers` inside your router by default, which contains\n  named helpers to help developers generate and keep their routes up to date.\n  Helpers can be disabled by passing `helpers: false` to `use Phoenix.Router`.\n\n  Helpers are automatically generated based on the controller name.\n  For example, the route:\n\n      get \"/pages/:page\", PageController, :show\n\n  will generate the following named helper:\n\n      MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, \"hello\")\n      \"/pages/hello\"\n\n      MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, \"hello\", some: \"query\")\n      \"/pages/hello?some=query\"\n\n      MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, \"hello\")\n      \"http://example.com/pages/hello\"\n\n      MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, \"hello\", some: \"query\")\n      \"http://example.com/pages/hello?some=query\"\n\n  If the route contains glob-like patterns, parameters for those have to be given as\n  list:\n\n      MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, [\"hello\", \"world\"])\n      \"/pages/hello/world\"\n\n  The URL generated in the named URL helpers is based on the configuration for\n  `:url`, `:http` and `:https`. However, if for some reason you need to manually\n  control the URL generation, the url helpers also allow you to pass in a `URI`\n  struct:\n\n      uri = %URI{scheme: \"https\", host: \"other.example.com\"}\n      MyAppWeb.Router.Helpers.page_url(uri, :show, \"hello\")\n      \"https://other.example.com/pages/hello\"\n\n  The named helper can also be customized with the `:as` option. Given\n  the route:\n\n      get \"/pages/:page\", PageController, :show, as: :special_page\n\n  the named helper will be:\n\n      MyAppWeb.Router.Helpers.special_page_path(conn, :show, \"hello\")\n      \"/pages/hello\"\n\n  ## Scopes and Resources\n\n  It is very common in Phoenix applications to namespace all of your\n  routes under the application scope:\n\n      scope \"/\", MyAppWeb do\n        get \"/pages/:id\", PageController, :show\n      end\n\n  The route above will dispatch to `MyAppWeb.PageController`. This syntax\n  is convenient for developers, since we don't have to repeat `MyAppWeb.`\n  prefix on all routes\n\n  Like all paths, you can define dynamic segments that will be applied as\n  parameters in the controller:\n\n      scope \"/api/:version\", MyAppWeb do\n        get \"/pages/:id\", PageController, :show\n      end\n\n  For example, the route above will match on the path `\"/api/v1/pages/1\"`\n  and in the controller the `params` argument will have a map with the\n  key `:version` with the value `\"v1\"`.\n\n  Phoenix also provides a `resources/4` macro that allows developers\n  to generate \"RESTful\" routes to a given resource:\n\n      defmodule MyAppWeb.Router do\n        use Phoenix.Router, helpers: false\n\n        resources \"/pages\", PageController, only: [:show]\n        resources \"/users\", UserController, except: [:delete]\n      end\n\n  Finally, Phoenix ships with a `mix phx.routes` task that nicely\n  formats all routes in a given router. We can use it to verify all\n  routes included in the router above:\n\n      $ mix phx.routes\n      GET    /pages/:id       PageController.show/2\n      GET    /users           UserController.index/2\n      GET    /users/:id/edit  UserController.edit/2\n      GET    /users/new       UserController.new/2\n      GET    /users/:id       UserController.show/2\n      POST   /users           UserController.create/2\n      PATCH  /users/:id       UserController.update/2\n      PUT    /users/:id       UserController.update/2\n\n  One can also pass a router explicitly as an argument to the task:\n\n      $ mix phx.routes MyAppWeb.Router\n\n  Check `scope/2` and `resources/4` for more information.\n\n  ## Pipelines and plugs\n\n  Once a request arrives at the Phoenix router, it performs\n  a series of transformations through pipelines until the\n  request is dispatched to a desired route.\n\n  Such transformations are defined via plugs, as defined\n  in the [Plug](https://github.com/elixir-lang/plug) specification.\n  Once a pipeline is defined, it can be piped through per scope.\n\n  For example:\n\n      defmodule MyAppWeb.Router do\n        use Phoenix.Router\n\n        pipeline :browser do\n          plug :fetch_session\n          plug :accepts, [\"html\"]\n        end\n\n        scope \"/\" do\n          pipe_through :browser\n\n          # browser related routes and resources\n        end\n      end\n\n  `Phoenix.Router` imports functions from both `Plug.Conn` and `Phoenix.Controller`\n  to help define plugs. In the example above, `fetch_session/2`\n  comes from `Plug.Conn` while `accepts/2` comes from `Phoenix.Controller`.\n\n  Note that router pipelines are only invoked after a route is found.\n  No plug is invoked in case no matches were found.\n\n  ## Learn more\n\n  See the [Routing](routing.md) guide for more information and examples\n  within an actual Phoenix application.\n  \"\"\"\n\n  alias Phoenix.Router.{Resource, Scope, Route, Helpers}\n\n  @http_methods [:get, :post, :put, :patch, :delete, :options, :connect, :trace, :head]\n\n  @doc false\n  defmacro __using__(opts) do\n    quote do\n      unquote(prelude(opts))\n      unquote(defs())\n      unquote(match_dispatch())\n      unquote(verified_routes())\n    end\n  end\n\n  defp prelude(opts) do\n    quote do\n      Module.register_attribute(__MODULE__, :phoenix_routes, accumulate: true)\n      # TODO: Require :helpers to be explicit given\n      @phoenix_helpers Keyword.get(unquote(opts), :helpers, true)\n\n      import Phoenix.Router\n\n      # TODO v2: No longer automatically import dependencies\n      import Plug.Conn\n      import Phoenix.Controller\n\n      # Set up initial scope\n      @phoenix_pipeline nil\n      Phoenix.Router.Scope.init(__MODULE__)\n      @before_compile unquote(__MODULE__)\n    end\n  end\n\n  # Because those macros are executed multiple times,\n  # we end-up generating a huge scope that drastically\n  # affects compilation. We work around it by defining\n  # those functions only once and calling it over and\n  # over again.\n  defp defs() do\n    quote unquote: false do\n      var!(add_resources, Phoenix.Router) = fn resource ->\n        path = resource.path\n        ctrl = resource.controller\n        opts = resource.route\n\n        if resource.singleton do\n          Enum.each(resource.actions, fn\n            :show ->\n              get path, ctrl, :show, opts\n\n            :new ->\n              get path <> \"/new\", ctrl, :new, opts\n\n            :edit ->\n              get path <> \"/edit\", ctrl, :edit, opts\n\n            :create ->\n              post path, ctrl, :create, opts\n\n            :delete ->\n              delete path, ctrl, :delete, opts\n\n            :update ->\n              patch path, ctrl, :update, opts\n              put path, ctrl, :update, Keyword.put(opts, :as, nil)\n          end)\n        else\n          param = resource.param\n\n          Enum.each(resource.actions, fn\n            :index ->\n              get path, ctrl, :index, opts\n\n            :show ->\n              get path <> \"/:\" <> param, ctrl, :show, opts\n\n            :new ->\n              get path <> \"/new\", ctrl, :new, opts\n\n            :edit ->\n              get path <> \"/:\" <> param <> \"/edit\", ctrl, :edit, opts\n\n            :create ->\n              post path, ctrl, :create, opts\n\n            :delete ->\n              delete path <> \"/:\" <> param, ctrl, :delete, opts\n\n            :update ->\n              patch path <> \"/:\" <> param, ctrl, :update, opts\n              put path <> \"/:\" <> param, ctrl, :update, Keyword.put(opts, :as, nil)\n          end)\n        end\n      end\n    end\n  end\n\n  @doc false\n  def __call__(\n        %{private: %{phoenix_router: router, phoenix_bypass: {router, pipes}}} = conn,\n        metadata,\n        prepare,\n        pipeline,\n        _\n      ) do\n    conn = prepare.(conn, metadata)\n\n    case pipes do\n      :current -> pipeline.(conn)\n      _ -> Enum.reduce(pipes, conn, fn pipe, acc -> apply(router, pipe, [acc, []]) end)\n    end\n  end\n\n  def __call__(%{private: %{phoenix_bypass: :all}} = conn, metadata, prepare, _, _) do\n    prepare.(conn, metadata)\n  end\n\n  def __call__(conn, metadata, prepare, pipeline, {plug, opts}) do\n    conn = prepare.(conn, metadata)\n    start = System.monotonic_time()\n    measurements = %{system_time: System.system_time()}\n    metadata = %{metadata | conn: conn}\n    :telemetry.execute([:phoenix, :router_dispatch, :start], measurements, metadata)\n\n    case pipeline.(conn) do\n      %Plug.Conn{halted: true} = halted_conn ->\n        measurements = %{duration: System.monotonic_time() - start}\n        metadata = %{metadata | conn: halted_conn}\n        :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)\n        halted_conn\n\n      %Plug.Conn{} = piped_conn ->\n        try do\n          plug.call(piped_conn, plug.init(opts))\n        else\n          conn ->\n            measurements = %{duration: System.monotonic_time() - start}\n            metadata = %{metadata | conn: conn}\n            :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)\n            conn\n        rescue\n          e in Plug.Conn.WrapperError ->\n            measurements = %{duration: System.monotonic_time() - start}\n            new_metadata = %{conn: conn, kind: :error, reason: e, stacktrace: __STACKTRACE__}\n            metadata = Map.merge(metadata, new_metadata)\n            :telemetry.execute([:phoenix, :router_dispatch, :exception], measurements, metadata)\n            Plug.Conn.WrapperError.reraise(e)\n        catch\n          kind, reason ->\n            measurements = %{duration: System.monotonic_time() - start}\n            new_metadata = %{conn: conn, kind: kind, reason: reason, stacktrace: __STACKTRACE__}\n            metadata = Map.merge(metadata, new_metadata)\n            :telemetry.execute([:phoenix, :router_dispatch, :exception], measurements, metadata)\n            Plug.Conn.WrapperError.reraise(piped_conn, kind, reason, __STACKTRACE__)\n        end\n    end\n  end\n\n  defp match_dispatch() do\n    quote location: :keep, generated: true do\n      @behaviour Plug\n\n      @doc \"\"\"\n      Callback required by Plug that initializes the router\n      for serving web requests.\n      \"\"\"\n      def init(opts) do\n        opts\n      end\n\n      @doc \"\"\"\n      Callback invoked by Plug on every request.\n      \"\"\"\n      def call(conn, _opts) do\n        %{method: method, path_info: path_info, host: host} = conn = prepare(conn)\n        decoded = Enum.map(path_info, &URI.decode/1)\n\n        case __match_route__(decoded, method, host) do\n          {metadata, prepare, pipeline, plug_opts} ->\n            Phoenix.Router.__call__(conn, metadata, prepare, pipeline, plug_opts)\n\n          :error ->\n            raise NoRouteError, conn: conn, router: __MODULE__\n        end\n      end\n\n      defoverridable init: 1, call: 2\n    end\n  end\n\n  defp verified_routes() do\n    quote location: :keep, generated: true do\n      @behaviour Phoenix.VerifiedRoutes\n\n      def formatted_routes(_) do\n        Phoenix.Router.__formatted_routes__(__MODULE__)\n      end\n\n      def verified_route?(_, split_path) do\n        Phoenix.Router.__verified_route__?(__MODULE__, split_path)\n      end\n    end\n  end\n\n  @doc false\n  defmacro __before_compile__(env) do\n    routes = env.module |> Module.get_attribute(:phoenix_routes) |> Enum.reverse()\n    routes_with_exprs = Enum.map(routes, &{&1, Route.exprs(&1)})\n\n    helpers =\n      if Module.get_attribute(env.module, :phoenix_helpers) do\n        Helpers.define(env, routes_with_exprs)\n      end\n\n    {matches, {pipelines, _}} =\n      Enum.map_reduce(routes_with_exprs, {[], %{}}, &build_match/2)\n\n    routes_per_path =\n      routes_with_exprs\n      |> Enum.group_by(&elem(&1, 1).path, &elem(&1, 0))\n\n    verifies =\n      routes_with_exprs\n      |> Enum.map(&elem(&1, 1).path)\n      |> Enum.uniq()\n      |> Enum.map(&build_verify(&1, routes_per_path))\n\n    verify_catch_all =\n      quote generated: true do\n        @doc false\n        def __verify_route__(_path_info) do\n          :error\n        end\n      end\n\n    match_catch_all =\n      quote generated: true do\n        @doc false\n        def __match_route__(_path_info, _verb, _host) do\n          :error\n        end\n      end\n\n    forward_catch_all =\n      quote generated: true do\n        @doc false\n        def __forward__(_), do: nil\n      end\n\n    checks =\n      routes\n      |> Enum.map(fn %{line: line, metadata: metadata, plug: plug} ->\n        {line, Map.get(metadata, :mfa, {plug, :init, 1})}\n      end)\n      |> Enum.uniq()\n      |> Enum.map(fn {line, {module, function, arity}} ->\n        quote line: line, do: _ = &(unquote(module).unquote(function) / unquote(arity))\n      end)\n\n    keys = [:verb, :path, :plug, :plug_opts, :helper, :metadata]\n    routes = Enum.map(routes, &Map.take(&1, keys))\n\n    quote do\n      @doc false\n      def __routes__, do: unquote(Macro.escape(routes))\n\n      @doc false\n      def __checks__, do: unquote({:__block__, [], checks})\n\n      @doc false\n      def __helpers__, do: unquote(helpers)\n\n      defp prepare(conn) do\n        merge_private(conn, [{:phoenix_router, __MODULE__}, {__MODULE__, conn.script_name}])\n      end\n\n      unquote(pipelines)\n      unquote(verifies)\n      unquote(verify_catch_all)\n      unquote(matches)\n      unquote(match_catch_all)\n      unquote(forward_catch_all)\n    end\n  end\n\n  defp build_verify(path, routes_per_path) do\n    routes = Map.get(routes_per_path, path)\n    warn_on_verify? = Enum.all?(routes, & &1.warn_on_verify?)\n\n    case Enum.find(routes, &(&1.kind == :forward)) do\n      %{metadata: %{forward: forward}, plug: plug, plug_opts: plug_opts} ->\n        quote generated: true do\n          def __forward__(unquote(plug)) do\n            unquote(forward)\n          end\n\n          def __verify_route__(unquote(path)) do\n            {{unquote(plug), unquote(forward), unquote(Macro.escape(plug_opts))},\n             unquote(warn_on_verify?)}\n          end\n        end\n\n      _ ->\n        quote generated: true do\n          def __verify_route__(unquote(path)) do\n            {nil, unquote(warn_on_verify?)}\n          end\n        end\n    end\n  end\n\n  defp build_match({route, expr}, {acc_pipes, known_pipes}) do\n    {pipe_name, acc_pipes, known_pipes} = build_match_pipes(route, acc_pipes, known_pipes)\n\n    %{\n      prepare: prepare,\n      dispatch: dispatch,\n      verb_match: verb_match,\n      path_params: path_params,\n      hosts: hosts,\n      path: path\n    } = expr\n\n    clauses =\n      for host <- hosts do\n        quote line: route.line do\n          def __match_route__(unquote(path), unquote(verb_match), unquote(host)) do\n            {unquote(build_metadata(route, path_params)),\n             fn var!(conn, :conn), %{path_params: var!(path_params, :conn)} ->\n               unquote(prepare)\n             end, &(unquote(Macro.var(pipe_name, __MODULE__)) / 1), unquote(dispatch)}\n          end\n        end\n      end\n\n    {clauses, {acc_pipes, known_pipes}}\n  end\n\n  defp build_match_pipes(route, acc_pipes, known_pipes) do\n    %{pipe_through: pipe_through} = route\n\n    case known_pipes do\n      %{^pipe_through => name} ->\n        {name, acc_pipes, known_pipes}\n\n      %{} ->\n        name = :\"__pipe_through#{map_size(known_pipes)}__\"\n        acc_pipes = [build_pipes(name, pipe_through) | acc_pipes]\n        known_pipes = Map.put(known_pipes, pipe_through, name)\n        {name, acc_pipes, known_pipes}\n    end\n  end\n\n  defp build_metadata(route, path_params) do\n    %{\n      path: path,\n      plug: plug,\n      plug_opts: plug_opts,\n      pipe_through: pipe_through,\n      metadata: metadata\n    } = route\n\n    pairs = [\n      conn: nil,\n      route: path,\n      plug: plug,\n      plug_opts: Macro.escape(plug_opts),\n      path_params: path_params,\n      pipe_through: pipe_through\n    ]\n\n    {:%{}, [], pairs ++ Macro.escape(Map.to_list(metadata))}\n  end\n\n  defp build_pipes(name, []) do\n    quote do\n      defp unquote(name)(conn), do: conn\n    end\n  end\n\n  defp build_pipes(name, pipe_through) do\n    plugs = pipe_through |> Enum.reverse() |> Enum.map(&{&1, [], true})\n    opts = [init_mode: Phoenix.plug_init_mode(), log_on_halt: :debug]\n    {conn, body} = Plug.Builder.compile(__ENV__, plugs, opts)\n\n    quote do\n      defp unquote(name)(unquote(conn)), do: unquote(body)\n    end\n  end\n\n  @doc \"\"\"\n  Generates a route match based on an arbitrary HTTP method.\n\n  Useful for defining routes not included in the built-in macros.\n\n  The catch-all verb, `:*`, may also be used to match all HTTP methods.\n\n  ## Options\n\n    * `:as` - configures the named helper. If `nil`, does not generate\n      a helper. Has no effect when using verified routes exclusively\n    * `:alias` - configure if the scope alias should be applied to the route.\n      Defaults to true, disables scoping if false.\n    * `:log` - the level to log the route dispatching under, may be set to false. Defaults to\n      `:debug`. Route dispatching contains information about how the route is handled (which controller\n      action is called, what parameters are available and which pipelines are used) and is separate from\n      the plug level logging. To alter the plug log level, please see\n      https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-dynamic-log-level.\n    * `:private` - a map of private data to merge into the connection\n      when a route matches\n    * `:assigns` - a map of data to merge into the connection when a route matches\n    * `:metadata` - a map of metadata used by the telemetry events and returned by\n      `route_info/4`. The `:mfa` field is used by telemetry to print logs and by the\n      router to emit compile time checks. Custom fields may be added.\n    * `:warn_on_verify` - the boolean for whether matches to this route trigger\n      an unmatched route warning for `Phoenix.VerifiedRoutes`. It is useful to ignore\n      an otherwise catch-all route definition from being matched when verifying routes.\n      Defaults `false`.\n\n  ## Examples\n\n      match(:move, \"/events/:id\", EventController, :move)\n\n      match(:*, \"/any\", SomeController, :any)\n\n  \"\"\"\n  defmacro match(verb, path, plug, plug_opts, options \\\\ []) do\n    add_route(:match, verb, path, expand_alias(plug, __CALLER__), plug_opts, options)\n  end\n\n  for verb <- @http_methods do\n    @doc \"\"\"\n    Generates a route to handle a #{verb} request to the given path.\n\n        #{verb}(\"/events/:id\", EventController, :action)\n\n    See `match/5` for options.\n\n    #{if verb == :head do\n      \"\"\"\n      ## Compatibility with `Plug.Head`\n\n      By default, Phoenix applications include `Plug.Head` in their endpoint,\n      which converts HEAD requests into regular GET requests. Therefore, if\n      you intend to use `head/4` in your router, you need to move `Plug.Head`\n      to inside your router in a way it does not conflict with the paths given\n      to `head/4`.\n      \"\"\"\n    end}\n    \"\"\"\n    defmacro unquote(verb)(path, plug, plug_opts, options \\\\ []) do\n      add_route(:match, unquote(verb), path, expand_alias(plug, __CALLER__), plug_opts, options)\n    end\n  end\n\n  defp add_route(kind, verb, path, plug, plug_opts, options) do\n    quote do\n      @phoenix_routes Scope.route(\n                        __ENV__.line,\n                        __ENV__.module,\n                        unquote(kind),\n                        unquote(verb),\n                        unquote(path),\n                        unquote(plug),\n                        unquote(plug_opts),\n                        unquote(options)\n                      )\n    end\n  end\n\n  @doc \"\"\"\n  Defines a plug pipeline.\n\n  Pipelines are defined at the router root and can be used\n  from any scope.\n\n  ## Examples\n\n      pipeline :api do\n        plug :token_authentication\n        plug :dispatch\n      end\n\n  A scope may then use this pipeline as:\n\n      scope \"/\" do\n        pipe_through :api\n      end\n\n  Every time `pipe_through/1` is called, the new pipelines\n  are appended to the ones previously given.\n  \"\"\"\n  defmacro pipeline(plug, do: block) do\n    with true <- is_atom(plug),\n         imports = __CALLER__.macros ++ __CALLER__.functions,\n         {mod, _} <- Enum.find(imports, fn {_, imports} -> {plug, 2} in imports end) do\n      raise ArgumentError,\n            \"cannot define pipeline named #{inspect(plug)} \" <>\n              \"because there is an import from #{inspect(mod)} with the same name\"\n    end\n\n    block =\n      quote do\n        plug = unquote(plug)\n        @phoenix_pipeline []\n        unquote(block)\n      end\n\n    compiler =\n      quote unquote: false do\n        Scope.pipeline(__MODULE__, plug)\n\n        {conn, body} =\n          Plug.Builder.compile(__ENV__, @phoenix_pipeline, init_mode: Phoenix.plug_init_mode())\n\n        def unquote(plug)(unquote(conn), _) do\n          try do\n            unquote(body)\n          rescue\n            e in Plug.Conn.WrapperError ->\n              Plug.Conn.WrapperError.reraise(e)\n          catch\n            :error, reason ->\n              Plug.Conn.WrapperError.reraise(unquote(conn), :error, reason, __STACKTRACE__)\n          end\n        end\n\n        @phoenix_pipeline nil\n      end\n\n    quote do\n      try do\n        unquote(block)\n        unquote(compiler)\n      after\n        :ok\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Defines a plug inside a pipeline.\n\n  See `pipeline/2` for more information.\n  \"\"\"\n  defmacro plug(plug, opts \\\\ []) do\n    {plug, opts} = expand_plug_and_opts(plug, opts, __CALLER__)\n\n    quote do\n      if pipeline = @phoenix_pipeline do\n        @phoenix_pipeline [{unquote(plug), unquote(opts), true} | pipeline]\n      else\n        raise \"cannot define plug at the router level, plug must be defined inside a pipeline\"\n      end\n    end\n  end\n\n  defp expand_plug_and_opts(plug, opts, caller) do\n    runtime? = Phoenix.plug_init_mode() == :runtime\n\n    plug =\n      if runtime? do\n        expand_alias(plug, caller)\n      else\n        plug\n      end\n\n    opts =\n      if runtime? and Macro.quoted_literal?(opts) do\n        Macro.prewalk(opts, &expand_alias(&1, caller))\n      else\n        opts\n      end\n\n    {plug, opts}\n  end\n\n  defp expand_alias({:__aliases__, _, _} = alias, env),\n    do: Macro.expand(alias, %{env | function: {:init, 1}})\n\n  defp expand_alias(other, _env), do: other\n\n  @doc \"\"\"\n  Defines a list of plugs (and pipelines) to send the connection through.\n\n  Plugs are specified using the atom name of any imported 2-arity function\n  which takes a `Plug.Conn` and options and returns a `Plug.Conn`. For\n  example, `:require_authenticated_user`.\n\n  Pipelines are defined in the router, see `pipeline/2` for more information.\n\n      pipe_through [:require_authenticated_user, :my_browser_pipeline]\n\n  ## Multiple invocations\n\n  `pipe_through/1` can be invoked multiple times within the same scope. Each\n  invocation appends new plugs and pipelines to run, which are applied to all\n  routes **after** the `pipe_through/1` invocation. For example:\n\n      scope \"/\" do\n        pipe_through [:browser]\n        get \"/\", HomeController, :index\n\n        pipe_through [:require_authenticated_user]\n        get \"/settings\", UserController, :edit\n      end\n\n  In the example above, `/` pipes through `browser` only, while `/settings` pipes\n  through both `browser` and `require_authenticated_user`. Therefore, to avoid\n  confusion, we recommend a single `pipe_through` at the top of each scope:\n\n      scope \"/\" do\n        pipe_through [:browser]\n        get \"/\", HomeController, :index\n      end\n\n      scope \"/\" do\n        pipe_through [:browser, :require_authenticated_user]\n        get \"/settings\", UserController, :edit\n      end\n  \"\"\"\n  defmacro pipe_through(pipes) do\n    pipes =\n      if Phoenix.plug_init_mode() == :runtime and Macro.quoted_literal?(pipes) do\n        Macro.prewalk(pipes, &expand_alias(&1, __CALLER__))\n      else\n        pipes\n      end\n\n    quote do\n      if pipeline = @phoenix_pipeline do\n        raise \"cannot pipe_through inside a pipeline\"\n      else\n        Scope.pipe_through(__MODULE__, unquote(pipes))\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Defines \"RESTful\" routes for a resource.\n\n  The given definition:\n\n      resources \"/users\", UserController\n\n  will include routes to the following actions:\n\n    * `GET /users` => `:index`\n    * `GET /users/new` => `:new`\n    * `POST /users` => `:create`\n    * `GET /users/:id` => `:show`\n    * `GET /users/:id/edit` => `:edit`\n    * `PATCH /users/:id` => `:update`\n    * `PUT /users/:id` => `:update`\n    * `DELETE /users/:id` => `:delete`\n\n  ## Options\n\n  This macro accepts a set of options:\n\n    * `:only` - a list of actions to generate routes for, for example: `[:show, :edit]`\n    * `:except` - a list of actions to exclude generated routes from, for example: `[:delete]`\n    * `:param` - the name of the parameter for this resource, defaults to `\"id\"`\n    * `:name` - the prefix for this resource. This is used for the named helper\n      and as the prefix for the parameter in nested resources. The default value\n      is automatically derived from the controller name, i.e. `UserController` will\n      have name `\"user\"`\n    * `:as` - configures the named helper. If `nil`, does not generate\n      a helper. Has no effect when using verified routes exclusively\n    * `:singleton` - defines routes for a singleton resource that is looked up by\n      the client without referencing an ID. Read below for more information\n\n  ## Singleton resources\n\n  When a resource needs to be looked up without referencing an ID, because\n  it contains only a single entry in the given context, the `:singleton`\n  option can be used to generate a set of routes that are specific to\n  such single resource:\n\n    * `GET /user` => `:show`\n    * `GET /user/new` => `:new`\n    * `POST /user` => `:create`\n    * `GET /user/edit` => `:edit`\n    * `PATCH /user` => `:update`\n    * `PUT /user` => `:update`\n    * `DELETE /user` => `:delete`\n\n  Usage example:\n\n      resources \"/account\", AccountController, only: [:show], singleton: true\n\n  ## Nested Resources\n\n  This macro also supports passing a nested block of route definitions.\n  This is helpful for nesting children resources within their parents to\n  generate nested routes.\n\n  The given definition:\n\n      resources \"/users\", UserController do\n        resources \"/posts\", PostController\n      end\n\n  will include the following routes:\n\n  ```console\n  user_post_path  GET     /users/:user_id/posts           PostController :index\n  user_post_path  GET     /users/:user_id/posts/:id/edit  PostController :edit\n  user_post_path  GET     /users/:user_id/posts/new       PostController :new\n  user_post_path  GET     /users/:user_id/posts/:id       PostController :show\n  user_post_path  POST    /users/:user_id/posts           PostController :create\n  user_post_path  PATCH   /users/:user_id/posts/:id       PostController :update\n                  PUT     /users/:user_id/posts/:id       PostController :update\n  user_post_path  DELETE  /users/:user_id/posts/:id       PostController :delete\n  ```\n  \"\"\"\n  defmacro resources(path, controller, opts, do: nested_context) do\n    add_resources(path, controller, opts, do: nested_context)\n  end\n\n  @doc \"\"\"\n  See `resources/4`.\n  \"\"\"\n  defmacro resources(path, controller, do: nested_context) do\n    add_resources(path, controller, [], do: nested_context)\n  end\n\n  defmacro resources(path, controller, opts) do\n    add_resources(path, controller, opts, do: nil)\n  end\n\n  @doc \"\"\"\n  See `resources/4`.\n  \"\"\"\n  defmacro resources(path, controller) do\n    add_resources(path, controller, [], do: nil)\n  end\n\n  defp add_resources(path, controller, options, do: context) do\n    scope =\n      if context do\n        quote do\n          scope(resource.member, do: unquote(context))\n        end\n      end\n\n    quote do\n      resource = Resource.build(unquote(path), unquote(controller), unquote(options))\n      var!(add_resources, Phoenix.Router).(resource)\n      unquote(scope)\n    end\n  end\n\n  @doc \"\"\"\n  Defines a scope in which routes can be nested.\n\n  ## Examples\n\n      scope path: \"/api/v1\", alias: API.V1 do\n        get \"/pages/:id\", PageController, :show\n      end\n\n  The generated route above will match on the path `\"/api/v1/pages/:id\"`\n  and will dispatch to `:show` action in `API.V1.PageController`. A named\n  helper `api_v1_page_path` will also be generated.\n\n  ## Options\n\n  The supported options are:\n\n    * `:path` - a string containing the path scope.\n    * `:as` - a string or atom containing the named helper scope. When set to\n      false, it resets the nested helper scopes. Has no effect when using verified\n      routes exclusively\n    * `:alias` - an alias (atom) containing the controller scope. When set to\n      false, it resets all nested aliases.\n    * `:host` - a string or list of strings containing the host scope, or prefix host scope,\n      ie `\"foo.bar.com\"`, `\"foo.\"`\n    * `:private` - a map of private data to merge into the connection when a route matches\n    * `:assigns` - a map of data to merge into the connection when a route matches\n    * `:log` - the level to log the route dispatching under, may be set to false. Defaults to\n      `:debug`. Route dispatching contains information about how the route is handled (which controller\n      action is called, what parameters are available and which pipelines are used) and is separate from\n      the plug level logging. To alter the plug log level, please see\n      https://hexdocs.pm/phoenix/Phoenix.Logger.html#module-dynamic-log-level.\n\n  \"\"\"\n  defmacro scope(options, do: context) do\n    options =\n      if Macro.quoted_literal?(options) do\n        Macro.prewalk(options, &expand_alias(&1, __CALLER__))\n      else\n        options\n      end\n\n    do_scope(options, context)\n  end\n\n  @doc \"\"\"\n  Define a scope with the given path.\n\n  This function is a shortcut for:\n\n      scope path: path do\n        ...\n      end\n\n  ## Examples\n\n      scope \"/v1\", host: \"api.\" do\n        get \"/pages/:id\", PageController, :show\n      end\n\n  \"\"\"\n  defmacro scope(path, options, do: context) do\n    options =\n      if Macro.quoted_literal?(options) do\n        Macro.prewalk(options, &expand_alias(&1, __CALLER__))\n      else\n        options\n      end\n\n    options =\n      quote do\n        path = unquote(path)\n\n        case unquote(options) do\n          alias when is_atom(alias) -> [path: path, alias: alias]\n          options when is_list(options) -> Keyword.put(options, :path, path)\n        end\n      end\n\n    do_scope(options, context)\n  end\n\n  @doc \"\"\"\n  Defines a scope with the given path and alias.\n\n  This function is a shortcut for:\n\n      scope path: path, alias: alias do\n        ...\n      end\n\n  ## Examples\n\n      scope \"/v1\", API.V1, host: \"api.\" do\n        get \"/pages/:id\", PageController, :show\n      end\n\n  \"\"\"\n  defmacro scope(path, alias, options, do: context) do\n    alias = expand_alias(alias, __CALLER__)\n\n    options =\n      quote do\n        unquote(options)\n        |> Keyword.put(:path, unquote(path))\n        |> Keyword.put(:alias, unquote(alias))\n      end\n\n    do_scope(options, context)\n  end\n\n  defp do_scope(options, context) do\n    quote do\n      Scope.push(__MODULE__, unquote(options))\n\n      try do\n        unquote(context)\n      after\n        Scope.pop(__MODULE__)\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Returns the full alias with the current scope's aliased prefix.\n\n  Useful for applying the same short-hand alias handling to\n  other values besides the second argument in route definitions.\n\n  ## Examples\n\n      scope \"/\", MyPrefix do\n        get \"/\", ProxyPlug, controller: scoped_alias(__MODULE__, MyController)\n      end\n  \"\"\"\n  @doc type: :reflection\n  def scoped_alias(router_module, alias) do\n    Scope.expand_alias(router_module, alias)\n  end\n\n  @doc \"\"\"\n  Returns the full path with the current scope's path prefix.\n  \"\"\"\n  @doc type: :reflection\n  def scoped_path(router_module, path) do\n    Scope.full_path(router_module, path)\n  end\n\n  @doc \"\"\"\n  Forwards a request at the given path to a plug.\n\n  This is commonly used to forward all subroutes to another Plug.\n  For example:\n\n      forward \"/admin\", SomeLib.AdminDashboard\n\n  The above will allow `SomeLib.AdminDashboard` to handle `/admin`,\n  `/admin/foo`, `/admin/bar/baz`, and so on. Furthermore,\n  `SomeLib.AdminDashboard` does not to be aware of the prefix it\n  is mounted in. From its point of view, the routes above are simply\n  handled as `/`, `/foo`, and `/bar/baz`.\n\n  A common use case for `forward` is for sharing a router between\n  applications or even breaking a big router into smaller ones.\n  However, in other for route generation to route accordingly, you\n  can only forward to a given `Phoenix.Router` once.\n\n  The router pipelines will be invoked prior to forwarding the\n  connection.\n\n  ## Examples\n\n      scope \"/\", MyApp do\n        pipe_through [:browser, :admin]\n\n        forward \"/admin\", SomeLib.AdminDashboard\n        forward \"/api\", ApiRouter\n      end\n\n  \"\"\"\n  defmacro forward(path, plug, plug_opts \\\\ [], router_opts \\\\ []) do\n    {plug, plug_opts} = expand_plug_and_opts(plug, plug_opts, __CALLER__)\n    router_opts = Keyword.put(router_opts, :as, nil)\n\n    quote unquote: true, bind_quoted: [path: path, plug: plug] do\n      unquote(add_route(:forward, :*, path, plug, plug_opts, router_opts))\n    end\n  end\n\n  @doc \"\"\"\n  Returns all routes information from the given router.\n  \"\"\"\n  def routes(router) do\n    router.__routes__()\n  end\n\n  @doc \"\"\"\n  Returns the compile-time route info and runtime path params for a request.\n\n  The `path` can be either a string or the `path_info` segments.\n\n  A map of metadata is returned with the following keys:\n\n    * `:log` - the configured log level. For example `:debug`\n    * `:path_params` - the map of runtime path params\n    * `:pipe_through` - the list of pipelines for the route's scope, for example `[:browser]`\n    * `:plug` - the plug to dispatch the route to, for example `AppWeb.PostController`\n    * `:plug_opts` - the options to pass when calling the plug, for example: `:index`\n    * `:route` - the string route pattern, such as `\"/posts/:id\"`\n\n  ## Examples\n\n      iex> Phoenix.Router.route_info(AppWeb.Router, \"GET\", \"/posts/123\", \"myhost\")\n      %{\n        log: :debug,\n        path_params: %{\"id\" => \"123\"},\n        pipe_through: [:browser],\n        plug: AppWeb.PostController,\n        plug_opts: :show,\n        route: \"/posts/:id\",\n      }\n\n      iex> Phoenix.Router.route_info(MyRouter, \"GET\", \"/not-exists\", \"myhost\")\n      :error\n  \"\"\"\n  @doc type: :reflection\n  def route_info(router, method, path, host) when is_binary(path) do\n    split_path = for segment <- String.split(path, \"/\"), segment != \"\", do: segment\n    route_info(router, method, split_path, host)\n  end\n\n  def route_info(router, method, split_path, host) when is_list(split_path) do\n    with {metadata, _prepare, _pipeline, {_plug, _opts}} <-\n           router.__match_route__(split_path, method, host) do\n      Map.delete(metadata, :conn)\n    end\n  end\n\n  @doc false\n  def __formatted_routes__(router) do\n    Enum.flat_map(router.__routes__(), fn route ->\n      Code.ensure_loaded(route.plug)\n\n      if function_exported?(route.plug, :formatted_routes, 1) do\n        route.plug_opts\n        |> route.plug.formatted_routes()\n        |> Enum.map(fn nested_route ->\n          route = %{\n            route\n            | path: Path.join(route.path, nested_route.path),\n              verb: nested_route.verb\n          }\n\n          Map.put(route, :label, nested_route.label)\n        end)\n      else\n        plug =\n          case route.metadata[:mfa] do\n            {module, _, _} -> module\n            _ -> route.plug\n          end\n\n        label = \"#{inspect(plug)} #{inspect(route.plug_opts)}\"\n\n        [\n          %{\n            helper: route.helper,\n            verb: route.verb,\n            path: route.path,\n            label: label\n          }\n        ]\n      end\n    end)\n  end\n\n  @doc false\n  def __verified_route__?(router, split_path) do\n    case router.__verify_route__(split_path) do\n      {_forward_plug, true = _warn_on_verify?} ->\n        false\n\n      {nil = _forward_plug, false = _warn_on_verify?} ->\n        true\n\n      {{router, script_name, plug_opts}, false = _warn_on_verify?} ->\n        Code.ensure_loaded(router)\n\n        if function_exported?(router, :verified_route?, 2) do\n          router.verified_route?(plug_opts, split_path -- script_name)\n        else\n          true\n        end\n\n      :error ->\n        false\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/socket/message.ex",
    "content": "defmodule Phoenix.Socket.Message do\n  @moduledoc \"\"\"\n  Defines a message dispatched over transport to channels and vice-versa.\n\n  The message format requires the following keys:\n\n    * `:topic` - The string topic or topic:subtopic pair namespace, for\n      example \"messages\", \"messages:123\"\n    * `:event`- The string event name, for example \"phx_join\"\n    * `:payload` - The message payload\n    * `:ref` - The unique string ref\n    * `:join_ref` - The unique string ref when joining\n\n  \"\"\"\n\n  @type t :: %Phoenix.Socket.Message{}\n  defstruct topic: nil, event: nil, payload: nil, ref: nil, join_ref: nil\n\n  @doc \"\"\"\n  Converts a map with string keys into a message struct.\n\n  Raises `Phoenix.Socket.InvalidMessageError` if not valid.\n  \"\"\"\n  def from_map!(map) when is_map(map) do\n    try do\n      %Phoenix.Socket.Message{\n        topic: Map.fetch!(map, \"topic\"),\n        event: Map.fetch!(map, \"event\"),\n        payload: Map.fetch!(map, \"payload\"),\n        ref: Map.fetch!(map, \"ref\"),\n        join_ref: Map.get(map, \"join_ref\")\n      }\n    rescue\n      err in [KeyError] ->\n        raise Phoenix.Socket.InvalidMessageError, \"missing key #{inspect(err.key)}\"\n    end\n  end\n\n  defimpl Inspect do\n    def inspect(%Phoenix.Socket.Message{} = msg, opts) do\n      processed_msg = process_message(msg)\n      Inspect.Any.inspect(processed_msg, opts)\n    end\n\n    defp process_message(%{payload: payload} = msg) when is_map(payload) do\n      %{msg | payload: Phoenix.Logger.filter_values(payload)}\n    end\n\n    defp process_message(msg), do: msg\n  end\nend\n\ndefmodule Phoenix.Socket.Reply do\n  @moduledoc \"\"\"\n  Defines a reply sent from channels to transports.\n\n  The message format requires the following keys:\n\n    * `:topic` - The string topic or topic:subtopic pair namespace, for example \"messages\", \"messages:123\"\n    * `:status` - The reply status as an atom\n    * `:payload` - The reply payload\n    * `:ref` - The unique string ref\n    * `:join_ref` - The unique string ref when joining\n\n  \"\"\"\n\n  @type t :: %Phoenix.Socket.Reply{}\n  defstruct topic: nil, status: nil, payload: nil, ref: nil, join_ref: nil\nend\n\ndefmodule Phoenix.Socket.Broadcast do\n  @moduledoc \"\"\"\n  Defines a message sent from pubsub to channels and vice-versa.\n\n  The message format requires the following keys:\n\n    * `:topic` - The string topic or topic:subtopic pair namespace, for example \"messages\", \"messages:123\"\n    * `:event`- The string event name, for example \"phx_join\"\n    * `:payload` - The message payload\n\n  \"\"\"\n\n  @type t :: %Phoenix.Socket.Broadcast{}\n  defstruct topic: nil, event: nil, payload: nil\nend\n"
  },
  {
    "path": "lib/phoenix/socket/pool_supervisor.ex",
    "content": "defmodule Phoenix.Socket.PoolSupervisor do\n  @moduledoc false\n  use Supervisor\n\n  def start_link({endpoint, name, partitions}) do\n    Supervisor.start_link(\n      __MODULE__,\n      {endpoint, name, partitions},\n      name: Module.concat(endpoint, name)\n    )\n  end\n\n  def start_child(socket, key, spec) do\n    %{endpoint: endpoint, handler: name} = socket\n\n    case endpoint.config({:socket, name}) do\n      ets when not is_nil(ets) ->\n        partitions = :ets.lookup_element(ets, :partitions, 2)\n        sup = :ets.lookup_element(ets, :erlang.phash2(key, partitions), 2)\n        DynamicSupervisor.start_child(sup, spec)\n\n      nil ->\n        raise ArgumentError, \"\"\"\n        no socket supervision tree found for #{inspect(name)}.\n\n        Ensure your #{inspect(endpoint)} contains a socket mount, for example:\n\n            socket \"/socket\", #{inspect(name)},\n              websocket: true,\n              longpoll: true\n        \"\"\"\n    end\n  end\n\n  def start_pooled(ref, i) do\n    case DynamicSupervisor.start_link(strategy: :one_for_one) do\n      {:ok, pid} ->\n        :ets.insert(ref, {i, pid})\n        {:ok, pid}\n\n      {:error, reason} ->\n        {:error, reason}\n    end\n  end\n\n  @impl true\n  def init({endpoint, name, partitions}) do\n    ref = :ets.new(name, [:public, read_concurrency: true])\n    :ets.insert(ref, {:partitions, partitions})\n    Phoenix.Config.permanent(endpoint, {:socket, name}, ref)\n\n    children =\n      for i <- 0..(partitions - 1) do\n        %{\n          id: i,\n          start: {__MODULE__, :start_pooled, [ref, i]},\n          type: :supervisor,\n          shutdown: :infinity\n        }\n      end\n\n    Supervisor.init(children, strategy: :one_for_one)\n  end\nend\n\ndefmodule Phoenix.Socket.PoolDrainer do\n  @moduledoc false\n  use GenServer\n\n  def child_spec({_endpoint, name, opts} = tuple) do\n    # The process should terminate within shutdown but,\n    # in case it doesn't, we will be killed if we exceed\n    # double of that\n    %{\n      id: {:terminator, name},\n      start: {__MODULE__, :start_link, [tuple]},\n      shutdown: Keyword.get(opts[:drainer], :shutdown, 30_000)\n    }\n  end\n\n  def start_link(tuple) do\n    GenServer.start_link(__MODULE__, tuple)\n  end\n\n  @impl true\n  def init({endpoint, name, opts}) do\n    Process.flag(:trap_exit, true)\n    size = Keyword.get(opts[:drainer], :batch_size, 10_000)\n    interval = Keyword.get(opts[:drainer], :batch_interval, 2_000)\n    log_level = Keyword.get(opts[:drainer], :log, opts[:log] || :info)\n    {:ok, {endpoint, name, size, interval, log_level}}\n  end\n\n  @impl true\n  def terminate(_reason, {endpoint, name, size, interval, log_level}) do\n    ets = endpoint.config({:socket, name})\n    partitions = :ets.lookup_element(ets, :partitions, 2)\n\n    {collection, total} =\n      Enum.map_reduce(0..(partitions - 1), 0, fn index, total ->\n        try do\n          sup = :ets.lookup_element(ets, index, 2)\n          children = DynamicSupervisor.which_children(sup)\n          {Enum.map(children, &elem(&1, 1)), total + length(children)}\n        catch\n          _, _ -> {[], total}\n        end\n      end)\n\n    rounds = div(total, size) + 1\n\n    for {pids, index} <-\n          collection |> Stream.concat() |> Stream.chunk_every(size) |> Stream.with_index(1) do\n      count = if index == rounds, do: length(pids), else: size\n\n      :telemetry.execute(\n        [:phoenix, :socket_drain],\n        %{count: count, total: total, index: index, rounds: rounds},\n        %{\n          endpoint: endpoint,\n          socket: name,\n          interval: interval,\n          log: log_level\n        }\n      )\n\n      spawn(fn ->\n        for pid <- pids do\n          send(pid, %Phoenix.Socket.Broadcast{event: \"phx_drain\"})\n        end\n      end)\n\n      if index < rounds do\n        Process.sleep(interval)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/socket/serializer.ex",
    "content": "defmodule Phoenix.Socket.Serializer do\n  @moduledoc \"\"\"\n  A behaviour that serializes incoming and outgoing socket messages.\n\n  By default Phoenix provides a serializer that encodes to JSON and\n  decodes JSON messages.\n\n  Custom serializers may be configured in the socket.\n  \"\"\"\n\n  @doc \"\"\"\n  Encodes a `Phoenix.Socket.Broadcast` struct to fastlane format.\n  \"\"\"\n  @callback fastlane!(Phoenix.Socket.Broadcast.t()) ::\n              {:socket_push, :text, iodata()}\n              | {:socket_push, :binary, iodata()}\n\n  @doc \"\"\"\n  Encodes `Phoenix.Socket.Message` and `Phoenix.Socket.Reply` structs to push format.\n  \"\"\"\n  @callback encode!(Phoenix.Socket.Message.t() | Phoenix.Socket.Reply.t()) ::\n              {:socket_push, :text, iodata()}\n              | {:socket_push, :binary, iodata()}\n\n  @doc \"\"\"\n  Decodes iodata into `Phoenix.Socket.Message` struct.\n  \"\"\"\n  @callback decode!(iodata, options :: Keyword.t()) :: Phoenix.Socket.Message.t()\nend\n"
  },
  {
    "path": "lib/phoenix/socket/serializers/v1_json_serializer.ex",
    "content": "defmodule Phoenix.Socket.V1.JSONSerializer do\n  @moduledoc false\n  @behaviour Phoenix.Socket.Serializer\n\n  alias Phoenix.Socket.{Broadcast, Message, Reply}\n\n  @impl true\n  def fastlane!(%Broadcast{} = msg) do\n    map = %Message{topic: msg.topic, event: msg.event, payload: msg.payload}\n    {:socket_push, :text, encode_v1_fields_only(map)}\n  end\n\n  @impl true\n  def encode!(%Reply{} = reply) do\n    map = %Message{\n      topic: reply.topic,\n      event: \"phx_reply\",\n      ref: reply.ref,\n      payload: %{status: reply.status, response: reply.payload}\n    }\n\n    {:socket_push, :text, encode_v1_fields_only(map)}\n  end\n\n  def encode!(%Message{} = map) do\n    {:socket_push, :text, encode_v1_fields_only(map)}\n  end\n\n  @impl true\n  def decode!(message, _opts) do\n    payload = Phoenix.json_library().decode!(message)\n\n    case payload do\n      %{} ->\n        Phoenix.Socket.Message.from_map!(payload)\n\n      other ->\n        raise \"V1 JSON Serializer expected a map, got #{inspect(other)}\"\n    end\n  end\n\n  defp encode_v1_fields_only(%Message{} = msg) do\n    msg\n    |> Map.take([:topic, :event, :payload, :ref])\n    |> Phoenix.json_library().encode_to_iodata!()\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/socket/serializers/v2_json_serializer.ex",
    "content": "defmodule Phoenix.Socket.V2.JSONSerializer do\n  @moduledoc false\n  @behaviour Phoenix.Socket.Serializer\n\n  @push 0\n  @reply 1\n  @broadcast 2\n\n  alias Phoenix.Socket.{Broadcast, Message, Reply}\n\n  @impl true\n  def fastlane!(%Broadcast{payload: {:binary, data}} = msg) do\n    topic_size = byte_size!(msg.topic, :topic, 255)\n    event_size = byte_size!(msg.event, :event, 255)\n\n    bin = <<\n      @broadcast::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def fastlane!(%Broadcast{payload: %{}} = msg) do\n    data = Phoenix.json_library().encode_to_iodata!([nil, nil, msg.topic, msg.event, msg.payload])\n    {:socket_push, :text, data}\n  end\n\n  def fastlane!(%Broadcast{payload: invalid}) do\n    raise ArgumentError, \"expected broadcasted payload to be a map, got: #{inspect(invalid)}\"\n  end\n\n  @impl true\n  def encode!(%Reply{payload: {:binary, data}} = reply) do\n    status = to_string(reply.status)\n    join_ref = to_string(reply.join_ref)\n    ref = to_string(reply.ref)\n    join_ref_size = byte_size!(join_ref, :join_ref, 255)\n    ref_size = byte_size!(ref, :ref, 255)\n    topic_size = byte_size!(reply.topic, :topic, 255)\n    status_size = byte_size!(status, :status, 255)\n\n    bin = <<\n      @reply::size(8),\n      join_ref_size::size(8),\n      ref_size::size(8),\n      topic_size::size(8),\n      status_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      ref::binary-size(ref_size),\n      reply.topic::binary-size(topic_size),\n      status::binary-size(status_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def encode!(%Reply{} = reply) do\n    data = [\n      reply.join_ref,\n      reply.ref,\n      reply.topic,\n      \"phx_reply\",\n      %{status: reply.status, response: reply.payload}\n    ]\n\n    {:socket_push, :text, Phoenix.json_library().encode_to_iodata!(data)}\n  end\n\n  def encode!(%Message{payload: {:binary, data}} = msg) do\n    join_ref = to_string(msg.join_ref)\n    join_ref_size = byte_size!(join_ref, :join_ref, 255)\n    topic_size = byte_size!(msg.topic, :topic, 255)\n    event_size = byte_size!(msg.event, :event, 255)\n\n    bin = <<\n      @push::size(8),\n      join_ref_size::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n\n    {:socket_push, :binary, bin}\n  end\n\n  def encode!(%Message{payload: %{}} = msg) do\n    data = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]\n    {:socket_push, :text, Phoenix.json_library().encode_to_iodata!(data)}\n  end\n\n  def encode!(%Message{payload: invalid}) do\n    raise ArgumentError, \"expected payload to be a map, got: #{inspect(invalid)}\"\n  end\n\n  @impl true\n  def decode!(raw_message, opts) do\n    case Keyword.fetch(opts, :opcode) do\n      {:ok, :text} -> decode_text(raw_message)\n      {:ok, :binary} -> decode_binary(raw_message)\n    end\n  end\n\n  defp decode_text(raw_message) do\n    [join_ref, ref, topic, event, payload | _] = Phoenix.json_library().decode!(raw_message)\n\n    %Message{\n      topic: topic,\n      event: event,\n      payload: payload,\n      ref: ref,\n      join_ref: join_ref\n    }\n  end\n\n  defp decode_binary(<<\n         @push::size(8),\n         join_ref_size::size(8),\n         ref_size::size(8),\n         topic_size::size(8),\n         event_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         ref::binary-size(ref_size),\n         topic::binary-size(topic_size),\n         event::binary-size(event_size),\n         data::binary\n       >>) do\n    %Message{\n      topic: topic,\n      event: event,\n      payload: {:binary, data},\n      ref: ref,\n      join_ref: join_ref\n    }\n  end\n\n  defp byte_size!(bin, kind, max) do\n    case byte_size(bin) do\n      size when size <= max ->\n        size\n\n      oversized ->\n        raise ArgumentError, \"\"\"\n        unable to convert #{kind} to binary.\n\n            #{inspect(bin)}\n\n        must be less than or equal to #{max} bytes, but is #{oversized} bytes.\n        \"\"\"\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/socket/transport.ex",
    "content": "defmodule Phoenix.Socket.Transport do\n  @moduledoc \"\"\"\n  Outlines the Socket <-> Transport communication.\n\n  Each transport, such as websockets and longpolling, must interact\n  with a socket. This module defines the functions a transport will\n  invoke on a given socket implementation.\n\n  `Phoenix.Socket` is just one possible implementation of a socket\n  that multiplexes events over multiple channels. If you implement\n  this behaviour, then existing transports can use your new socket\n  implementation, without passing through channels.\n\n  This module also provides guidelines and convenience functions for\n  implementing transports. Albeit its primary goal is to aid in the\n  definition of custom sockets.\n\n  ## Example\n\n  Here is a simple echo socket implementation:\n\n      defmodule EchoSocket do\n        @behaviour Phoenix.Socket.Transport\n\n        def child_spec(opts) do\n          # We won't spawn any process, so let's ignore the child spec\n          :ignore\n        end\n\n        def connect(state) do\n          # Callback to retrieve relevant data from the connection.\n          # The map contains options, params, transport and endpoint keys.\n          {:ok, state}\n        end\n\n        def init(state) do\n          # Now we are effectively inside the process that maintains the socket.\n          {:ok, state}\n        end\n\n        def handle_in({text, _opts}, state) do\n          {:reply, :ok, {:text, text}, state}\n        end\n\n        def handle_info(_, state) do\n          {:ok, state}\n        end\n\n        def terminate(_reason, _state) do\n          :ok\n        end\n      end\n\n  It can be mounted in your endpoint like any other socket:\n\n      socket \"/socket\", EchoSocket, websocket: true, longpoll: true\n\n  You can now interact with the socket under `/socket/websocket`\n  and `/socket/longpoll`.\n\n  ## Custom transports\n\n  Sockets are operated by a transport. When a transport is defined,\n  it usually receives a socket module and the module will be invoked\n  when certain events happen at the transport level. The functions\n  a transport can invoke are the callbacks defined in this module.\n\n  Whenever the transport receives a new connection, it should invoke\n  the `c:connect/1` callback with a map of metadata. Different sockets\n  may require different metadata.\n\n  If the connection is accepted, the transport can move the connection\n  to another process, if so desires, or keep using the same process. The\n  process responsible for managing the socket should then call `c:init/1`.\n\n  For each message received from the client, the transport must call\n  `c:handle_in/2` on the socket. For each informational message the\n  transport receives, it should call `c:handle_info/2` on the socket.\n\n  Transports can optionally implement `c:handle_control/2` for handling\n  control frames such as `:ping` and `:pong`.\n\n  On termination, `c:terminate/2` must be called. A special atom with\n  reason `:closed` can be used to specify that the client terminated\n  the connection.\n\n  ### Booting\n\n  When you list a socket under `Phoenix.Endpoint.socket/3`, Phoenix\n  will automatically start the socket module under its supervision tree,\n  however Phoenix does not manage any transport.\n\n  Whenever your endpoint starts, Phoenix invokes the `child_spec/1` on\n  each listed socket and start that specification under the endpoint\n  supervisor. Since the socket supervision tree is started by the endpoint,\n  any custom transport must be started after the endpoint.\n  \"\"\"\n\n  @type state :: term()\n\n  @doc \"\"\"\n  Returns a child specification for socket management.\n\n  This is invoked only once per socket regardless of\n  the number of transports and should be responsible\n  for setting up any process structure used exclusively\n  by the socket regardless of transports.\n\n  Each socket connection is started by the transport\n  and the process that controls the socket likely\n  belongs to the transport. However, some sockets spawn\n  new processes, such as `Phoenix.Socket` which spawns\n  channels, and this gives the ability to start a\n  supervision tree associated to the socket.\n\n  It receives the socket options from the endpoint,\n  for example:\n\n      socket \"/my_app\", MyApp.Socket, shutdown: 5000\n\n  means `child_spec([shutdown: 5000])` will be invoked.\n\n  `:ignore` means no child spec is necessary for this socket.\n  \"\"\"\n  @callback child_spec(keyword) :: :supervisor.child_spec() | :ignore\n\n  @doc \"\"\"\n  Returns a child specification for terminating the socket.\n\n  This is a process that is started late in the supervision\n  tree with the specific goal of draining connections on\n  application shutdown.\n\n  Similar to `child_spec/1`, it receives the socket options\n  from the endpoint.\n  \"\"\"\n  @callback drainer_spec(keyword) :: :supervisor.child_spec() | :ignore\n\n  @doc \"\"\"\n  Connects to the socket.\n\n  The transport passes a map of metadata and the socket\n  returns `{:ok, state}`, `{:error, reason}` or `:error`.\n  The state must be stored by the transport and returned\n  in all future operations. When `{:error, reason}` is\n  returned, some transports - such as WebSockets - allow\n  customizing the response based on `reason` via a custom\n  `:error_handler`.\n\n  This function is used for authorization purposes and it\n  may be invoked outside of the process that effectively\n  runs the socket.\n\n  In the default `Phoenix.Socket` implementation, the\n  metadata expects the following keys:\n\n    * `:endpoint` - the application endpoint\n    * `:transport` - the transport name\n    * `:params` - the connection parameters\n    * `:options` - a keyword list of transport options, often\n      given by developers when configuring the transport.\n      It must include a `:serializer` field with the list of\n      serializers and their requirements\n\n  \"\"\"\n  @callback connect(transport_info :: map) :: {:ok, state} | {:error, term()} | :error\n\n  @doc \"\"\"\n  Initializes the socket state.\n\n  This must be executed from the process that will effectively\n  operate the socket.\n  \"\"\"\n  @callback init(state) :: {:ok, state}\n\n  @doc \"\"\"\n  Handles incoming socket messages.\n\n  The message is represented as `{payload, options}`. It must\n  return one of:\n\n    * `{:ok, state}` - continues the socket with no reply\n    * `{:reply, status, reply, state}` - continues the socket with reply\n    * `{:stop, reason, state}` - stops the socket\n\n  The `reply` is a tuple contain an `opcode` atom and a message that can\n  be any term. The built-in websocket transport supports both `:text` and\n  `:binary` opcode and the message must be always iodata. Long polling only\n  supports text opcode.\n  \"\"\"\n  @callback handle_in({message :: term, opts :: keyword}, state) ::\n              {:ok, state}\n              | {:reply, :ok | :error, {opcode :: atom, message :: term}, state}\n              | {:stop, reason :: term, state}\n\n  @doc \"\"\"\n  Handles incoming control frames.\n\n  The message is represented as `{payload, options}`. It must\n  return one of:\n\n    * `{:ok, state}` - continues the socket with no reply\n    * `{:reply, status, reply, state}` - continues the socket with reply\n    * `{:stop, reason, state}` - stops the socket\n\n  Control frames are only supported when using websockets.\n\n  The `options` contains an `opcode` key, this will be either `:ping` or\n  `:pong`.\n\n  If a control frame doesn't have a payload, then the payload value\n  will be `nil`.\n  \"\"\"\n  @callback handle_control({message :: term, opts :: keyword}, state) ::\n              {:ok, state}\n              | {:reply, :ok | :error, {opcode :: atom, message :: term}, state}\n              | {:stop, reason :: term, state}\n\n  @doc \"\"\"\n  Handles info messages.\n\n  The message is a term. It must return one of:\n\n    * `{:ok, state}` - continues the socket with no reply\n    * `{:push, reply, state}` - continues the socket with reply\n    * `{:stop, reason, state}` - stops the socket\n\n  The `reply` is a tuple contain an `opcode` atom and a message that can\n  be any term. The built-in websocket transport supports both `:text` and\n  `:binary` opcode and the message must be always iodata. Long polling only\n  supports text opcode.\n  \"\"\"\n  @callback handle_info(message :: term, state) ::\n              {:ok, state}\n              | {:push, {opcode :: atom, message :: term}, state}\n              | {:stop, reason :: term, state}\n\n  @doc \"\"\"\n  Invoked on termination.\n\n  If `reason` is `:closed`, it means the client closed the socket. This is\n  considered a `:normal` exit signal, so linked process will not automatically\n  exit. See `Process.exit/2` for more details on exit signals.\n  \"\"\"\n  @callback terminate(reason :: term, state) :: :ok\n\n  @optional_callbacks handle_control: 2, drainer_spec: 1\n\n  require Logger\n\n  @doc false\n  def load_config(true, module),\n    do: module.default_config()\n\n  def load_config(config, module),\n    do: module.default_config() |> Keyword.merge(config) |> load_config()\n\n  @doc false\n  def load_config(config) do\n    {connect_info, config} = Keyword.pop(config, :connect_info, [])\n\n    connect_info =\n      if config[:auth_token] do\n        # auth_token is included by default when enabled\n        [:auth_token | connect_info]\n      else\n        connect_info\n      end\n\n    connect_info =\n      Enum.map(connect_info, fn\n        key when key in [:peer_data, :trace_context_headers, :uri, :user_agent, :x_headers, :sec_websocket_headers, :auth_token] ->\n          key\n\n        {:session, session} ->\n          {:session, init_session(session)}\n\n        {_, _} = pair ->\n          pair\n\n        other ->\n          raise ArgumentError,\n                \":connect_info keys are expected to be one of :peer_data, :trace_context_headers, :x_headers, :user_agent, :sec_websocket_headers, :uri, or {:session, config}, \" <>\n                  \"optionally followed by custom keyword pairs, got: #{inspect(other)}\"\n      end)\n\n    [connect_info: connect_info] ++ config\n  end\n\n  # The original session_config is returned in addition to init value so we can\n  # access special config like :csrf_token_key downstream.\n  defp init_session(session_config) when is_list(session_config) do\n    key = Keyword.fetch!(session_config, :key)\n    store = Plug.Session.Store.get(Keyword.fetch!(session_config, :store))\n    init = store.init(Keyword.drop(session_config, [:store, :key]))\n    csrf_token_key = Keyword.get(session_config, :csrf_token_key, \"_csrf_token\")\n    {key, store, {csrf_token_key, init}}\n  end\n\n  defp init_session({_, _, _} = mfa) do\n    {:mfa, mfa}\n  end\n\n  @doc \"\"\"\n  Runs the code reloader if enabled.\n  \"\"\"\n  def code_reload(conn, endpoint, opts) do\n    if Keyword.get(opts, :code_reloader, endpoint.config(:code_reloader)) do\n      Phoenix.CodeReloader.reload(endpoint)\n    end\n\n    conn\n  end\n\n  @doc \"\"\"\n  Logs the transport request.\n\n  Available for transports that generate a connection.\n  \"\"\"\n  def transport_log(conn, level) do\n    if level do\n      Plug.Logger.call(conn, Plug.Logger.init(log: level))\n    else\n      conn\n    end\n  end\n\n  @doc \"\"\"\n  Checks the origin request header against the list of allowed origins.\n\n  Should be called by transports before connecting when appropriate.\n  If the origin header matches the allowed origins, no origin header was\n  sent or no origin was configured, it will return the given connection.\n\n  Otherwise a 403 Forbidden response will be sent and the connection halted.\n  It is a noop if the connection has been halted.\n  \"\"\"\n  def check_origin(conn, handler, endpoint, opts, sender \\\\ &Plug.Conn.send_resp/1)\n\n  def check_origin(%Plug.Conn{halted: true} = conn, _handler, _endpoint, _opts, _sender),\n    do: conn\n\n  def check_origin(conn, handler, endpoint, opts, sender) do\n    import Plug.Conn\n    origin = conn |> get_req_header(\"origin\") |> List.first()\n    check_origin = check_origin_config(handler, endpoint, opts)\n\n    cond do\n      is_nil(origin) or check_origin == false ->\n        conn\n\n      origin_allowed?(check_origin, URI.parse(origin), endpoint, conn) ->\n        conn\n\n      true ->\n        Logger.error(\"\"\"\n        Could not check origin for Phoenix.Socket transport.\n\n        Origin of the request: #{origin}\n\n        This happens when you are attempting a socket connection to\n        a different host than the one configured in your config/\n        files. For example, in development the host is configured\n        to \"localhost\" but you may be trying to access it from\n        \"127.0.0.1\". To fix this issue, you may either:\n\n          1. update [url: [host: ...]] to your actual host in the\n             config file for your current environment (recommended)\n\n          2. pass the :check_origin option when configuring your\n             endpoint or when configuring the transport in your\n             UserSocket module, explicitly outlining which origins\n             are allowed:\n\n                check_origin: [\"https://example.com\",\n                               \"//another.com:888\", \"//other.com\"]\n\n        \"\"\")\n\n        resp(conn, :forbidden, \"\")\n        |> sender.()\n        |> halt()\n    end\n  end\n\n  @doc \"\"\"\n  Checks the Websocket subprotocols request header against the allowed subprotocols.\n\n  Should be called by transports before connecting when appropriate.\n  If the sec-websocket-protocol header matches the allowed subprotocols,\n  it will put sec-websocket-protocol response header and return the given connection.\n  If no sec-websocket-protocol header was sent it will return the given connection.\n\n  Otherwise a 403 Forbidden response will be sent and the connection halted.\n  It is a noop if the connection has been halted.\n  \"\"\"\n  def check_subprotocols(conn, subprotocols)\n\n  def check_subprotocols(%Plug.Conn{halted: true} = conn, _subprotocols), do: conn\n  def check_subprotocols(conn, nil), do: conn\n\n  def check_subprotocols(conn, subprotocols) when is_list(subprotocols) do\n    case Plug.Conn.get_req_header(conn, \"sec-websocket-protocol\") do\n      [] ->\n        conn\n\n      [subprotocols_header | _] ->\n        request_subprotocols = subprotocols_header |> Plug.Conn.Utils.list()\n\n        subprotocol =\n          Enum.find(subprotocols, fn elem -> Enum.find(request_subprotocols, &(&1 == elem)) end)\n\n        if subprotocol do\n          Plug.Conn.put_resp_header(conn, \"sec-websocket-protocol\", subprotocol)\n        else\n          subprotocols_error_response(conn, subprotocols)\n        end\n    end\n  end\n\n  def check_subprotocols(conn, subprotocols), do: subprotocols_error_response(conn, subprotocols)\n\n  defp subprotocols_error_response(conn, subprotocols) do\n    import Plug.Conn\n    request_headers = get_req_header(conn, \"sec-websocket-protocol\")\n\n    Logger.error(\"\"\"\n    Could not check Websocket subprotocols for Phoenix.Socket transport.\n\n    Subprotocols of the request: #{inspect(request_headers)}\n    Configured supported subprotocols: #{inspect(subprotocols)}\n\n    This happens when you are attempting a socket connection to\n    a different subprotocols than the one configured in your endpoint\n    or when you incorrectly configured supported subprotocols.\n\n    To fix this issue, you may either:\n\n      1. update websocket: [subprotocols: [..]] to your actual subprotocols\n         in your endpoint socket configuration.\n\n      2. check the correctness of the `sec-websocket-protocol` request header\n         sent from the client.\n\n      3. remove `websocket` option from your endpoint socket configuration\n         if you don't use Websocket subprotocols.\n    \"\"\")\n\n    resp(conn, :forbidden, \"\")\n    |> send_resp()\n    |> halt()\n  end\n\n  @doc \"\"\"\n  Extracts connection information from `conn` and returns a map.\n\n  Keys are retrieved from the optional transport option `:connect_info`.\n  This functionality is transport specific. Please refer to your transports'\n  documentation for more information.\n\n  The supported keys are:\n\n    * `:peer_data` - the result of `Plug.Conn.get_peer_data/1`\n\n    * `:trace_context_headers` - a list of all trace context headers\n\n    * `:x_headers` - a list of all request headers that have an \"x-\" prefix\n\n    * `:uri` - a `%URI{}` derived from the conn\n\n    * `:user_agent` - the value of the \"user-agent\" request header\n\n    * `:sec_websocket_headers` - a list of all request headers that have a \"sec-websocket-\" prefix\n\n  The CSRF check can be disabled by setting the `:check_csrf` option to `false`.\n  \"\"\"\n  def connect_info(conn, endpoint, keys, opts \\\\ []) do\n    for key <- keys, into: %{} do\n      case key do\n        :peer_data ->\n          {:peer_data, Plug.Conn.get_peer_data(conn)}\n\n        :trace_context_headers ->\n          {:trace_context_headers, fetch_trace_context_headers(conn)}\n\n        :x_headers ->\n          {:x_headers, fetch_headers(conn, \"x-\")}\n\n        :uri ->\n          {:uri, fetch_uri(conn)}\n\n        :user_agent ->\n          {:user_agent, fetch_user_agent(conn)}\n\n        :sec_websocket_headers ->\n          {:sec_websocket_headers, fetch_headers(conn, \"sec-websocket-\")}\n\n        {:session, session} ->\n          {:session, connect_session(conn, endpoint, session, opts)}\n\n        :auth_token ->\n          {:auth_token, conn.private[:phoenix_transport_auth_token]}\n\n        {key, val} ->\n          {key, val}\n      end\n    end\n  end\n\n  defp connect_session(conn, endpoint, {key, store, {csrf_token_key, init}}, opts) do\n    conn = Plug.Conn.fetch_cookies(conn)\n    check_csrf = Keyword.get(opts, :check_csrf, true)\n\n    with cookie when is_binary(cookie) <- conn.cookies[key],\n         conn = put_in(conn.secret_key_base, endpoint.config(:secret_key_base)),\n         {_, session} <- store.get(conn, cookie, init),\n         true <- not check_csrf or csrf_token_valid?(conn, session, csrf_token_key) do\n      session\n    else\n      _ -> nil\n    end\n  end\n\n  defp connect_session(conn, endpoint, {:mfa, {module, function, args}}, opts) do\n    case apply(module, function, args) do\n      session_config when is_list(session_config) ->\n        connect_session(conn, endpoint, init_session(session_config), opts)\n\n      other ->\n        raise ArgumentError,\n              \"the MFA given to `session_config` must return a keyword list, got: #{inspect(other)}\"\n    end\n  end\n\n  defp fetch_headers(conn, prefix) do\n    for {header, _} = pair <- conn.req_headers,\n        String.starts_with?(header, prefix),\n        do: pair\n  end\n\n  defp fetch_trace_context_headers(conn) do\n    for {header, _} = pair <- conn.req_headers,\n        header in [\"traceparent\", \"tracestate\"],\n        do: pair\n  end\n\n  defp fetch_uri(conn) do\n    %URI{\n      scheme: to_string(conn.scheme),\n      query: conn.query_string,\n      port: conn.port,\n      host: conn.host,\n      authority: conn.host,\n      path: conn.request_path\n    }\n  end\n\n  defp fetch_user_agent(conn) do\n    with {_, value} <- List.keyfind(conn.req_headers, \"user-agent\", 0) do\n      value\n    end\n  end\n\n  defp csrf_token_valid?(conn, session, csrf_token_key) do\n    with csrf_token when is_binary(csrf_token) <- conn.params[\"_csrf_token\"],\n         csrf_state when is_binary(csrf_state) <-\n           Plug.CSRFProtection.dump_state_from_session(session[csrf_token_key]) do\n      Plug.CSRFProtection.valid_state_and_csrf_token?(csrf_state, csrf_token)\n    end\n  end\n\n  defp check_origin_config(handler, endpoint, opts) do\n    Phoenix.Config.cache(endpoint, {:check_origin, handler}, fn _ ->\n      check_origin =\n        case Keyword.get(opts, :check_origin, endpoint.config(:check_origin)) do\n          origins when is_list(origins) ->\n            Enum.map(origins, &parse_origin/1)\n\n          boolean when is_boolean(boolean) ->\n            boolean\n\n          {module, function, arguments} ->\n            {module, function, arguments}\n\n          :conn ->\n            :conn\n\n          invalid ->\n            raise ArgumentError,\n                  \":check_origin expects a boolean, list of hosts, :conn, or MFA tuple, got: #{inspect(invalid)}\"\n        end\n\n      {:cache, check_origin}\n    end)\n  end\n\n  defp parse_origin(origin) do\n    case URI.parse(origin) do\n      %{host: nil} ->\n        raise ArgumentError,\n              \"invalid :check_origin option: #{inspect(origin)}. \" <>\n                \"Expected an origin with a host that is parsable by URI.parse/1. For example: \" <>\n                \"[\\\"https://example.com\\\", \\\"//another.com:888\\\", \\\"//other.com\\\"]\"\n\n      %{scheme: scheme, port: port, host: host} ->\n        {scheme, host, port}\n    end\n  end\n\n  defp origin_allowed?({module, function, arguments}, uri, _endpoint, _conn),\n    do: apply(module, function, [uri | arguments])\n\n  defp origin_allowed?(:conn, uri, _endpoint, %Plug.Conn{} = conn) do\n    uri.host == conn.host and\n      uri.scheme == Atom.to_string(conn.scheme) and\n      uri.port == conn.port\n  end\n\n  defp origin_allowed?(_check_origin, %{host: nil}, _endpoint, _conn),\n    do: false\n\n  defp origin_allowed?(true, uri, endpoint, _conn),\n    do: compare?(uri.host, host_to_binary(endpoint.config(:url)[:host]))\n\n  defp origin_allowed?(check_origin, uri, _endpoint, _conn) when is_list(check_origin),\n    do: origin_allowed?(uri, check_origin)\n\n  defp origin_allowed?(uri, allowed_origins) do\n    %{scheme: origin_scheme, host: origin_host, port: origin_port} = uri\n\n    Enum.any?(allowed_origins, fn {allowed_scheme, allowed_host, allowed_port} ->\n      compare?(origin_scheme, allowed_scheme) and\n        compare?(origin_port, allowed_port) and\n        compare_host?(origin_host, allowed_host)\n    end)\n  end\n\n  defp compare?(request_val, allowed_val) do\n    is_nil(allowed_val) or request_val == allowed_val\n  end\n\n  defp compare_host?(_request_host, nil),\n    do: true\n\n  defp compare_host?(request_host, \"*.\" <> allowed_host),\n    do: request_host == allowed_host or String.ends_with?(request_host, \".\" <> allowed_host)\n\n  defp compare_host?(request_host, allowed_host),\n    do: request_host == allowed_host\n\n  # TODO: Remove this once {:system, env_var} deprecation is removed\n  defp host_to_binary({:system, env_var}), do: host_to_binary(System.get_env(env_var))\n  defp host_to_binary(host), do: host\nend\n"
  },
  {
    "path": "lib/phoenix/socket.ex",
    "content": "defmodule Phoenix.Socket do\n  @moduledoc ~S\"\"\"\n  A socket implementation that multiplexes messages over channels.\n\n  `Phoenix.Socket` is used as a module for establishing a connection\n  between client and server. Once the connection is established,\n  the initial state is stored in the `Phoenix.Socket` struct.\n\n  The same socket can be used to receive events from different transports.\n  Phoenix supports `websocket` and `longpoll` options when invoking\n  `Phoenix.Endpoint.socket/3` in your endpoint. `websocket` is set by default\n  and `longpoll` can also be configured explicitly.\n\n      socket \"/socket\", MyAppWeb.Socket, websocket: true, longpoll: false\n\n  The command above means incoming socket connections can be made via\n  a WebSocket connection. Incoming and outgoing events are routed to\n  channels by topic:\n\n      channel \"room:lobby\", MyAppWeb.LobbyChannel\n\n  See `Phoenix.Channel` for more information on channels.\n\n  ## Socket Behaviour\n\n  Socket handlers are mounted in Endpoints and must define two callbacks:\n\n    * `connect/3` - receives the socket params, connection info if any, and\n      authenticates the connection. Must return a `Phoenix.Socket` struct,\n      often with custom assigns\n\n    * `id/1` - receives the socket returned by `connect/3` and returns the\n      id of this connection as a string. The `id` is used to identify socket\n      connections, often to a particular user, allowing us to force disconnections.\n      For sockets requiring no authentication, `nil` can be returned\n\n  ## Examples\n\n      defmodule MyAppWeb.UserSocket do\n        use Phoenix.Socket\n\n        channel \"room:*\", MyAppWeb.RoomChannel\n\n        def connect(params, socket, _connect_info) do\n          {:ok, assign(socket, :user_id, params[\"user_id\"])}\n        end\n\n        def id(socket), do: \"users_socket:#{socket.assigns.user_id}\"\n      end\n\n      # Disconnect all user's socket connections and their multiplexed channels\n      MyAppWeb.Endpoint.broadcast(\"users_socket:\" <> user.id, \"disconnect\", %{})\n\n  ## Socket fields\n\n    * `:id` - The string id of the socket\n    * `:assigns` - The map of socket assigns, default: `%{}`\n    * `:channel` - The current channel module\n    * `:channel_pid` - The channel pid\n    * `:endpoint` - The endpoint module where this socket originated, for example: `MyAppWeb.Endpoint`\n    * `:handler` - The socket module where this socket originated, for example: `MyAppWeb.UserSocket`\n    * `:joined` - If the socket has effectively joined the channel\n    * `:join_ref` - The ref sent by the client when joining\n    * `:ref` - The latest ref sent by the client\n    * `:pubsub_server` - The registered name of the socket's pubsub server\n    * `:topic` - The string topic, for example `\"room:123\"`\n    * `:transport` - An identifier for the transport, used for logging\n    * `:transport_pid` - The pid of the socket's transport process\n    * `:serializer` - The serializer for socket messages\n\n  ## Using options\n\n  On `use Phoenix.Socket`, the following options are accepted:\n\n    * `:log` - the default level to log socket actions. Defaults\n      to `:info`. May be set to `false` to disable it\n\n    * `:partitions` - each channel is spawned under a supervisor.\n      This option controls how many supervisors will be spawned\n      to handle channels. Defaults to the number of cores.\n\n  ## Garbage collection\n\n  It's possible to force garbage collection in the transport process after\n  processing large messages. For example, to trigger such from your channels,\n  run:\n\n      send(socket.transport_pid, :garbage_collect)\n\n  Alternatively, you can configure your endpoint socket to trigger more\n  fullsweep garbage collections more frequently, by setting the `:fullsweep_after`\n  option for websockets. See `Phoenix.Endpoint.socket/3` for more info.\n\n  ## Client-server communication\n\n  The encoding of server data and the decoding of client data is done\n  according to a serializer, defined in `Phoenix.Socket.Serializer`.\n  By default, JSON encoding is used to broker messages to and from clients.\n\n  The serializer `decode!` function must return a `Phoenix.Socket.Message`\n  which is forwarded to channels except:\n\n    * `\"heartbeat\"` events in the \"phoenix\" topic - should just emit an OK reply\n    * `\"phx_join\"` on any topic - should join the topic\n    * `\"phx_leave\"` on any topic - should leave the topic\n\n  Each message also has a `ref` field which is used to track responses.\n\n  The server may send messages or replies back. For messages, the\n  ref uniquely identifies the message. For replies, the ref matches\n  the original message. Both data-types also include a join_ref that\n  uniquely identifies the currently joined channel.\n\n  The `Phoenix.Socket` implementation may also send special messages\n  and replies:\n\n    * `\"phx_error\"` - in case of errors, such as a channel process\n      crashing, or when attempting to join an already joined channel\n\n    * `\"phx_close\"` - the channel was gracefully closed\n\n  Phoenix ships with a JavaScript implementation of both websocket\n  and long polling that interacts with Phoenix.Socket and can be\n  used as reference for those interested in implementing custom clients.\n\n  ## Custom sockets and transports\n\n  See the `Phoenix.Socket.Transport` documentation for more information on\n  writing your own socket that does not leverage channels or for writing\n  your own transports that interacts with other sockets.\n\n  ## Custom channels\n\n  You can list any module as a channel as long as it implements\n  a `child_spec/1` function. The `child_spec/1` function receives\n  the caller as argument and it must return a child spec that\n  initializes a process.\n\n  Once the process is initialized, it will receive the following\n  message:\n\n      {Phoenix.Channel, auth_payload, from, socket}\n\n  A custom channel implementation MUST invoke\n  `GenServer.reply(from, {:ok | :error, reply_payload})` during its\n  initialization with a custom `reply_payload` that will be sent as\n  a reply to the client. Failing to do so will block the socket forever.\n\n  A custom channel receives `Phoenix.Socket.Message` structs as regular\n  messages from the transport. Replies to those messages and custom\n  messages can be sent to the socket at any moment by building an\n  appropriate `Phoenix.Socket.Reply` and `Phoenix.Socket.Message`\n  structs, encoding them with the serializer and dispatching the\n  serialized result to the transport.\n\n  For example, to handle \"phx_leave\" messages, which is recommended\n  to be handled by all channel implementations, one may do:\n\n      def handle_info(\n            %Message{topic: topic, event: \"phx_leave\"} = message,\n            %{topic: topic, serializer: serializer, transport_pid: transport_pid} = socket\n          ) do\n        send transport_pid, serializer.encode!(build_leave_reply(message))\n        {:stop, {:shutdown, :left}, socket}\n      end\n\n  A special message delivered to all channels is a Broadcast with\n  event \"phx_drain\", which is sent when draining the socket during\n  application shutdown. Typically it is handled by sending a drain\n  message to the transport, causing it to shutdown:\n\n      def handle_info(\n            %Broadcast{event: \"phx_drain\"},\n            %{transport_pid: transport_pid} = socket\n          ) do\n        send(transport_pid, :socket_drain)\n        {:stop, {:shutdown, :draining}, socket}\n      end\n\n  We also recommend all channels to monitor the `transport_pid`\n  on `init` and exit if the transport exits. We also advise to rewrite\n  `:normal` exit reasons (usually due to the socket being closed)\n  to the `{:shutdown, :closed}` to guarantee links are broken on\n  the channel exit (as a `:normal` exit does not break links):\n\n      def handle_info({:DOWN, _, _, transport_pid, reason}, %{transport_pid: transport_pid} = socket) do\n        reason = if reason == :normal, do: {:shutdown, :closed}, else: reason\n        {:stop, reason, socket}\n      end\n\n  Any process exit is treated as an error by the socket layer unless\n  a `{:socket_close, pid, reason}` message is sent to the socket before\n  shutdown.\n\n  Custom channel implementations cannot be tested with `Phoenix.ChannelTest`.\n  \"\"\"\n\n  require Logger\n  alias Phoenix.Socket\n  alias Phoenix.Socket.{Broadcast, Message, Reply}\n\n  @doc \"\"\"\n  Receives the socket params and authenticates the connection.\n\n  ## Socket params and assigns\n\n  Socket params are passed from the client and can\n  be used to verify and authenticate a user. After\n  verification, you can put default assigns into\n  the socket that will be set for all channels, ie\n\n      {:ok, assign(socket, :user_id, verified_user_id)}\n\n  To deny connection, return `:error` or `{:error, term}`. To control the\n  response the client receives in that case, [define an error handler in the\n  websocket\n  configuration](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration).\n\n  See `Phoenix.Token` documentation for examples in\n  performing token verification on connect.\n  \"\"\"\n  @callback connect(params :: map, Socket.t(), connect_info :: map) ::\n              {:ok, Socket.t()} | {:error, term} | :error\n\n  @doc \"\"\"\n  Shortcut version of `connect/3` which does not receive `connect_info`.\n\n  Provided for backwards compatibility.\n  \"\"\"\n  @callback connect(params :: map, Socket.t()) :: {:ok, Socket.t()} | {:error, term} | :error\n\n  @doc ~S\"\"\"\n  Identifies the socket connection.\n\n  Socket IDs are topics that allow you to identify all sockets for a given user:\n\n      def id(socket), do: \"users_socket:#{socket.assigns.user_id}\"\n\n  Would allow you to broadcast a `\"disconnect\"` event and terminate\n  all active sockets and channels for a given user:\n\n      MyAppWeb.Endpoint.broadcast(\"users_socket:\" <> user.id, \"disconnect\", %{})\n\n  Returning `nil` makes this socket anonymous.\n  \"\"\"\n  @callback id(Socket.t()) :: String.t() | nil\n\n  @optional_callbacks connect: 2, connect: 3\n\n  defmodule InvalidMessageError do\n    @moduledoc \"\"\"\n    Raised when the socket message is invalid.\n    \"\"\"\n    defexception [:message]\n  end\n\n  defstruct assigns: %{},\n            channel: nil,\n            channel_pid: nil,\n            endpoint: nil,\n            handler: nil,\n            id: nil,\n            joined: false,\n            join_ref: nil,\n            private: %{},\n            pubsub_server: nil,\n            ref: nil,\n            serializer: nil,\n            topic: nil,\n            transport: nil,\n            transport_pid: nil\n\n  @type t :: %Socket{\n          assigns: map,\n          channel: atom,\n          channel_pid: pid,\n          endpoint: atom,\n          handler: atom,\n          id: String.t() | nil,\n          joined: boolean,\n          ref: term,\n          private: map,\n          pubsub_server: atom,\n          serializer: atom,\n          topic: String.t(),\n          transport: atom,\n          transport_pid: pid\n        }\n\n  defmacro __using__(opts) do\n    quote do\n      ## User API\n\n      import Phoenix.Socket\n      @behaviour Phoenix.Socket\n      @before_compile Phoenix.Socket\n      Module.register_attribute(__MODULE__, :phoenix_channels, accumulate: true)\n      @phoenix_socket_options unquote(opts)\n\n      ## Callbacks\n\n      @behaviour Phoenix.Socket.Transport\n\n      @doc false\n      def child_spec(opts) do\n        Phoenix.Socket.__child_spec__(__MODULE__, opts, @phoenix_socket_options)\n      end\n\n      @doc false\n      def drainer_spec(opts) do\n        Phoenix.Socket.__drainer_spec__(__MODULE__, opts, @phoenix_socket_options)\n      end\n\n      @doc false\n      def connect(map), do: Phoenix.Socket.__connect__(__MODULE__, map, @phoenix_socket_options)\n\n      @doc false\n      def init(state), do: Phoenix.Socket.__init__(state)\n\n      @doc false\n      def handle_in(message, state), do: Phoenix.Socket.__in__(message, state)\n\n      @doc false\n      def handle_info(message, state), do: Phoenix.Socket.__info__(message, state)\n\n      @doc false\n      def terminate(reason, state), do: Phoenix.Socket.__terminate__(reason, state)\n    end\n  end\n\n  ## USER API\n\n  @doc \"\"\"\n  Adds a `key`/`value` pair to `socket` assigns.\n\n  See also `assign/2` to add multiple key/value pairs.\n\n  ## Examples\n\n      iex> assign(socket, :name, \"Elixir\")\n  \"\"\"\n  def assign(%Socket{} = socket, key, value) do\n    assign(socket, [{key, value}])\n  end\n\n  @doc \"\"\"\n  Adds key/value pairs to socket assigns.\n  Accepts a keyword list, a map, or a single-argument function.\n\n  When a keyword list or map is provided, it will be merged into the existing assigns.\n\n  If a function is given, it takes the current assigns as an argument and its return\n  value will be merged into the current assigns.\n\n  ## Examples\n\n      iex> assign(socket, name: \"Elixir\", logo: \"💧\")\n      iex> assign(socket, %{name: \"Elixir\"})\n      iex> assign(socket, fn %{name: name, logo: logo} -> %{title: Enum.join([name, logo], \" | \")} end)\n\n  \"\"\"\n  def assign(%Socket{} = socket, keyword_or_map)\n      when is_map(keyword_or_map) or is_list(keyword_or_map) do\n    %{socket | assigns: Map.merge(socket.assigns, Map.new(keyword_or_map))}\n  end\n\n  def assign(%Socket{} = socket, fun) when is_function(fun, 1) do\n    assign(socket, fun.(socket.assigns))\n  end\n\n  @doc \"\"\"\n  Defines a channel matching the given topic and transports.\n\n    * `topic_pattern` - The string pattern, for example `\"room:*\"`, `\"users:*\"`,\n      or `\"system\"`\n    * `module` - The channel module handler, for example `MyAppWeb.RoomChannel`\n    * `opts` - The optional list of options, see below\n\n  ## Options\n\n    * `:assigns` - the map of socket assigns to merge into the socket on join\n\n  ## Examples\n\n      channel \"topic1:*\", MyChannel\n\n  ## Topic Patterns\n\n  The `channel` macro accepts topic patterns in two flavors. A splat (the `*`\n  character) argument can be provided as the last character to indicate a\n  `\"topic:subtopic\"` match. If a plain string is provided, only that topic will\n  match the channel handler. Most use-cases will use the `\"topic:*\"` pattern to\n  allow more versatile topic scoping.\n\n  See `Phoenix.Channel` for more information\n  \"\"\"\n  defmacro channel(topic_pattern, module, opts \\\\ []) do\n    module = expand_alias(module, __CALLER__)\n\n    opts =\n      if Macro.quoted_literal?(opts) do\n        Macro.prewalk(opts, &expand_alias(&1, __CALLER__))\n      else\n        opts\n      end\n\n    quote do\n      @phoenix_channels {unquote(topic_pattern), unquote(module), unquote(opts)}\n    end\n  end\n\n  defp expand_alias({:__aliases__, _, _} = alias, env),\n    do: Macro.expand(alias, %{env | function: {:channel, 3}})\n\n  defp expand_alias(other, _env), do: other\n\n  @doc false\n  @deprecated \"transport/3 in Phoenix.Socket is deprecated and has no effect\"\n  defmacro transport(_name, _module, _config \\\\ []) do\n    :ok\n  end\n\n  defmacro __before_compile__(env) do\n    channels =\n      env.module\n      |> Module.get_attribute(:phoenix_channels, [])\n      |> Enum.reverse()\n\n    channel_defs =\n      for {topic_pattern, module, opts} <- channels do\n        topic_pattern\n        |> to_topic_match()\n        |> defchannel(module, opts)\n      end\n\n    quote do\n      unquote(channel_defs)\n      def __channel__(_topic), do: nil\n    end\n  end\n\n  defp to_topic_match(topic_pattern) do\n    case String.split(topic_pattern, \"*\") do\n      [prefix, \"\"] -> quote do: <<unquote(prefix) <> _rest>>\n      [bare_topic] -> bare_topic\n      _ -> raise ArgumentError, \"channels using splat patterns must end with *\"\n    end\n  end\n\n  defp defchannel(topic_match, channel_module, opts) do\n    quote do\n      def __channel__(unquote(topic_match)), do: unquote({channel_module, Macro.escape(opts)})\n    end\n  end\n\n  ## CALLBACKS IMPLEMENTATION\n\n  def __child_spec__(handler, opts, socket_options) do\n    endpoint = Keyword.fetch!(opts, :endpoint)\n    opts = Keyword.merge(socket_options, opts)\n    partitions = Keyword.get(opts, :partitions, System.schedulers_online())\n    args = {endpoint, handler, partitions}\n    Supervisor.child_spec({Phoenix.Socket.PoolSupervisor, args}, id: handler)\n  end\n\n  def __drainer_spec__(handler, opts, socket_options) do\n    endpoint = Keyword.fetch!(opts, :endpoint)\n    opts = Keyword.merge(socket_options, opts)\n\n    if drainer = Keyword.get(opts, :drainer, []) do\n      drainer =\n        case drainer do\n          {module, function, arguments} ->\n            apply(module, function, arguments)\n\n          _ ->\n            drainer\n        end\n\n      opts = Keyword.merge(opts, drainer: drainer)\n\n      {Phoenix.Socket.PoolDrainer, {endpoint, handler, opts}}\n    else\n      :ignore\n    end\n  end\n\n  def __connect__(user_socket, map, socket_options) do\n    %{\n      endpoint: endpoint,\n      options: options,\n      transport: transport,\n      params: params,\n      connect_info: connect_info\n    } = map\n\n    vsn = params[\"vsn\"] || \"1.0.0\"\n\n    options = Keyword.merge(socket_options, options)\n    start = System.monotonic_time()\n\n    case negotiate_serializer(Keyword.fetch!(options, :serializer), vsn) do\n      {:ok, serializer} ->\n        result = user_connect(user_socket, endpoint, transport, serializer, params, connect_info)\n\n        metadata = %{\n          endpoint: endpoint,\n          transport: transport,\n          params: params,\n          connect_info: connect_info,\n          vsn: vsn,\n          user_socket: user_socket,\n          log: Keyword.get(options, :log, :info),\n          result: result(result),\n          serializer: serializer\n        }\n\n        duration = System.monotonic_time() - start\n        :telemetry.execute([:phoenix, :socket_connected], %{duration: duration}, metadata)\n        result\n\n      :error ->\n        :error\n    end\n  end\n\n  defp result({:ok, _}), do: :ok\n  defp result(:error), do: :error\n  defp result({:error, _}), do: :error\n\n  defp set_label(socket) do\n    # TODO: replace with Process.put_label/2 when we require Elixir 1.17\n    Process.put(:\"$process_label\", {Phoenix.Socket, socket.handler, socket.id})\n  end\n\n  def __init__({state, %{id: id, endpoint: endpoint} = socket}) do\n    set_label(socket)\n    _ = id && endpoint.subscribe(id)\n    {:ok, {state, %{socket | transport_pid: self()}}}\n  end\n\n  def __in__({payload, opts}, {state, socket}) do\n    %{topic: topic} = message = socket.serializer.decode!(payload, opts)\n    handle_in(Map.get(state.channels, topic), message, state, socket)\n  end\n\n  def __info__({:DOWN, ref, _, pid, reason}, {state, socket}) do\n    case state.channels_inverse do\n      %{^pid => {topic, join_ref}} ->\n        state = delete_channel(state, pid, topic, ref)\n        {:push, encode_on_exit(socket, topic, join_ref, reason), {state, socket}}\n\n      %{} ->\n        {:ok, {state, socket}}\n    end\n  end\n\n  def __info__(%Broadcast{event: \"disconnect\"}, state) do\n    {:stop, {:shutdown, :disconnected}, state}\n  end\n\n  def __info__(:socket_drain, state) do\n    # downstream websock_adapter's will close with 1012 Service Restart\n    {:stop, {:shutdown, :restart}, state}\n  end\n\n  def __info__({:socket_push, opcode, payload}, state) do\n    {:push, {opcode, payload}, state}\n  end\n\n  def __info__({:socket_close, pid, _reason}, state) do\n    socket_close(pid, state)\n  end\n\n  def __info__(:garbage_collect, state) do\n    :erlang.garbage_collect(self())\n    {:ok, state}\n  end\n\n  def __info__({:debug_channels, ref, reply_to}, {state, socket}) do\n    channels =\n      Enum.map(state.channels, fn {topic, {pid, _monitor_ref, status}} ->\n        %{topic: topic, pid: pid, status: status}\n      end)\n\n    send(reply_to, {:debug_channels, ref, channels})\n    {:ok, {state, socket}}\n  end\n\n  def __info__(_, state) do\n    {:ok, state}\n  end\n\n  def __terminate__(_reason, _state_socket) do\n    :ok\n  end\n\n  defp negotiate_serializer(serializers, vsn) when is_list(serializers) do\n    case Version.parse(vsn) do\n      {:ok, vsn} ->\n        serializers\n        |> Enum.find(:error, fn {_serializer, vsn_req} -> Version.match?(vsn, vsn_req) end)\n        |> case do\n          {serializer, _vsn_req} ->\n            {:ok, serializer}\n\n          :error ->\n            Logger.warning(\n              \"The client's requested transport version \\\"#{vsn}\\\" \" <>\n                \"does not match server's version requirements of #{inspect(serializers)}\"\n            )\n\n            :error\n        end\n\n      :error ->\n        Logger.warning(\"Client sent invalid transport version \\\"#{vsn}\\\"\")\n        :error\n    end\n  end\n\n  defp user_connect(handler, endpoint, transport, serializer, params, connect_info) do\n    # The information in the Phoenix.Socket goes to userland and channels.\n    socket = %Socket{\n      handler: handler,\n      endpoint: endpoint,\n      pubsub_server: endpoint.config(:pubsub_server),\n      serializer: serializer,\n      transport: transport\n    }\n\n    # The information in the state is kept only inside the socket process.\n    state = %{\n      channels: %{},\n      channels_inverse: %{}\n    }\n\n    connect_result =\n      if function_exported?(handler, :connect, 3) do\n        handler.connect(params, socket, connect_info)\n      else\n        handler.connect(params, socket)\n      end\n\n    case connect_result do\n      {:ok, %Socket{} = socket} ->\n        case handler.id(socket) do\n          nil ->\n            {:ok, {state, socket}}\n\n          id when is_binary(id) ->\n            # update the process label\n            set_label(socket)\n            {:ok, {state, %{socket | id: id}}}\n\n          invalid ->\n            Logger.warning(\n              \"#{inspect(handler)}.id/1 returned invalid identifier \" <>\n                \"#{inspect(invalid)}. Expected nil or a string.\"\n            )\n\n            :error\n        end\n\n      :error ->\n        :error\n\n      {:error, _reason} = err ->\n        err\n\n      invalid ->\n        connect_arity =\n          if function_exported?(handler, :connect, 3), do: \"connect/3\", else: \"connect/2\"\n\n        Logger.error(\n          \"#{inspect(handler)}. #{connect_arity} returned invalid value #{inspect(invalid)}. \" <>\n            \"Expected {:ok, socket}, {:error, reason} or :error\"\n        )\n\n        :error\n    end\n  end\n\n  defp handle_in(_, %{ref: ref, topic: \"phoenix\", event: \"heartbeat\"}, state, socket) do\n    reply = %Reply{\n      ref: ref,\n      topic: \"phoenix\",\n      status: :ok,\n      payload: %{}\n    }\n\n    {:reply, :ok, encode_reply(socket, reply), {state, socket}}\n  end\n\n  defp handle_in(\n         nil,\n         %{event: \"phx_join\", topic: topic, ref: ref, join_ref: join_ref} = message,\n         state,\n         socket\n       ) do\n    case socket.handler.__channel__(topic) do\n      {channel, opts} ->\n        case Phoenix.Channel.Server.join(socket, channel, message, opts) do\n          {:ok, reply, pid} ->\n            reply = %Reply{\n              join_ref: join_ref,\n              ref: ref,\n              topic: topic,\n              status: :ok,\n              payload: reply\n            }\n\n            state = put_channel(state, pid, topic, join_ref)\n            {:reply, :ok, encode_reply(socket, reply), {state, socket}}\n\n          {:error, reply} ->\n            reply = %Reply{\n              join_ref: join_ref,\n              ref: ref,\n              topic: topic,\n              status: :error,\n              payload: reply\n            }\n\n            {:reply, :error, encode_reply(socket, reply), {state, socket}}\n        end\n\n      _ ->\n        Logger.warning(\"Ignoring unmatched topic \\\"#{topic}\\\" in #{inspect(socket.handler)}\")\n        {:reply, :error, encode_ignore(socket, message), {state, socket}}\n    end\n  end\n\n  defp handle_in({pid, _ref, status}, %{event: \"phx_join\", topic: topic} = message, state, socket) do\n    receive do\n      {:socket_close, ^pid, _reason} -> :ok\n    after\n      0 ->\n        if status != :leaving do\n          Logger.debug(fn ->\n            \"Duplicate channel join for topic \\\"#{topic}\\\" in #{inspect(socket.handler)}. \" <>\n              \"Closing existing channel for new join.\"\n          end)\n        end\n    end\n\n    :ok = shutdown_duplicate_channel(pid)\n    {:push, {opcode, payload}, {new_state, new_socket}} = socket_close(pid, {state, socket})\n    send(self(), {:socket_push, opcode, payload})\n    handle_in(nil, message, new_state, new_socket)\n  end\n\n  defp handle_in({pid, _ref, _status}, %{event: \"phx_leave\"} = msg, state, socket) do\n    %{topic: topic, join_ref: join_ref} = msg\n\n    case state.channels_inverse do\n      # we need to match on nil to handle v1 protocol\n      %{^pid => {^topic, existing_join_ref}} when existing_join_ref in [join_ref, nil] ->\n        send(pid, msg)\n        {:ok, {update_channel_status(state, pid, topic, :leaving), socket}}\n\n      # the client has raced a server close. No need to reply since we already sent close\n      %{^pid => {^topic, _old_join_ref}} ->\n        {:ok, {state, socket}}\n    end\n  end\n\n  defp handle_in({pid, _ref, _status}, msg, state, socket) do\n    %{topic: topic, join_ref: join_ref} = msg\n\n    case state.channels_inverse do\n      # we need to match on nil to handle v1 protocol\n      %{^pid => {^topic, existing_join_ref}} when existing_join_ref in [join_ref, nil] ->\n        send(pid, msg)\n        {:ok, {state, socket}}\n\n      # the client has sent a stale message to a previous join_ref, ignore\n      %{^pid => {^topic, _old_join_ref}} ->\n        {:ok, {state, socket}}\n    end\n  end\n\n  defp handle_in(\n         nil,\n         %{event: \"phx_leave\", ref: ref, topic: topic, join_ref: join_ref},\n         state,\n         socket\n       ) do\n    reply = %Reply{\n      ref: ref,\n      join_ref: join_ref,\n      topic: topic,\n      status: :ok,\n      payload: %{}\n    }\n\n    {:reply, :ok, encode_reply(socket, reply), {state, socket}}\n  end\n\n  defp handle_in(nil, message, state, socket) do\n    # This clause can happen if the server drops the channel\n    # and the client sends a message meanwhile\n    {:reply, :error, encode_ignore(socket, message), {state, socket}}\n  end\n\n  defp put_channel(state, pid, topic, join_ref) do\n    %{channels: channels, channels_inverse: channels_inverse} = state\n    monitor_ref = Process.monitor(pid)\n\n    %{\n      state\n      | channels: Map.put(channels, topic, {pid, monitor_ref, :joined}),\n        channels_inverse: Map.put(channels_inverse, pid, {topic, join_ref})\n    }\n  end\n\n  defp delete_channel(state, pid, topic, monitor_ref) do\n    %{channels: channels, channels_inverse: channels_inverse} = state\n    Process.demonitor(monitor_ref, [:flush])\n\n    %{\n      state\n      | channels: Map.delete(channels, topic),\n        channels_inverse: Map.delete(channels_inverse, pid)\n    }\n  end\n\n  defp encode_on_exit(socket, topic, ref, _reason) do\n    message = %Message{join_ref: ref, ref: ref, topic: topic, event: \"phx_error\", payload: %{}}\n    encode_reply(socket, message)\n  end\n\n  defp encode_ignore(socket, %{ref: ref, topic: topic}) do\n    reply = %Reply{ref: ref, topic: topic, status: :error, payload: %{reason: \"unmatched topic\"}}\n    encode_reply(socket, reply)\n  end\n\n  defp encode_reply(%{serializer: serializer}, message) do\n    {:socket_push, opcode, payload} = serializer.encode!(message)\n    {opcode, payload}\n  end\n\n  defp encode_close(socket, topic, join_ref) do\n    message = %Message{\n      join_ref: join_ref,\n      ref: join_ref,\n      topic: topic,\n      event: \"phx_close\",\n      payload: %{}\n    }\n\n    encode_reply(socket, message)\n  end\n\n  defp shutdown_duplicate_channel(pid) do\n    ref = Process.monitor(pid)\n    Process.exit(pid, {:shutdown, :duplicate_join})\n\n    receive do\n      {:DOWN, ^ref, _, _, _} -> :ok\n    after\n      5_000 ->\n        Process.exit(pid, :kill)\n        receive do: ({:DOWN, ^ref, _, _, _} -> :ok)\n    end\n  end\n\n  defp socket_close(pid, {state, socket}) do\n    case state.channels_inverse do\n      %{^pid => {topic, join_ref}} ->\n        {^pid, monitor_ref, _status} = Map.fetch!(state.channels, topic)\n        state = delete_channel(state, pid, topic, monitor_ref)\n        {:push, encode_close(socket, topic, join_ref), {state, socket}}\n\n      %{} ->\n        {:ok, {state, socket}}\n    end\n  end\n\n  defp update_channel_status(state, pid, topic, status) do\n    new_channels = Map.update!(state.channels, topic, fn {^pid, ref, _} -> {pid, ref, status} end)\n    %{state | channels: new_channels}\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/test/channel_test.ex",
    "content": "defmodule Phoenix.ChannelTest do\n  @moduledoc \"\"\"\n  Conveniences for testing Phoenix channels.\n\n  In channel tests, we interact with channels via process\n  communication, sending and receiving messages. It is also\n  common to subscribe to the same topic the channel subscribes\n  to, allowing us to assert if a given message was broadcast\n  or not.\n\n  ## Channel testing\n\n  To get started, define the module attribute `@endpoint`\n  in your test case pointing to your application endpoint.\n\n  Then you can directly create a socket and\n  `subscribe_and_join/4` topics and channels:\n\n      {:ok, _, socket} =\n        socket(UserSocket, \"user:id\", %{some_assigns: 1})\n        |> subscribe_and_join(RoomChannel, \"room:lobby\", %{\"id\" => 3})\n\n  You usually want to set the same ID and assigns your\n  `UserSocket.connect/3` callback would set. Alternatively,\n  you can use the `connect/3` helper to call your `UserSocket.connect/3`\n  callback and initialize the socket with the socket id:\n\n      {:ok, socket} = connect(UserSocket, %{\"some\" => \"params\"}, %{})\n      {:ok, _, socket} = subscribe_and_join(socket, \"room:lobby\", %{\"id\" => 3})\n\n  Once called, `subscribe_and_join/4` will subscribe the\n  current test process to the \"room:lobby\" topic and start a\n  channel in another process. It returns `{:ok, reply, socket}`\n  or `{:error, reply}`.\n\n  Now, in the same way the channel has a socket representing\n  communication it will push to the client. Our test has a\n  socket representing communication to be pushed to the server.\n\n  For example, we can use the `push/3` function in the test\n  to push messages to the channel (it will invoke `handle_in/3`):\n\n      push(socket, \"my_event\", %{\"some\" => \"data\"})\n\n  Similarly, we can broadcast messages from the test itself\n  on the topic that both test and channel are subscribed to,\n  triggering `handle_out/3` on the channel:\n\n      broadcast_from(socket, \"my_event\", %{\"some\" => \"data\"})\n\n  > Note only `broadcast_from/3` and `broadcast_from!/3` are\n  available in tests to avoid broadcast messages to be resent\n  to the test process.\n\n  While the functions above are pushing data to the channel\n  (server) we can use `assert_push/3` to verify the channel\n  pushed a message to the client:\n\n      assert_push \"my_event\", %{\"some\" => \"data\"}\n\n  Or even assert something was broadcast into pubsub:\n\n      assert_broadcast \"my_event\", %{\"some\" => \"data\"}\n\n  Finally, every time a message is pushed to the channel,\n  a reference is returned. We can use this reference to\n  assert a particular reply was sent from the server:\n\n      ref = push(socket, \"counter\", %{})\n      assert_reply ref, :ok, %{\"counter\" => 1}\n\n  ## Checking side-effects\n\n  Often one may want to do side-effects inside channels,\n  like writing to the database, and verify those side-effects\n  during their tests.\n\n  Imagine the following `handle_in/3` inside a channel:\n\n      def handle_in(\"publish\", %{\"id\" => id}, socket) do\n        Repo.get!(Post, id) |> Post.publish() |> Repo.update!()\n        {:noreply, socket}\n      end\n\n  Because the whole communication is asynchronous, the\n  following test would be very brittle:\n\n      push(socket, \"publish\", %{\"id\" => 3})\n      assert Repo.get_by(Post, id: 3, published: true)\n\n  The issue is that we have no guarantees the channel has\n  done processing our message after calling `push/3`. The\n  best solution is to assert the channel sent us a reply\n  before doing any other assertion. First change the\n  channel to send replies:\n\n      def handle_in(\"publish\", %{\"id\" => id}, socket) do\n        Repo.get!(Post, id) |> Post.publish() |> Repo.update!()\n        {:reply, :ok, socket}\n      end\n\n  Then expect them in the test:\n\n      ref = push(socket, \"publish\", %{\"id\" => 3})\n      assert_reply ref, :ok\n      assert Repo.get_by(Post, id: 3, published: true)\n\n  ## Leave and close\n\n  This module also provides functions to simulate leaving\n  and closing a channel. Once you leave or close a channel,\n  because the channel is linked to the test process on join,\n  it will crash the test process:\n\n      leave(socket)\n      ** (EXIT from #PID<...>) {:shutdown, :leave}\n\n  You can avoid this by unlinking the channel process in\n  the test:\n\n      Process.unlink(socket.channel_pid)\n\n  Notice `leave/1` is async, so it will also return a\n  reference which you can use to check for a reply:\n\n      ref = leave(socket)\n      assert_reply ref, :ok\n\n  On the other hand, close is always sync and it will\n  return only after the channel process is guaranteed to\n  have been terminated:\n\n      :ok = close(socket)\n\n  This mimics the behaviour existing in clients.\n\n  To assert that your channel closes or errors asynchronously,\n  you can monitor the channel process with the tools provided\n  by Elixir, and wait for the `:DOWN` message.\n  Imagine an implementation of the `handle_info/2` function\n  that closes the channel when it receives `:some_message`:\n\n      def handle_info(:some_message, socket) do\n        {:stop, :normal, socket}\n      end\n\n  In your test, you can assert that the close happened by:\n\n      Process.monitor(socket.channel_pid)\n      send(socket.channel_pid, :some_message)\n      assert_receive {:DOWN, _, _, _, :normal}\n\n  \"\"\"\n\n  alias Phoenix.Socket\n  alias Phoenix.Socket.{Broadcast, Message, Reply}\n  alias Phoenix.Channel.Server\n\n  defmodule NoopSerializer do\n    @behaviour Phoenix.Socket.Serializer\n    @moduledoc false\n\n    def fastlane!(%Broadcast{} = msg) do\n      %Message{\n        topic: msg.topic,\n        event: msg.event,\n        payload: msg.payload\n      }\n    end\n\n    def encode!(%Reply{} = reply), do: reply\n    def encode!(%Message{} = msg), do: msg\n    def decode!(message, _opts), do: message\n  end\n\n  @doc false\n  defmacro __using__(_) do\n    IO.warn \"\"\"\n    Using Phoenix.ChannelTest is deprecated, instead of:\n\n        use Phoenix.ChannelTest\n\n    do:\n\n        import Phoenix.ChannelTest\n    \"\"\", Macro.Env.stacktrace(__CALLER__)\n\n    quote do\n      import Phoenix.ChannelTest\n    end\n  end\n\n  @doc \"\"\"\n  Builds a socket for the given `socket_module`.\n\n  The socket is then used to subscribe and join channels.\n  Use this function when you want to create a blank socket\n  to pass to functions like `UserSocket.connect/3`.\n\n  Otherwise, use `socket/4` if you want to build a socket with\n  existing id and assigns.\n\n  ## Examples\n\n      socket(MyApp.UserSocket)\n\n  \"\"\"\n  defmacro socket(socket_module) do\n    socket(socket_module, nil, [], [], __CALLER__)\n  end\n\n  @doc \"\"\"\n  Builds a socket for the given `socket_module` with given id and assigns.\n\n  ## Examples\n\n      socket(MyApp.UserSocket, \"user_id\", %{some: :assign})\n\n  If you need to access the socket in another process than the test process,\n  you can give the `pid` of the test process in the 4th argument.\n\n  ## Examples\n\n      test \"connect in a task\" do\n        pid = self()\n        task = Task.async(fn ->\n          socket = socket(MyApp.UserSocket, \"user_id\", %{some: :assign}, test_process: pid)\n          broadcast_from!(socket, \"default\", %{\"foo\" => \"bar\"})\n          assert_push \"default\", %{\"foo\" => \"bar\"}\n        end)\n        Task.await(task)\n      end\n\n  \"\"\"\n  defmacro socket(socket_module, socket_id, socket_assigns, options \\\\ []) do\n    socket(socket_module, socket_id, socket_assigns, options, __CALLER__)\n  end\n\n  defp socket(module, id, assigns, options, caller) do\n    if endpoint = Module.get_attribute(caller.module, :endpoint) do\n      quote do\n        unquote(__MODULE__).__socket__(\n          unquote(module),\n          unquote(id),\n          unquote(assigns),\n          unquote(endpoint),\n          unquote(options)\n        )\n      end\n    else\n      raise \"module attribute @endpoint not set for socket/2\"\n    end\n  end\n\n  @doc false\n  def __socket__(socket, id, assigns, endpoint, options) do\n    %Socket{\n      assigns: Enum.into(assigns, %{}),\n      endpoint: endpoint,\n      handler: socket || first_socket!(endpoint),\n      id: id,\n      pubsub_server: endpoint.config(:pubsub_server),\n      serializer: NoopSerializer,\n      transport: {__MODULE__, fetch_test_supervisor!(options)},\n      transport_pid: self()\n    }\n  end\n\n  defp first_socket!(endpoint) do\n    case endpoint.__sockets__ do\n      [] -> raise ArgumentError, \"#{inspect endpoint} has no socket declaration\"\n      [{_, socket, _} | _] -> socket\n    end\n  end\n\n  defp fetch_test_supervisor!(options) do\n    case ExUnit.OnExitHandler.get_supervisor(Keyword.get(options, :test_process, self())) do\n      {:ok, nil} ->\n        opts = [strategy: :one_for_one, max_restarts: 1_000_000, max_seconds: 1]\n        {:ok, sup} = Supervisor.start_link([], opts)\n        ExUnit.OnExitHandler.put_supervisor(self(), sup)\n        sup\n\n      {:ok, sup} ->\n        sup\n\n      :error ->\n        raise ArgumentError, \"socket/1-3 can only be invoked from the test process\"\n    end\n  end\n\n  @doc false\n  @deprecated \"Phoenix.ChannelTest.socket/0 is deprecated, please call socket/1 instead\"\n  defmacro socket() do\n    socket(nil, nil, [], [], __CALLER__)\n  end\n\n  @doc false\n  @deprecated \"Phoenix.ChannelTest.socket/2 is deprecated, please call socket/4 instead\"\n  defmacro socket(id, assigns) do\n    socket(nil, id, assigns, [], __CALLER__)\n  end\n\n  @doc \"\"\"\n  Initiates a transport connection for the socket handler.\n\n  Useful for testing UserSocket authentication. Returns\n  the result of the handler's `connect/3` callback.\n  \"\"\"\n  defmacro connect(handler, params, options \\\\ quote(do: [])) do\n    if endpoint = Module.get_attribute(__CALLER__.module, :endpoint) do\n      quote do\n        unquote(__MODULE__).__connect__(\n          unquote(endpoint),\n          unquote(handler),\n          unquote(params),\n          unquote(options)\n        )\n      end\n    else\n      raise \"module attribute @endpoint not set for socket/2\"\n    end\n  end\n\n  @doc false\n  def __connect__(endpoint, handler, params, options) do\n    {connect_info, options} =\n      if is_map(options) do\n        IO.warn(\n          \"Passing \\\"connect_info\\\" directly to connect/3 is deprecated, please pass \\\"connect_info: ...\\\" as an option instead\"\n        )\n\n        {options, []}\n      else\n        Keyword.pop(options, :connect_info, %{})\n      end\n\n    map = %{\n      endpoint: endpoint,\n      transport: {__MODULE__, fetch_test_supervisor!(options)},\n      options: [serializer: [{NoopSerializer, \"~> 1.0.0\"}]],\n      params: __stringify__(params),\n      connect_info: connect_info\n    }\n\n    with {:ok, state} <- handler.connect(map),\n         {:ok, {_, socket}} = handler.init(state),\n         do: {:ok, socket}\n  end\n\n  @doc \"See `subscribe_and_join!/4`.\"\n  def subscribe_and_join!(%Socket{} = socket, topic) when is_binary(topic) do\n    subscribe_and_join!(socket, nil, topic, %{})\n  end\n\n  @doc \"See `subscribe_and_join!/4`.\"\n  def subscribe_and_join!(%Socket{} = socket, topic, payload)\n      when is_binary(topic) and is_map(payload) do\n    subscribe_and_join!(socket, nil, topic, payload)\n  end\n\n  @doc \"\"\"\n  Same as `subscribe_and_join/4`, but returns either the socket\n  or throws an error.\n\n  This is helpful when you are not testing joining the channel\n  and just need the socket.\n  \"\"\"\n  def subscribe_and_join!(%Socket{} = socket, channel, topic, payload \\\\ %{})\n      when is_atom(channel) and is_binary(topic) and is_map(payload) do\n    case subscribe_and_join(socket, channel, topic, payload) do\n      {:ok, _, socket} -> socket\n      {:error, error}  -> raise \"could not join channel, got error: #{inspect(error)}\"\n    end\n  end\n\n  @doc \"See `subscribe_and_join/4`.\"\n  def subscribe_and_join(%Socket{} = socket, topic) when is_binary(topic) do\n    subscribe_and_join(socket, nil, topic, %{})\n  end\n\n  @doc \"See `subscribe_and_join/4`.\"\n  def subscribe_and_join(%Socket{} = socket, topic, payload)\n      when is_binary(topic) and is_map(payload) do\n    subscribe_and_join(socket, nil, topic, payload)\n  end\n\n  @doc \"\"\"\n  Subscribes to the given topic and joins the channel\n  under the given topic and payload.\n\n  By subscribing to the topic, we can use `assert_broadcast/3`\n  to verify a message has been sent through the pubsub layer.\n\n  By joining the channel, we can interact with it directly.\n  The given channel is joined in a separate process which is\n  linked to the test process.\n\n  If no channel module is provided, the socket's handler is used to\n  lookup the matching channel for the given topic.\n\n  It returns `{:ok, reply, socket}` or `{:error, reply}`.\n  \"\"\"\n  def subscribe_and_join(%Socket{} = socket, channel, topic, payload \\\\ %{})\n      when is_atom(channel) and is_binary(topic) and is_map(payload) do\n    socket.endpoint.subscribe(topic)\n    join(socket, channel, topic, payload)\n  end\n\n  @doc \"See `join/4`.\"\n  def join(%Socket{} = socket, topic) when is_binary(topic) do\n    join(socket, nil, topic, %{})\n  end\n\n  @doc \"See `join/4`.\"\n  def join(%Socket{} = socket, topic, payload) when is_binary(topic) and is_map(payload) do\n    join(socket, nil, topic, payload)\n  end\n\n  @doc \"\"\"\n  Joins the channel under the given topic and payload.\n\n  The given channel is joined in a separate process\n  which is linked to the test process.\n\n  It returns `{:ok, reply, socket}` or `{:error, reply}`.\n  \"\"\"\n  def join(%Socket{} = socket, channel, topic, payload \\\\ %{})\n      when is_atom(channel) and is_binary(topic) and is_map(payload) do\n    message = %Message{\n      event: \"phx_join\",\n      payload: __stringify__(payload),\n      topic: topic,\n      ref: System.unique_integer([:positive])\n    }\n\n    {channel, opts} =\n      if channel do\n        {channel, []}\n      else\n        match_topic_to_channel!(socket, topic)\n      end\n\n    %Socket{transport: {__MODULE__, sup}} = socket\n\n    starter =\n      fn _, _, spec ->\n        Supervisor.start_child(sup, %{spec | id: make_ref()})\n      end\n\n    case Server.join(socket, channel, message, [starter: starter] ++ opts) do\n      {:ok, reply, pid} ->\n        Process.link(pid)\n        {:ok, reply, Server.socket(pid)}\n      {:error, _} = error ->\n        error\n    end\n  end\n\n  @doc \"\"\"\n  Pushes a message into the channel.\n\n  The triggers the `handle_in/3` callback in the channel.\n\n  ## Examples\n\n      iex> push(socket, \"new_message\", %{id: 1, content: \"hello\"})\n      reference\n\n  \"\"\"\n  @spec push(Socket.t, String.t, map()) :: reference()\n  def push(%Socket{} = socket, event, payload \\\\ %{}) do\n    ref = make_ref()\n    send(socket.channel_pid,\n         %Message{event: event, topic: socket.topic, ref: ref, payload: __stringify__(payload)})\n    ref\n  end\n\n  @doc \"\"\"\n  Emulates the client leaving the channel.\n\n  By default this will crash the test process. Run\n  `Process.unlink(socket.channel_pid)` before this to prevent\n  this from happening. See [Leave and close](#module-leave-and-close).\n  \"\"\"\n  @spec leave(Socket.t) :: reference()\n  def leave(%Socket{} = socket) do\n    push(socket, \"phx_leave\", %{})\n  end\n\n  @doc \"\"\"\n  Emulates the client closing the socket.\n\n  By default this will crash the test process. Run\n  `Process.unlink(socket.channel_pid)` before this to prevent\n  this from happening. See [Leave and close](#module-leave-and-close).\n\n  Closing socket is synchronous and has a default timeout\n  of 5000 milliseconds.\n  \"\"\"\n  def close(%Socket{} = socket, timeout \\\\ 5000) do\n    Server.close(socket.channel_pid, timeout)\n  end\n\n  @doc \"\"\"\n  Broadcast event from pid to all subscribers of the socket topic.\n\n  The test process will not receive the published message. This triggers\n  the `handle_out/3` callback in the channel.\n\n  ## Examples\n\n      iex> broadcast_from(socket, \"new_message\", %{id: 1, content: \"hello\"})\n      :ok\n\n  \"\"\"\n  def broadcast_from(%Socket{} = socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic, transport_pid: transport_pid} = socket\n    Server.broadcast_from pubsub_server, transport_pid, topic, event, message\n  end\n\n  @doc \"\"\"\n  Same as `broadcast_from/3`, but raises if broadcast fails.\n  \"\"\"\n  def broadcast_from!(%Socket{} = socket, event, message) do\n    %{pubsub_server: pubsub_server, topic: topic, transport_pid: transport_pid} = socket\n    Server.broadcast_from! pubsub_server, transport_pid, topic, event, message\n  end\n\n  @doc \"\"\"\n  Asserts the channel has pushed a message back to the client\n  with the given event and payload within `timeout`.\n\n  Notice event and payload are patterns. This means one can write:\n\n      assert_push \"some_event\", %{\"data\" => _}\n\n  In the assertion above, we don't particularly care about\n  the data being sent, as long as something was sent.\n\n  The timeout is in milliseconds and defaults to the `:assert_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n\n  **NOTE:** Because event and payload are patterns, they will be matched.  This\n  means that if you wish to assert that the received payload is equivalent to\n  an existing variable, you need to pin the variable in the assertion\n  expression.\n\n  Good:\n\n      expected_payload = %{foo: \"bar\"}\n      assert_push \"some_event\", ^expected_payload\n\n  Bad:\n\n      expected_payload = %{foo: \"bar\"}\n      assert_push \"some_event\", expected_payload\n      # The code above does not assert the payload matches the described map.\n\n  Guards can also be given to the payload pattern:\n\n      assert_push \"some_event\", %{\"counter\" => c} when c > 0\n  \"\"\"\n  defmacro assert_push(event, payload, timeout \\\\ Application.fetch_env!(:ex_unit, :assert_receive_timeout)) do\n    pattern = extract_pattern_and_apply_guard(event, payload, Phoenix.Socket.Message)\n\n    quote do\n      assert_receive unquote(pattern), unquote(timeout)\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the channel has not pushed a message to the client\n  matching the given event and payload within `timeout`.\n\n  Like `assert_push`, the event and payload are patterns.\n\n  The timeout is in milliseconds and defaults to the `:refute_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n  Keep in mind this macro will block the test by the\n  timeout value, so use it only when necessary as overuse\n  will certainly slow down your test suite.\n  \"\"\"\n  defmacro refute_push(event, payload, timeout \\\\ Application.fetch_env!(:ex_unit, :refute_receive_timeout)) do\n\n    quote do\n      refute_receive %Phoenix.Socket.Message{\n                        event: unquote(event),\n                        payload: unquote(payload)}, unquote(timeout)\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the channel has replied to the given message within\n  `timeout`.\n\n  Notice status and payload are patterns. This means one can write:\n\n      ref = push(channel, \"some_event\")\n      assert_reply ref, :ok, %{\"data\" => _}\n\n  In the assertion above, we don't particularly care about\n  the data being sent, as long as something was replied.\n\n  The timeout is in milliseconds and defaults to the `:assert_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n\n  Guards can also be given to the payload pattern:\n\n      ref = push(channel, \"some_event\")\n      assert_reply ref, :ok, %{\"counter\" => c} when c > 0\n  \"\"\"\n  defmacro assert_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch_env!(:ex_unit, :assert_receive_timeout)) do\n    {payload, guard} = extract_guard(payload)\n\n    pattern = quote do\n      %Phoenix.Socket.Reply{\n                        ref: ^ref,\n                        status: unquote(status),\n                        payload: unquote(payload)}\n    end\n\n    struct_pattern = apply_guard(pattern, guard)\n\n    quote do\n      ref = unquote(ref)\n      assert_receive unquote(struct_pattern), unquote(timeout)\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the channel has not replied with a matching payload within\n  `timeout`.\n\n  Like `assert_reply`, the event and payload are patterns.\n\n  The timeout is in milliseconds and defaults to the `:refute_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n  Keep in mind this macro will block the test by the\n  timeout value, so use it only when necessary as overuse\n  will certainly slow down your test suite.\n  \"\"\"\n  defmacro refute_reply(ref, status, payload \\\\ Macro.escape(%{}), timeout \\\\ Application.fetch_env!(:ex_unit, :refute_receive_timeout)) do\n    quote do\n      ref = unquote(ref)\n      refute_receive %Phoenix.Socket.Reply{\n                        ref: ^ref,\n                        status: unquote(status),\n                        payload: unquote(payload)}, unquote(timeout)\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the channel has broadcast a message within `timeout`.\n\n  Before asserting anything was broadcast, we must first\n  subscribe to the topic of the channel in the test process:\n\n      @endpoint.subscribe(\"foo:ok\")\n\n  Now we can match on event and payload as patterns:\n\n      assert_broadcast \"some_event\", %{\"data\" => _}\n\n  In the assertion above, we don't particularly care about\n  the data being sent, as long as something was sent.\n\n  The timeout is in milliseconds and defaults to the `:assert_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n\n  Guards can also be given to the payload pattern:\n\n      assert_broadcast \"some_event\", %{\"counter\" => c} when c > 0\n  \"\"\"\n  defmacro assert_broadcast(event, payload, timeout \\\\ Application.fetch_env!(:ex_unit, :assert_receive_timeout)) do\n    pattern = extract_pattern_and_apply_guard(event, payload, Phoenix.Socket.Broadcast)\n\n    quote do\n      assert_receive unquote(pattern), unquote(timeout)\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the channel has not broadcast a message within `timeout`.\n\n  Like `assert_broadcast`, the event and payload are patterns.\n\n  The timeout is in milliseconds and defaults to the `:refute_receive_timeout`\n  set on the `:ex_unit` application (which defaults to 100ms).\n  Keep in mind this macro will block the test by the\n  timeout value, so use it only when necessary as overuse\n  will certainly slow down your test suite.\n  \"\"\"\n  defmacro refute_broadcast(event, payload, timeout \\\\ Application.fetch_env!(:ex_unit, :refute_receive_timeout)) do\n    quote do\n      refute_receive %Phoenix.Socket.Broadcast{event: unquote(event),\n                                               payload: unquote(payload)}, unquote(timeout)\n    end\n  end\n\n  defp match_topic_to_channel!(socket, topic) do\n    unless socket.handler do\n      raise \"\"\"\n      no socket handler found to lookup channel for topic #{inspect topic}.\n      Use connect/3 when calling subscribe_and_join/* (or subscribe_and_join!/*)\n      without a channel, for example:\n\n          {:ok, socket} = connect(UserSocket, %{}, %{})\n          socket = subscribe_and_join!(socket, \"foo:bar\", %{})\n\n      \"\"\"\n    end\n\n    case socket.handler.__channel__(topic) do\n      {channel, opts} when is_atom(channel) -> {channel, opts}\n      _ -> raise \"no channel found for topic #{inspect topic} in #{inspect socket.handler}\"\n    end\n  end\n\n  defp extract_guard({:when, _, [payload, guard]}), do: {payload, guard}\n  defp extract_guard(payload), do: {payload, nil}\n\n  defp apply_guard(pattern, nil), do: pattern\n  defp apply_guard(pattern, guard), do: {:when, [], [pattern, guard]}\n\n  defp extract_pattern_and_apply_guard(event, payload, struct_module) do\n    {payload, guard} = extract_guard(payload)\n\n    pattern_struct = quote do\n      %{__struct__: unquote(struct_module), event: unquote(event), payload: unquote(payload)}\n    end\n\n    apply_guard(pattern_struct, guard)\n  end\n\n  @doc false\n  def __stringify__(%{__struct__: _} = struct),\n    do: struct\n  def __stringify__(%{} = params),\n    do: Enum.into(params, %{}, &stringify_kv/1)\n  def __stringify__(params) when is_list(params),\n    do: Enum.map(params, &__stringify__/1)\n  def __stringify__(other),\n    do: other\n\n  defp stringify_kv({k, v}),\n    do: {to_string(k), __stringify__(v)}\nend\n"
  },
  {
    "path": "lib/phoenix/test/conn_test.ex",
    "content": "defmodule Phoenix.ConnTest do\n  @moduledoc \"\"\"\n  Conveniences for testing Phoenix endpoints and connection related helpers.\n\n  You likely want to use this module or make it part of your `ExUnit.CaseTemplate`.\n  Once used, this module automatically imports all functions defined here as\n  well as the functions in `Plug.Conn`.\n\n  ## Endpoint testing\n\n  `Phoenix.ConnTest` typically works against endpoints. That's the preferred way\n  to test anything that your router dispatches to:\n\n      @endpoint MyAppWeb.Endpoint\n\n      test \"says welcome on the home page\" do\n        conn = get(build_conn(), \"/\")\n        assert conn.resp_body =~ \"Welcome!\"\n      end\n\n      test \"logs in\" do\n        conn = post(build_conn(), \"/login\", [username: \"john\", password: \"doe\"])\n        assert conn.resp_body =~ \"Logged in!\"\n      end\n\n  The `@endpoint` module attribute contains the endpoint under testing,\n  most commonly your application endpoint itself. If you are using the\n  MyApp.ConnCase generated by Phoenix, it is automatically set for you.\n\n  As in your router and controllers, the connection is the main abstraction\n  in testing. `build_conn()` returns a new connection and functions in this\n  module can be used to manipulate the connection before dispatching\n  to the endpoint.\n\n  For example, one could set the accepts header for json requests as\n  follows:\n\n      build_conn()\n      |> put_req_header(\"accept\", \"application/json\")\n      |> get(\"/\")\n\n  You can also create your own helpers, such as `json_conn()` that uses\n  `build_conn/0` and `put_req_header/3`, so you avoid repeating the connection\n  setup throughout your tests.\n\n  ## Controller testing\n\n  The functions in this module can also be used for controller testing.\n  While endpoint testing is preferred over controller testing, especially\n  since the controller in Phoenix plays an integration role between your\n  domain and your views, unit testing controllers may be helpful in some\n  situations.\n\n  For such cases, you need to set the `@endpoint` attribute to your controller\n  and pass an atom representing the action to dispatch:\n\n      @endpoint MyAppWeb.HomeController\n\n      test \"says welcome on the home page\" do\n        conn = get(build_conn(), :index)\n        assert conn.resp_body =~ \"Welcome!\"\n      end\n\n  Keep in mind that, once the `@endpoint` variable is set, all tests after\n  setting it will be affected.\n\n  ## Views testing\n\n  Under other circumstances, you may be testing a view or another layer that\n  requires a connection for processing. For such cases, a connection can be\n  created using the `build_conn/3` helper:\n\n      MyApp.UserView.render(\"hello.html\", conn: build_conn(:get, \"/\"))\n\n  While `build_conn/0` returns a connection with no request information to it,\n  `build_conn/3` returns a connection with the given request information already\n  filled in.\n\n  ## Recycling\n\n  Browsers implement a storage by using cookies. When a cookie is set in the\n  response, the browser stores it and sends it in the next request.\n\n  To emulate this behaviour, this module provides the idea of recycling.\n  The `recycle/1` function receives a connection and returns a new connection,\n  similar to the one returned by `build_conn/0` with all the response cookies\n  from the previous connection defined as request headers. This is useful when\n  testing multiple routes that require cookies or session to work.\n\n  Keep in mind Phoenix will automatically recycle the connection between\n  dispatches. This usually works out well most times, but it may discard\n  information if you are modifying the connection before the next dispatch:\n\n      # No recycling as the connection is fresh\n      conn = get(build_conn(), \"/\")\n\n      # The connection is recycled, creating a new one behind the scenes\n      conn = post(conn, \"/login\")\n\n      # We can also recycle manually in case we want custom headers\n      conn =\n        conn\n        |> recycle()\n        |> put_req_header(\"x-special\", \"nice\")\n\n      # No recycling as we did it explicitly\n      conn = delete(conn, \"/logout\")\n\n  Recycling also recycles the \"accept\" and \"authorization\" headers,\n  as well as peer data information.\n  \"\"\"\n\n  @doc false\n  defmacro __using__(_) do\n    IO.warn \"\"\"\n    Using Phoenix.ConnTest is deprecated, instead of:\n\n        use Phoenix.ConnTest\n\n    do:\n\n        import Plug.Conn\n        import Phoenix.ConnTest\n    \"\"\", Macro.Env.stacktrace(__CALLER__)\n\n    quote do\n      import Plug.Conn\n      import Phoenix.ConnTest\n    end\n  end\n\n  alias Plug.Conn\n  import ExUnit.Assertions, only: [flunk: 1]\n\n  @doc \"\"\"\n  Creates a connection to be used in upcoming requests.\n  \"\"\"\n  @spec build_conn() :: Conn.t\n  def build_conn() do\n    build_conn(:get, \"/\", nil)\n  end\n\n  @doc \"\"\"\n  Creates a connection to be used in upcoming requests\n  with a preset method, path and body.\n\n  This is useful when a specific connection is required\n  for testing a plug or a particular function.\n  \"\"\"\n  @spec build_conn(atom | binary, binary, binary | list | map | nil) :: Conn.t\n  def build_conn(method, path, params_or_body \\\\ nil) do\n    Plug.Adapters.Test.Conn.conn(%Conn{}, method, path, params_or_body)\n    |> Conn.put_private(:plug_skip_csrf_protection, true)\n    |> Conn.put_private(:phoenix_recycled, true)\n  end\n\n  @http_methods [:get, :post, :put, :patch, :delete, :options, :connect, :trace, :head]\n\n  for method <- @http_methods do\n    @doc \"\"\"\n    Dispatches to the current endpoint.\n\n    See `dispatch/5` for more information.\n    \"\"\"\n    defmacro unquote(method)(conn, path_or_action, params_or_body \\\\ nil) do\n      method = unquote(method)\n      quote do\n        Phoenix.ConnTest.dispatch(unquote(conn), @endpoint, unquote(method),\n                                  unquote(path_or_action), unquote(params_or_body))\n      end\n    end\n  end\n\n  @doc \"\"\"\n  Dispatches the connection to the given endpoint.\n\n  When invoked via `get/3`, `post/3` and friends, the endpoint\n  is automatically retrieved from the `@endpoint` module\n  attribute, otherwise it must be given as an argument.\n\n  The connection will be configured with the given `method`,\n  `path_or_action` and `params_or_body`.\n\n  If `path_or_action` is a string, it is considered to be the\n  request path and stored as so in the connection. If an atom,\n  it is assumed to be an action and the connection is dispatched\n  to the given action.\n\n  ## Parameters and body\n\n  This function, as well as `get/3`, `post/3` and friends, accepts the\n  request body or parameters as last argument:\n\n        get(build_conn(), \"/\", some: \"param\")\n        get(build_conn(), \"/\", \"some=param&url=encoded\")\n\n  The allowed values are:\n\n    * `nil` - meaning there is no body\n\n    * a binary - containing a request body. For such cases, `:headers`\n      must be given as option with a content-type\n\n    * a map or list - containing the parameters which will automatically\n      set the content-type to multipart. The map or list may contain\n      other lists or maps and all entries will be normalized to string\n      keys\n\n    * a struct - unlike other maps, a struct will be passed through as-is\n      without normalizing its entries\n  \"\"\"\n  def dispatch(conn, endpoint, method, path_or_action, params_or_body \\\\ nil)\n  def dispatch(%Plug.Conn{} = conn, endpoint, method, path_or_action, params_or_body) do\n    if is_nil(endpoint) do\n      raise \"no @endpoint set in test case\"\n    end\n\n    if is_binary(params_or_body) and is_nil(List.keyfind(conn.req_headers, \"content-type\", 0)) do\n      raise ArgumentError, \"a content-type header is required when setting \" <>\n                           \"a binary body in a test connection\"\n    end\n\n    conn\n    |> ensure_recycled()\n    |> dispatch_endpoint(endpoint, method, path_or_action, params_or_body)\n    |> Conn.put_private(:phoenix_recycled, false)\n    |> from_set_to_sent()\n  end\n  def dispatch(conn, _endpoint, method, _path_or_action, _params_or_body) do\n    raise ArgumentError, \"expected first argument to #{method} to be a \" <>\n                         \"%Plug.Conn{}, got #{inspect conn}\"\n  end\n\n  defp dispatch_endpoint(conn, endpoint, method, path, params_or_body) when is_binary(path) do\n    conn\n    |> Plug.Adapters.Test.Conn.conn(method, path, params_or_body)\n    |> endpoint.call(endpoint.init([]))\n  end\n\n  defp dispatch_endpoint(conn, endpoint, method, action, params_or_body) when is_atom(action) do\n    conn\n    |> Plug.Adapters.Test.Conn.conn(method, \"/\", params_or_body)\n    |> endpoint.call(endpoint.init(action))\n  end\n\n  defp from_set_to_sent(%Conn{state: :set} = conn), do: Conn.send_resp(conn)\n  defp from_set_to_sent(conn), do: conn\n\n  @doc \"\"\"\n  Inits a session used exclusively for testing.\n  \"\"\"\n  @spec init_test_session(Conn.t, map | keyword) :: Conn.t\n  defdelegate init_test_session(conn, session), to: Plug.Test\n\n  @doc \"\"\"\n  Puts a request cookie.\n  \"\"\"\n  @spec put_req_cookie(Conn.t, binary, binary) :: Conn.t\n  defdelegate put_req_cookie(conn, key, value), to: Plug.Test\n\n  @doc \"\"\"\n  Deletes a request cookie.\n  \"\"\"\n  @spec delete_req_cookie(Conn.t, binary) :: Conn.t\n  defdelegate delete_req_cookie(conn, key), to: Plug.Test\n\n  @doc \"\"\"\n  Fetches the flash storage.\n  \"\"\"\n  @spec fetch_flash(Conn.t) :: Conn.t\n  defdelegate fetch_flash(conn), to: Phoenix.Controller\n\n  @doc \"\"\"\n  Gets the whole flash storage.\n  \"\"\"\n  @spec get_flash(Conn.t) :: map\n  @deprecated \"get_flash/1 is deprecated. Use conn.assigns.flash instead\"\n  def get_flash(conn), do: conn.assigns.flash\n\n  @doc \"\"\"\n  Gets the given key from the flash storage.\n  \"\"\"\n  @spec get_flash(Conn.t, term) :: term\n  @deprecated \"get_flash/2 is deprecated. Use Phoenix.Flash.get/2 instead\"\n  def get_flash(conn, key) do\n    Phoenix.Flash.get(conn.assigns.flash, key)\n  end\n\n  @doc \"\"\"\n  Puts the given value under key in the flash storage.\n  \"\"\"\n  @spec put_flash(Conn.t, term, term) :: Conn.t\n  defdelegate put_flash(conn, key, value), to: Phoenix.Controller\n\n  @doc \"\"\"\n  Clears up the flash storage.\n  \"\"\"\n  @spec clear_flash(Conn.t) :: Conn.t\n  defdelegate clear_flash(conn), to: Phoenix.Controller\n\n  @doc \"\"\"\n  Returns the content type as long as it matches the given format.\n\n  ## Examples\n\n      # Assert we have an html response with utf-8 charset\n      assert response_content_type(conn, :html) =~ \"charset=utf-8\"\n\n  \"\"\"\n  @spec response_content_type(Conn.t, atom) :: String.t\n  def response_content_type(conn, format) when is_atom(format) do\n    case Conn.get_resp_header(conn, \"content-type\") do\n      [] ->\n        raise \"no content-type was set, expected a #{format} response\"\n      [h] ->\n        if response_content_type?(h, format) do\n          h\n        else\n          raise \"expected content-type for #{format}, got: #{inspect h}\"\n        end\n      [_|_] ->\n        raise \"more than one content-type was set, expected a #{format} response\"\n    end\n  end\n\n  defp response_content_type?(header, format) do\n    case parse_content_type(header) do\n      {part, subpart} ->\n        format = Atom.to_string(format)\n        format in MIME.extensions(part <> \"/\" <> subpart) or\n          format == subpart or String.ends_with?(subpart, \"+\" <> format)\n      _  ->\n        false\n    end\n  end\n\n  defp parse_content_type(header) do\n    case Plug.Conn.Utils.content_type(header) do\n      {:ok, part, subpart, _params} ->\n        {part, subpart}\n      _ ->\n        false\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the given status code and returns the response body\n  if one was set or sent.\n\n  ## Examples\n\n      conn = get(build_conn(), \"/\")\n      assert response(conn, 200) =~ \"hello world\"\n\n  \"\"\"\n  @spec response(Conn.t, status :: integer | atom) :: binary\n  def response(%Conn{state: :unset}, _status) do\n    raise \"\"\"\n    expected connection to have a response but no response was set/sent.\n    Please verify that you assign to \"conn\" after a request:\n\n        conn = get(conn, \"/\")\n        assert html_response(conn) =~ \"Hello\"\n    \"\"\"\n  end\n\n  def response(%Conn{status: status, resp_body: body}, given) do\n    given = Plug.Conn.Status.code(given)\n\n    if given == status do\n      body\n    else\n      raise \"expected response with status #{given}, got: #{status}, with body:\\n#{inspect(body)}\"\n    end\n  end\n\n  @doc \"\"\"\n  Asserts the given status code, that we have an html response and\n  returns the response body if one was set or sent.\n\n  ## Examples\n\n      assert html_response(conn, 200) =~ \"<html>\"\n  \"\"\"\n  @spec html_response(Conn.t, status :: integer | atom) :: String.t\n  def html_response(conn, status) do\n    body = response(conn, status)\n    _    = response_content_type(conn, :html)\n    body\n  end\n\n  @doc \"\"\"\n  Asserts the given status code, that we have a text response and\n  returns the response body if one was set or sent.\n\n  ## Examples\n\n      assert text_response(conn, 200) =~ \"hello\"\n  \"\"\"\n  @spec text_response(Conn.t, status :: integer | atom) :: String.t\n  def text_response(conn, status) do\n    body = response(conn, status)\n    _    = response_content_type(conn, :text)\n    body\n  end\n\n  @doc \"\"\"\n  Asserts the given status code, that we have a json response and\n  returns the decoded JSON response if one was set or sent.\n\n  ## Examples\n\n      body = json_response(conn, 200)\n      assert \"can't be blank\" in body[\"errors\"]\n\n  \"\"\"\n  @spec json_response(Conn.t, status :: integer | atom) :: term\n  def json_response(conn, status) do\n    body = response(conn, status)\n    _    = response_content_type(conn, :json)\n\n    Phoenix.json_library().decode!(body)\n  end\n\n  @doc \"\"\"\n  Returns the location header from the given redirect response.\n\n  Raises if the response does not match the redirect status code\n  (defaults to 302).\n\n  ## Examples\n\n      assert redirected_to(conn) =~ \"/foo/bar\"\n      assert redirected_to(conn, 301) =~ \"/foo/bar\"\n      assert redirected_to(conn, :moved_permanently) =~ \"/foo/bar\"\n  \"\"\"\n  @spec redirected_to(Conn.t, status :: non_neg_integer) :: String.t\n  def redirected_to(conn, status \\\\ 302)\n\n  def redirected_to(%Conn{state: :unset}, _status) do\n    raise \"expected connection to have redirected but no response was set/sent\"\n  end\n\n  def redirected_to(conn, status) when is_atom(status) do\n    redirected_to(conn, Plug.Conn.Status.code(status))\n  end\n\n  def redirected_to(%Conn{status: status} = conn, status) do\n    location = Conn.get_resp_header(conn, \"location\") |> List.first\n    location || raise \"no location header was set on redirected_to\"\n  end\n\n  def redirected_to(conn, status) do\n    raise \"expected redirection with status #{status}, got: #{conn.status}\"\n  end\n\n  @doc \"\"\"\n  Recycles the connection.\n\n  Recycling receives a connection and returns a new connection,\n  containing cookies and relevant information from the given one.\n\n  This emulates behaviour performed by browsers where cookies\n  returned in the response are available in following requests.\n\n  By default, only the headers \"accept\", \"accept-language\", and\n  \"authorization\" are recycled. However, a custom set of headers\n  can be specified by passing a list of strings representing its\n  names as the second argument of the function.\n\n  Note `recycle/1` is automatically invoked when dispatching\n  to the endpoint, unless the connection has already been\n  recycled.\n  \"\"\"\n  @spec recycle(Conn.t, [String.t]) :: Conn.t\n  def recycle(conn, headers \\\\ ~w(accept accept-language authorization)) do\n    build_conn()\n    |> Map.put(:host, conn.host)\n    |> Map.put(:remote_ip, conn.remote_ip)\n    |> Plug.Test.recycle_cookies(conn)\n    |> Plug.Test.put_peer_data(Plug.Conn.get_peer_data(conn))\n    |> copy_headers(conn.req_headers, headers)\n  end\n\n  defp copy_headers(conn, headers, copy) do\n    headers = for {k, v} <- headers, k in copy, do: {k, v}\n    %{conn | req_headers: headers ++ conn.req_headers}\n  end\n\n  @doc \"\"\"\n  Ensures the connection is recycled if it wasn't already.\n\n  See `recycle/1` for more information.\n  \"\"\"\n  @spec ensure_recycled(Conn.t) :: Conn.t\n  def ensure_recycled(conn) do\n    if conn.private[:phoenix_recycled] do\n      conn\n    else\n      recycle(conn)\n    end\n  end\n\n  @doc \"\"\"\n  Calls the Endpoint and Router pipelines.\n\n  Useful for unit testing Plugs where Endpoint and/or router pipeline\n  plugs are required for proper setup.\n\n  Note the use of `get(\"/\")` following `bypass_through` in the examples below.\n  To execute the plug pipelines, you must issue a request against the router.\n  Most often, you can simply send a GET request against the root path, but you\n  may also specify a different method or path which your pipelines may operate\n  against.\n\n  ## Examples\n\n  For example, imagine you are testing an authentication plug in\n  isolation, but you need to invoke the Endpoint plugs and router\n  pipelines to set up session and flash related dependencies.\n  One option is to invoke an existing route that uses the proper\n  pipelines. You can do so by passing the connection and the\n  router name to `bypass_through`:\n\n      conn =\n        conn\n        |> bypass_through(MyAppWeb.Router)\n        |> get(\"/some_url\")\n        |> MyApp.RequireAuthentication.call([])\n      assert conn.halted\n\n  You can also specify which pipelines you want to run:\n\n      conn =\n        conn\n        |> bypass_through(MyAppWeb.Router, [:browser])\n        |> get(\"/\")\n        |> MyApp.RequireAuthentication.call([])\n      assert conn.halted\n\n  Alternatively, you could only invoke the Endpoint's plugs:\n\n      conn =\n        conn\n        |> bypass_through()\n        |> get(\"/\")\n        |> MyApp.RequireAuthentication.call([])\n\n      assert conn.halted\n  \"\"\"\n  @spec bypass_through(Conn.t) :: Conn.t\n  def bypass_through(conn) do\n    Plug.Conn.put_private(conn, :phoenix_bypass, :all)\n  end\n\n  @doc \"\"\"\n  Calls the Endpoint and Router pipelines for the current route.\n\n  See `bypass_through/1`.\n  \"\"\"\n  @spec bypass_through(Conn.t, module) :: Conn.t\n  def bypass_through(conn, router) do\n    Plug.Conn.put_private(conn, :phoenix_bypass, {router, :current})\n  end\n\n  @doc \"\"\"\n  Calls the Endpoint and the given Router pipelines.\n\n  See `bypass_through/1`.\n  \"\"\"\n  @spec bypass_through(Conn.t, module, atom | list) :: Conn.t\n  def bypass_through(conn, router, pipelines) do\n    Plug.Conn.put_private(conn, :phoenix_bypass, {router, List.wrap(pipelines)})\n  end\n\n  @doc \"\"\"\n  Returns the matched params from the URL the connection was redirected to.\n\n  Uses the provided `%Plug.Conn{}`s router matched in the previous request.\n  Raises if the response's location header is not set or if the response does\n  not match the redirect status code (defaults to 302).\n\n  ## Examples\n\n      assert redirected_to(conn) =~ \"/posts/123\"\n      assert %{id: \"123\"} = redirected_params(conn)\n      assert %{id: \"123\"} = redirected_params(conn, 303)\n  \"\"\"\n  @spec redirected_params(Conn.t, status :: non_neg_integer) :: map\n  def redirected_params(%Plug.Conn{} = conn, status \\\\ 302) do\n    router = Phoenix.Controller.router_module(conn)\n    %URI{path: path, host: host} = conn |> redirected_to(status) |> URI.parse()\n    path = remove_script_name(conn, router, path)\n\n    case Phoenix.Router.route_info(router, \"GET\", path, host || conn.host) do\n      :error ->\n        raise Phoenix.Router.NoRouteError, conn: conn, router: router\n      %{path_params: path_params} ->\n        Enum.into(path_params, %{}, fn {key, val} -> {String.to_atom(key), val} end)\n    end\n  end\n\n  defp remove_script_name(conn, router, path) do\n    case conn.private[router] do\n      [_ | _] = list ->\n        script_name = \"/\" <> Enum.join(list, \",\")\n        String.replace_leading(path, script_name, \"\")\n\n      _ ->\n        path\n    end\n  end\n\n  @doc \"\"\"\n  Returns the matched params of the URL for the `%Plug.Conn{}`'s router.\n\n  Useful for extracting path params out of returned URLs, such as those\n  returned by `Phoenix.LiveViewTest`'s redirected results.\n\n  ## Examples\n\n      assert {:error, {:redirect, %{to: \"/posts/123\" = to}}} = live(conn, \"/path\")\n      assert %{id: \"123\"} = path_params(conn, to)\n  \"\"\"\n  @spec path_params(Conn.t, String.t) :: map\n  def path_params(%Plug.Conn{} = conn, to) when is_binary(to) do\n    router = Phoenix.Controller.router_module(conn)\n\n    case Phoenix.Router.route_info(router, \"GET\", to, conn.host) do\n    %{path_params: path_params} ->\n      Enum.into(path_params, %{}, fn {key, val} -> {String.to_atom(key), val} end)\n\n    :error ->\n      raise Phoenix.Router.NoRouteError, conn: conn, router: router\n    end\n  end\n\n  @doc \"\"\"\n  Asserts an error was wrapped and sent with the given status.\n\n  Useful for testing actions that you expect raise an error and have\n  the response wrapped in an HTTP status, with content usually rendered\n  by your MyAppWeb.ErrorHTML view.\n\n  The function accepts a status either as an integer HTTP status or\n  atom, such as `500` or `:internal_server_error`. The list of allowed atoms is available\n  in `Plug.Conn.Status`. If an error is raised, a 3-tuple of the wrapped\n  response is returned matching the status, headers, and body of the response:\n\n      {500, [{\"content-type\", \"text/html\"} | _], \"Internal Server Error\"}\n\n  ## Examples\n\n      assert_error_sent :internal_server_error, fn ->\n        get(build_conn(), \"/broken/route\")\n      end\n\n      response = assert_error_sent 500, fn ->\n        get(build_conn(), \"/broken/route\")\n      end\n      assert {500, [_h | _t], \"Internal Server Error\"} = response\n\n  This can also be used to test a route resulted in an error that was translated to a\n  specific response by the `Plug.Status` protocol, such as `Ecto.NoResultsError`:\n\n      assert_error_sent :not_found, fn ->\n        get(build_conn(), \"/something-that-raises-no-results-error\")\n      end\n\n  *Note*: for routes that don't raise an error, but instead return a status, you should test the\n  response directly:\n\n      conn = get(build_conn(), \"/users/not-found\")\n      assert response(conn, 404)\n  \"\"\"\n  @spec assert_error_sent(integer | atom, function) :: {integer, list, term}\n  def assert_error_sent(status_int_or_atom, func) do\n    expected_status = Plug.Conn.Status.code(status_int_or_atom)\n    discard_previously_sent()\n    result =\n      func\n      |> wrap_request()\n      |> receive_response(expected_status)\n\n    discard_previously_sent()\n    result\n  end\n\n  defp receive_response({:ok, conn}, expected_status) do\n    if conn.state == :sent do\n      flunk \"expected error to be sent as #{expected_status} status, but response sent #{conn.status} without error\"\n    else\n      flunk \"expected error to be sent as #{expected_status} status, but no error happened\"\n    end\n  end\n  defp receive_response({:error, {_kind, exception, stack}}, expected_status) do\n    receive do\n      {ref, {^expected_status, headers, body}} when is_reference(ref) ->\n        {expected_status, headers, body}\n\n      {ref, {sent_status, _headers, _body}} when is_reference(ref) ->\n        reraise ExUnit.AssertionError.exception(\"\"\"\n        expected error to be sent as #{expected_status} status, but got #{sent_status} from:\n\n        #{Exception.format_banner(:error, exception)}\n        \"\"\"), stack\n\n    after 0 ->\n      reraise ExUnit.AssertionError.exception(\"\"\"\n      expected error to be sent as #{expected_status} status, but got an error with no response from:\n\n      #{Exception.format_banner(:error, exception)}\n      \"\"\"), stack\n    end\n  end\n\n  defp discard_previously_sent() do\n    receive do\n      {ref, {_, _, _}} when is_reference(ref) -> discard_previously_sent()\n      {:plug_conn, :sent}                     -> discard_previously_sent()\n    after\n      0 -> :ok\n    end\n  end\n\n  defp wrap_request(func) do\n    try do\n      {:ok, func.()}\n    catch\n      kind, error -> {:error, {kind, error, __STACKTRACE__}}\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/token.ex",
    "content": "defmodule Phoenix.Token do\n  @moduledoc \"\"\"\n  Conveniences to sign/encrypt data inside tokens\n  for use in Channels, API authentication, and more.\n\n  The data stored in the token is signed to prevent tampering, and is\n  optionally encrypted. This means that, so long as the\n  key (see below) remains secret, you can be assured that the data\n  stored in the token has not been tampered with by a third party.\n  However, unless the token is encrypted, it is not safe to use this\n  token to store private information, such as a user's sensitive\n  identification data, as it can be trivially decoded. If the\n  token is encrypted, its contents will be kept secret from the\n  client, but it is still a best practice to encode as little secret\n  information as possible, to minimize the impact of key leakage.\n\n  ## Example\n\n  When generating a unique token for use in an API or Channel\n  it is advised to use a unique identifier for the user, typically\n  the id from a database. For example:\n\n      iex> user_id = 1\n      iex> token = Phoenix.Token.sign(MyAppWeb.Endpoint, \"user auth\", user_id)\n      iex> Phoenix.Token.verify(MyAppWeb.Endpoint, \"user auth\", token, max_age: 86400)\n      {:ok, 1}\n\n  In that example we have a user's id, we generate a token and\n  verify it using the secret key base configured in the given\n  `endpoint`. We guarantee the token will only be valid for one day\n  by setting a max age (recommended).\n\n  The first argument to `sign/4`, `verify/4`, `encrypt/4`, and\n  `decrypt/4` can be one of:\n\n    * the module name of a Phoenix endpoint (shown above) - where\n      the secret key base is extracted from the endpoint\n    * `Plug.Conn` - where the secret key base is extracted from the\n      endpoint stored in the connection\n    * `Phoenix.Socket` or `Phoenix.LiveView.Socket` - where the secret\n      key base is extracted from the endpoint stored in the socket\n    * a string, representing the secret key base itself. A key base\n      with at least 20 randomly generated characters should be used\n      to provide adequate entropy\n\n  The second argument is a [cryptographic salt](https://en.wikipedia.org/wiki/Salt_(cryptography))\n  which must be the same in both calls to `sign/4` and `verify/4`, or\n  both calls to `encrypt/4` and `decrypt/4`. For instance, it may be\n  called \"user auth\" and treated as namespace when generating a token\n  that will be used to authenticate users on channels or on your APIs.\n\n  The third argument can be any term (string, int, list, etc.)\n  that you wish to codify into the token. Upon valid verification,\n  this same term will be extracted from the token.\n\n  ## Usage\n\n  Once a token is signed, we can send it to the client in multiple ways.\n\n  One is via the meta tag:\n\n  ```heex\n  <meta name=\"channel_token\" content={Phoenix.Token.sign(@conn, \"user auth\", @current_user.id)}>\n  ```\n\n  Or an endpoint that returns it:\n\n      def create(conn, params) do\n        user = User.create(params)\n        render(conn, \"user.json\",\n               %{token: Phoenix.Token.sign(conn, \"user auth\", user.id), user: user})\n      end\n\n  Once the token is sent, the client may now send it back to the server\n  as an authentication mechanism. For example, we can use it to authenticate\n  a user on a Phoenix channel:\n\n      defmodule MyApp.UserSocket do\n        use Phoenix.Socket\n\n        def connect(%{\"token\" => token}, socket, _connect_info) do\n          case Phoenix.Token.verify(socket, \"user auth\", token, max_age: 86400) do\n            {:ok, user_id} ->\n              socket = assign(socket, :user, Repo.get!(User, user_id))\n              {:ok, socket}\n            {:error, _} ->\n              :error\n          end\n        end\n\n        def connect(_params, _socket, _connect_info), do: :error\n      end\n\n  In this example, the phoenix.js client will send the token in the\n  `connect` command which is then validated by the server.\n\n  `Phoenix.Token` can also be used for validating APIs, handling\n  password resets, e-mail confirmation and more.\n  \"\"\"\n\n  @type context ::\n          Plug.Conn.t()\n          | %{required(:endpoint) => atom, optional(atom()) => any()}\n          | atom\n          | binary\n\n  @type shared_opt ::\n          {:key_iterations, pos_integer}\n          | {:key_length, pos_integer}\n          | {:key_digest, :sha256 | :sha384 | :sha512}\n\n  @type max_age_opt :: {:max_age, pos_integer | :infinity}\n  @type signed_at_opt :: {:signed_at, pos_integer}\n\n  @doc \"\"\"\n  Encodes and signs data into a token you can send to clients.\n\n  ## Options\n\n    * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 1000\n    * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 32\n    * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to `:sha256`\n    * `:signed_at` - set the timestamp of the token in *seconds*.\n      If no value is provided, it will be set to the current time in milliseconds.\n    * `:max_age` - the default maximum age in **seconds** of the token. Defaults to\n      86400 seconds (1 day) and it may be overridden on `verify/4`.\n\n  \"\"\"\n  @spec sign(context, binary, term, [shared_opt | max_age_opt | signed_at_opt]) :: binary\n  def sign(context, salt, data, opts \\\\ []) when is_binary(salt) do\n    context\n    |> get_key_base()\n    |> Plug.Crypto.sign(salt, data, opts)\n  end\n\n  @doc \"\"\"\n  Encodes, encrypts, and signs data into a token you can send to\n  clients. Its usage is identical to that of `sign/4`, but the data\n  is extracted using `decrypt/4`, rather than `verify/4`.\n\n  ## Options\n\n    * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 1000\n    * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 32\n    * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to `:sha256`\n    * `:signed_at` - set the timestamp of the token in **seconds**.\n      If no value is provided, it will be set to the current time in milliseconds.\n    * `:max_age` - the default maximum age in **seconds** of the token. Defaults to\n      86400 seconds (1 day) and it may be overridden on `decrypt/4`.\n\n  \"\"\"\n  @spec encrypt(context, binary, term, [shared_opt | max_age_opt | signed_at_opt]) :: binary\n  def encrypt(context, secret, data, opts \\\\ []) when is_binary(secret) do\n    context\n    |> get_key_base()\n    |> Plug.Crypto.encrypt(secret, data, opts)\n  end\n\n  @doc \"\"\"\n  Decodes the original data from the token and verifies its integrity.\n\n  ## Examples\n\n  In this scenario we will create a token, sign it, then provide it to a client\n  application. The client will then use this token to authenticate requests for\n  resources from the server. See `Phoenix.Token` summary for more info about\n  creating tokens.\n\n      iex> user_id    = 99\n      iex> secret     = \"kjoy3o1zeidquwy1398juxzldjlksahdk3\"\n      iex> namespace  = \"user auth\"\n      iex> token      = Phoenix.Token.sign(secret, namespace, user_id)\n\n  The mechanism for passing the token to the client is typically through a\n  cookie, a JSON response body, or HTTP header. For now, assume the client has\n  received a token it can use to validate requests for protected resources.\n\n  When the server receives a request, it can use `verify/4` to determine if it\n  should provide the requested resources to the client:\n\n      iex> Phoenix.Token.verify(secret, namespace, token, max_age: 86400)\n      {:ok, 99}\n\n  In this example, we know the client sent a valid token because `verify/4`\n  returned a tuple of type `{:ok, user_id}`. The server can now proceed with\n  the request.\n\n  However, if the client had sent an expired token, an invalid token, or `nil`,\n  `verify/4` would have returned an error instead:\n\n      iex> Phoenix.Token.verify(secret, namespace, expired, max_age: 86400)\n      {:error, :expired}\n\n      iex> Phoenix.Token.verify(secret, namespace, invalid, max_age: 86400)\n      {:error, :invalid}\n\n      iex> Phoenix.Token.verify(secret, namespace, nil, max_age: 86400)\n      {:error, :missing}\n\n  ## Options\n\n    * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 1000\n    * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 32\n    * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to `:sha256`\n    * `:max_age` - verifies the token only if it has been generated\n      \"max age\" ago in seconds. Defaults to the max age signed in the\n      token by `sign/4`.\n  \"\"\"\n  @spec verify(context, binary, binary, [shared_opt | max_age_opt]) ::\n          {:ok, term} | {:error, :expired | :invalid | :missing}\n  def verify(context, salt, token, opts \\\\ []) when is_binary(salt) do\n    context\n    |> get_key_base()\n    |> Plug.Crypto.verify(salt, token, opts)\n  end\n\n  @doc \"\"\"\n  Decrypts the original data from the token and verifies its integrity.\n\n  Its usage is identical to `verify/4` but for encrypted tokens.\n\n  ## Options\n\n    * `:key_iterations` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 1000\n    * `:key_length` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to 32\n    * `:key_digest` - option passed to `Plug.Crypto.KeyGenerator`\n      when generating the encryption and signing keys. Defaults to `:sha256`\n    * `:max_age` - verifies the token only if it has been generated\n      \"max age\" ago in seconds. Defaults to the max age signed in the\n      token by `encrypt/4`.\n  \"\"\"\n  @spec decrypt(context, binary, binary, [shared_opt | max_age_opt]) :: term()\n  def decrypt(context, secret, token, opts \\\\ []) when is_binary(secret) do\n    context\n    |> get_key_base()\n    |> Plug.Crypto.decrypt(secret, token, opts)\n  end\n\n  ## Helpers\n\n  defp get_key_base(%Plug.Conn{} = conn),\n    do: conn |> Phoenix.Controller.endpoint_module() |> get_endpoint_key_base()\n\n  defp get_key_base(%_{endpoint: endpoint}),\n    do: get_endpoint_key_base(endpoint)\n\n  defp get_key_base(endpoint) when is_atom(endpoint),\n    do: get_endpoint_key_base(endpoint)\n\n  defp get_key_base(string) when is_binary(string) and byte_size(string) >= 20,\n    do: string\n\n  defp get_endpoint_key_base(endpoint) do\n    endpoint.config(:secret_key_base) ||\n      raise \"\"\"\n      no :secret_key_base configuration found in #{inspect(endpoint)}.\n      Ensure your environment has the necessary mix configuration. For example:\n\n          config :my_app, MyAppWeb.Endpoint,\n              secret_key_base: ...\n\n      \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/transports/long_poll.ex",
    "content": "defmodule Phoenix.Transports.LongPoll do\n  @moduledoc false\n  @behaviour Plug\n\n  # 10MB\n  @max_base64_size 10_000_000\n  @connect_info_opts [:check_csrf]\n\n  import Plug.Conn\n  alias Phoenix.Socket.{V1, V2, Transport}\n\n  def default_config() do\n    [\n      window_ms: 10_000,\n      path: \"/longpoll\",\n      pubsub_timeout_ms: 2_000,\n      serializer: [{V1.JSONSerializer, \"~> 1.0.0\"}, {V2.JSONSerializer, \"~> 2.0.0\"}],\n      transport_log: false,\n      crypto: [max_age: 1_209_600]\n    ]\n  end\n\n  def init(opts), do: opts\n\n  def call(conn, {endpoint, handler, opts}) do\n    conn\n    |> fetch_query_params()\n    |> put_resp_header(\"access-control-allow-origin\", \"*\")\n    |> Transport.code_reload(endpoint, opts)\n    |> Transport.transport_log(opts[:transport_log])\n    |> Transport.check_origin(handler, endpoint, opts, &status_json/1)\n    |> dispatch(endpoint, handler, opts)\n  end\n\n  defp dispatch(%{halted: true} = conn, _, _, _) do\n    conn\n  end\n\n  # Responds to pre-flight CORS requests with Allow-Origin-* headers.\n  # We allow cross-origin requests as we always validate the Origin header.\n  defp dispatch(%{method: \"OPTIONS\"} = conn, _, _, _) do\n    headers = get_req_header(conn, \"access-control-request-headers\") |> Enum.join(\", \")\n\n    conn\n    |> put_resp_header(\"access-control-allow-headers\", headers)\n    |> put_resp_header(\"access-control-allow-methods\", \"get, post, options\")\n    |> put_resp_header(\"access-control-max-age\", \"3600\")\n    |> send_resp(:ok, \"\")\n  end\n\n  # Starts a new session or listen to a message if one already exists.\n  defp dispatch(%{method: \"GET\"} = conn, endpoint, handler, opts) do\n    case resume_session(conn, conn.params, endpoint, opts) do\n      {:ok, new_conn, server_ref} ->\n        listen(new_conn, server_ref, endpoint, opts)\n\n      :error ->\n        new_session(conn, endpoint, handler, opts)\n    end\n  end\n\n  # Publish the message.\n  defp dispatch(%{method: \"POST\"} = conn, endpoint, _, opts) do\n    case resume_session(conn, conn.params, endpoint, opts) do\n      {:ok, new_conn, server_ref} ->\n        publish(new_conn, server_ref, endpoint, opts)\n\n      :error ->\n        conn |> put_status(:gone) |> status_json()\n    end\n  end\n\n  # All other requests should fail.\n  defp dispatch(conn, _, _, _) do\n    send_resp(conn, :bad_request, \"\")\n  end\n\n  defp publish(conn, server_ref, endpoint, opts) do\n    case read_body(conn, []) do\n      {:ok, body, conn} ->\n        # we need to match on both v1 and v2 protocol, as well as wrap for backwards compat\n        batch =\n          case get_req_header(conn, \"content-type\") do\n            [\"application/x-ndjson\"] ->\n              body\n              |> String.split([\"\\n\", \"\\r\\n\"])\n              |> Enum.map(fn\n                \"[\" <> _ = txt -> {txt, :text}\n                base64 -> {safe_decode64!(base64), :binary}\n              end)\n\n            _ ->\n              [{body, :text}]\n          end\n\n        {conn, status} =\n          Enum.reduce_while(batch, {conn, nil}, fn msg, {conn, _status} ->\n            case transport_dispatch(endpoint, server_ref, msg, opts) do\n              :ok -> {:cont, {conn, :ok}}\n              :request_timeout = timeout -> {:halt, {conn, timeout}}\n            end\n          end)\n\n        conn |> put_status(status) |> status_json()\n\n      _ ->\n        raise Plug.BadRequestError\n    end\n  end\n\n  defp safe_decode64!(base64) do\n    if byte_size(base64) <= @max_base64_size do\n      Base.decode64!(base64)\n    else\n      raise Plug.BadRequestError\n    end\n  end\n\n  defp transport_dispatch(endpoint, server_ref, body, opts) do\n    ref = make_ref()\n    broadcast_from!(endpoint, server_ref, {:dispatch, client_ref(server_ref), body, ref})\n\n    receive do\n      {:ok, ^ref} -> :ok\n      {:error, ^ref} -> :ok\n    after\n      opts[:window_ms] -> :request_timeout\n    end\n  end\n\n  ## Session handling\n\n  defp new_session(conn, endpoint, handler, opts) do\n    priv_topic =\n      \"phx:lp:\" <>\n        Base.encode64(:crypto.strong_rand_bytes(16)) <>\n        (System.system_time(:millisecond) |> Integer.to_string())\n\n    keys = Keyword.get(opts, :connect_info, [])\n\n    conn = maybe_auth_token_from_header(conn, opts[:auth_token])\n\n    connect_info =\n      Transport.connect_info(conn, endpoint, keys, Keyword.take(opts, @connect_info_opts))\n\n    arg = {endpoint, handler, opts, conn.params, priv_topic, connect_info}\n    spec = {Phoenix.Transports.LongPoll.Server, arg}\n\n    case DynamicSupervisor.start_child(Phoenix.Transports.LongPoll.Supervisor, spec) do\n      :ignore ->\n        conn |> put_status(:forbidden) |> status_json()\n\n      {:ok, server_pid} ->\n        data = {:v1, endpoint.config(:endpoint_id), server_pid, priv_topic}\n        token = sign_token(endpoint, data, opts)\n        conn |> put_status(:gone) |> status_token_messages_json(token, [])\n    end\n  end\n\n  defp listen(conn, server_ref, endpoint, opts) do\n    ref = make_ref()\n    client_ref = client_ref(server_ref)\n    broadcast_from!(endpoint, server_ref, {:flush, client_ref, ref})\n\n    {status, messages} =\n      receive do\n        {:messages, messages, ^ref} ->\n          {:ok, messages}\n\n        {:now_available, ^ref} ->\n          broadcast_from!(endpoint, server_ref, {:flush, client_ref, ref})\n\n          receive do\n            {:messages, messages, ^ref} -> {:ok, messages}\n          after\n            opts[:window_ms] ->\n              broadcast_from!(endpoint, server_ref, {:expired, client_ref, ref})\n              {:no_content, []}\n          end\n      after\n        opts[:window_ms] ->\n          broadcast_from!(endpoint, server_ref, {:expired, client_ref, ref})\n          {:no_content, []}\n      end\n\n    conn\n    |> put_status(status)\n    |> status_token_messages_json(conn.params[\"token\"], messages)\n  end\n\n  # Retrieves the serialized `Phoenix.LongPoll.Server` pid\n  # by publishing a message in the encrypted private topic.\n  defp resume_session(%Plug.Conn{} = conn, %{\"token\" => token}, endpoint, opts) do\n    case verify_token(endpoint, token, opts) do\n      {:ok, {:v1, id, pid, priv_topic}} ->\n        server_ref = server_ref(endpoint.config(:endpoint_id), id, pid, priv_topic)\n\n        new_conn =\n          Plug.Conn.register_before_send(conn, fn conn ->\n            unsubscribe(endpoint, server_ref)\n            conn\n          end)\n\n        ref = make_ref()\n        :ok = subscribe(endpoint, server_ref)\n        broadcast_from!(endpoint, server_ref, {:subscribe, client_ref(server_ref), ref})\n\n        receive do\n          {:subscribe, ^ref} -> {:ok, new_conn, server_ref}\n        after\n          opts[:pubsub_timeout_ms] -> :error\n        end\n\n      _ ->\n        :error\n    end\n  end\n\n  defp resume_session(%Plug.Conn{}, _params, _endpoint, _opts), do: :error\n\n  ## Helpers\n\n  defp server_ref(endpoint_id, id, pid, topic) when is_pid(pid) do\n    cond do\n      node(pid) in Node.list() -> pid\n      endpoint_id == id and Process.alive?(pid) -> pid\n      true -> topic\n    end\n  end\n\n  defp client_ref(topic) when is_binary(topic), do: topic\n  defp client_ref(pid) when is_pid(pid), do: self()\n\n  defp subscribe(endpoint, topic) when is_binary(topic),\n    do: Phoenix.PubSub.subscribe(endpoint.config(:pubsub_server), topic)\n\n  defp subscribe(_endpoint, pid) when is_pid(pid),\n    do: :ok\n\n  defp unsubscribe(endpoint, topic) when is_binary(topic),\n    do: Phoenix.PubSub.unsubscribe(endpoint.config(:pubsub_server), topic)\n\n  defp unsubscribe(_endpoint, pid) when is_pid(pid),\n    do: :ok\n\n  defp broadcast_from!(endpoint, topic, msg) when is_binary(topic),\n    do: Phoenix.PubSub.broadcast_from!(endpoint.config(:pubsub_server), self(), topic, msg)\n\n  defp broadcast_from!(_endpoint, pid, msg) when is_pid(pid),\n    do: send(pid, msg)\n\n  defp sign_token(endpoint, data, opts) do\n    Phoenix.Token.sign(\n      endpoint,\n      Atom.to_string(endpoint.config(:pubsub_server)),\n      data,\n      opts[:crypto]\n    )\n  end\n\n  defp verify_token(endpoint, signed, opts) do\n    Phoenix.Token.verify(\n      endpoint,\n      Atom.to_string(endpoint.config(:pubsub_server)),\n      signed,\n      opts[:crypto]\n    )\n  end\n\n  defp maybe_auth_token_from_header(conn, true) do\n    case Plug.Conn.get_req_header(conn, \"x-phoenix-authtoken\") do\n      [] ->\n        conn\n\n      [token | _] ->\n        Plug.Conn.put_private(conn, :phoenix_transport_auth_token, token)\n    end\n  end\n\n  defp maybe_auth_token_from_header(conn, _), do: conn\n\n  defp status_json(conn) do\n    send_json(conn, %{\"status\" => conn.status || 200})\n  end\n\n  defp status_token_messages_json(conn, token, messages) do\n    send_json(conn, %{\"status\" => conn.status || 200, \"token\" => token, \"messages\" => messages})\n  end\n\n  defp send_json(conn, data) do\n    conn\n    |> put_resp_header(\"content-type\", \"application/json; charset=utf-8\")\n    |> send_resp(200, Phoenix.json_library().encode_to_iodata!(data))\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/transports/long_poll_server.ex",
    "content": "defmodule Phoenix.Transports.LongPoll.Server do\n  @moduledoc false\n\n  use GenServer, restart: :temporary\n  alias Phoenix.PubSub\n\n  def start_link(arg) do\n    GenServer.start_link(__MODULE__, arg)\n  end\n\n  def init({endpoint, handler, options, params, priv_topic, connect_info}) do\n    config = %{\n      endpoint: endpoint,\n      transport: :longpoll,\n      options: options,\n      params: params,\n      connect_info: connect_info\n    }\n\n    window_ms = Keyword.fetch!(options, :window_ms)\n\n    case handler.connect(config) do\n      {:ok, handler_state} ->\n        {:ok, handler_state} = handler.init(handler_state)\n\n        state = %{\n          buffer: [],\n          handler: {handler, handler_state},\n          window_ms: trunc(window_ms * 1.5),\n          pubsub_server: endpoint.config(:pubsub_server),\n          priv_topic: priv_topic,\n          last_client_poll: now_ms(),\n          client_ref: nil\n        }\n\n        :ok = PubSub.subscribe(state.pubsub_server, priv_topic)\n        schedule_inactive_shutdown(state.window_ms)\n        {:ok, state}\n\n      :error ->\n        :ignore\n\n      {:error, _reason} ->\n        :ignore\n    end\n  end\n\n  def handle_info({:dispatch, client_ref, {body, opcode}, ref}, state) do\n    %{handler: {handler, handler_state}} = state\n\n    case handler.handle_in({body, opcode: opcode}, handler_state) do\n      {:reply, status, {_, reply}, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        status = if status == :ok, do: :ok, else: :error\n        broadcast_from!(state, client_ref, {status, ref})\n        publish_reply(state, reply)\n\n      {:ok, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        broadcast_from!(state, client_ref, {:ok, ref})\n        {:noreply, state}\n\n      {:stop, reason, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        broadcast_from!(state, client_ref, {:error, ref})\n        {:stop, reason, state}\n    end\n  end\n\n  def handle_info({:subscribe, client_ref, ref}, state) do\n    broadcast_from!(state, client_ref, {:subscribe, ref})\n    {:noreply, state}\n  end\n\n  def handle_info({:flush, client_ref, ref}, state) do\n    case state.buffer do\n      [] ->\n        {:noreply, %{state | client_ref: {client_ref, ref}, last_client_poll: now_ms()}}\n\n      buffer ->\n        broadcast_from!(state, client_ref, {:messages, Enum.reverse(buffer), ref})\n        {:noreply, %{state | client_ref: nil, last_client_poll: now_ms(), buffer: []}}\n    end\n  end\n\n  def handle_info({:expired, client_ref, ref}, state) do\n    case state.client_ref do\n      {^client_ref, ^ref} ->\n        {:noreply, %{state | client_ref: nil}}\n\n      _ ->\n        {:noreply, state}\n    end\n  end\n\n  def handle_info(:shutdown_if_inactive, state) do\n    if now_ms() - state.last_client_poll > state.window_ms do\n      {:stop, {:shutdown, :inactive}, state}\n    else\n      schedule_inactive_shutdown(state.window_ms)\n      {:noreply, state}\n    end\n  end\n\n  def handle_info(message, state) do\n    %{handler: {handler, handler_state}} = state\n\n    case handler.handle_info(message, handler_state) do\n      {:push, {_, reply}, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        publish_reply(state, reply)\n\n      {:ok, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        {:noreply, state}\n\n      {:stop, reason, handler_state} ->\n        state = %{state | handler: {handler, handler_state}}\n        {:stop, reason, state}\n    end\n  end\n\n  def terminate(reason, state) do\n    %{handler: {handler, handler_state}} = state\n    handler.terminate(reason, handler_state)\n    :ok\n  end\n\n  defp broadcast_from!(state, client_ref, msg) when is_binary(client_ref),\n    do: PubSub.broadcast_from!(state.pubsub_server, self(), client_ref, msg)\n\n  defp broadcast_from!(_state, client_ref, msg) when is_pid(client_ref),\n    do: send(client_ref, msg)\n\n  defp publish_reply(state, reply) when is_map(reply) do\n    IO.warn(\n      \"Returning a map from the LongPolling serializer is deprecated. \" <>\n        \"Please return JSON encoded data instead (see Phoenix.Socket.Serializer)\"\n    )\n\n    publish_reply(state, Phoenix.json_library().encode_to_iodata!(reply))\n  end\n\n  defp publish_reply(state, reply) do\n    notify_client_now_available(state)\n    {:noreply, update_in(state.buffer, &[IO.iodata_to_binary(reply) | &1])}\n  end\n\n  defp notify_client_now_available(state) do\n    case state.client_ref do\n      {client_ref, ref} -> broadcast_from!(state, client_ref, {:now_available, ref})\n      nil -> :ok\n    end\n  end\n\n  defp now_ms, do: System.system_time(:millisecond)\n\n  defp schedule_inactive_shutdown(window_ms) do\n    Process.send_after(self(), :shutdown_if_inactive, window_ms)\n  end\nend\n"
  },
  {
    "path": "lib/phoenix/transports/websocket.ex",
    "content": "defmodule Phoenix.Transports.WebSocket do\n  @moduledoc false\n  #\n  # How WebSockets Work In Phoenix\n  #\n  # WebSocket support in Phoenix is implemented on top of the `WebSockAdapter` library. Upgrade\n  # requests from clients originate as regular HTTP requests that get routed to this module via\n  # Plug. These requests are then upgraded to WebSocket connections via\n  # `WebSockAdapter.upgrade/4`, which takes as an argument the handler for a given socket endpoint\n  # as configured in the application's Endpoint. This handler module must implement the\n  # transport-agnostic `Phoenix.Socket.Transport` behaviour (this same behaviour is also used for\n  # other transports such as long polling). Because this behaviour is a superset of the `WebSock`\n  # behaviour, the `WebSock` library is able to use the callbacks in the `WebSock` behaviour to\n  # call this handler module directly for the rest of the WebSocket connection's lifetime.\n  #\n  @behaviour Plug\n\n  @connect_info_opts [:check_csrf]\n\n  @auth_token_prefix \"base64url.bearer.phx.\"\n\n  import Plug.Conn\n\n  alias Phoenix.Socket.{V1, V2, Transport}\n\n  def default_config() do\n    [\n      path: \"/websocket\",\n      serializer: [{V1.JSONSerializer, \"~> 1.0.0\"}, {V2.JSONSerializer, \"~> 2.0.0\"}],\n      error_handler: {__MODULE__, :handle_error, []},\n      timeout: 60_000,\n      transport_log: false,\n      compress: false\n    ]\n  end\n\n  def init(opts), do: opts\n\n  def call(%{method: \"GET\"} = conn, {endpoint, handler, opts}) do\n    subprotocols =\n      if opts[:auth_token] do\n        # when using Sec-WebSocket-Protocol for passing an auth token\n        # the server must reply with one of the subprotocols in the request;\n        # therefore we include \"phoenix\" as allowed subprotocol and include it on the client\n        [\"phoenix\" | Keyword.get(opts, :subprotocols, [])]\n      else\n        opts[:subprotocols]\n      end\n\n    conn\n    |> fetch_query_params()\n    |> Transport.code_reload(endpoint, opts)\n    |> Transport.transport_log(opts[:transport_log])\n    |> Transport.check_origin(handler, endpoint, opts)\n    |> maybe_auth_token_from_header(opts[:auth_token])\n    |> Transport.check_subprotocols(subprotocols)\n    |> case do\n      %{halted: true} = conn ->\n        conn\n\n      %{params: params} = conn ->\n        keys = Keyword.get(opts, :connect_info, [])\n\n        connect_info =\n          Transport.connect_info(conn, endpoint, keys, Keyword.take(opts, @connect_info_opts))\n\n        config = %{\n          endpoint: endpoint,\n          transport: :websocket,\n          options: opts,\n          params: params,\n          connect_info: connect_info\n        }\n\n        case handler.connect(config) do\n          {:ok, arg} ->\n            try do\n              conn\n              |> WebSockAdapter.upgrade(handler, arg, opts)\n              |> halt()\n            rescue\n              e in WebSockAdapter.UpgradeError -> send_resp(conn, 400, e.message)\n            end\n\n          :error ->\n            send_resp(conn, 403, \"\")\n\n          {:error, reason} ->\n            {m, f, args} = opts[:error_handler]\n            apply(m, f, [conn, reason | args])\n        end\n    end\n  end\n\n  def call(conn, _), do: send_resp(conn, 400, \"\")\n\n  def handle_error(conn, _reason), do: send_resp(conn, 403, \"\")\n\n  defp maybe_auth_token_from_header(conn, true) do\n    case get_req_header(conn, \"sec-websocket-protocol\") do\n      [] ->\n        conn\n\n      [subprotocols_header | _] ->\n        request_subprotocols =\n          subprotocols_header\n          |> Plug.Conn.Utils.list()\n          |> Enum.split_with(&String.starts_with?(&1, @auth_token_prefix))\n\n        case request_subprotocols do\n          {[@auth_token_prefix <> encoded_token], actual_subprotocols} ->\n            token = Base.decode64!(encoded_token, padding: false)\n\n            conn\n            |> put_private(:phoenix_transport_auth_token, token)\n            |> set_actual_subprotocols(actual_subprotocols)\n\n          _ ->\n            conn\n        end\n    end\n  end\n\n  defp maybe_auth_token_from_header(conn, _), do: conn\n\n  defp set_actual_subprotocols(conn, []), do: delete_req_header(conn, \"sec-websocket-protocol\")\n\n  defp set_actual_subprotocols(conn, subprotocols),\n    do: put_req_header(conn, \"sec-websocket-protocol\", Enum.join(subprotocols, \", \"))\nend\n"
  },
  {
    "path": "lib/phoenix/verified_routes.ex",
    "content": "defmodule Phoenix.VerifiedRoutes do\n  @moduledoc ~S'''\n  Provides route generation with compile-time verification.\n\n  Use of the `sigil_p` macro allows paths and URLs throughout your\n  application to be compile-time verified against your Phoenix router(s).\n  For example, the following path and URL usages:\n\n      ~H\"\"\"\n      <a href={~p\"/sessions/new\"}>Log in</a>\n      \"\"\"\n\n      redirect(to: url(~p\"/posts/#{post}\"))\n\n  Will be verified against your standard `Phoenix.Router` definitions:\n\n      get \"/posts/:post_id\", PostController, :show\n      post \"/sessions/new\", SessionController, :create\n\n  Unmatched routes will issue compiler warnings:\n\n  ```console\n  warning: no route path for AppWeb.Router matches \"/postz/#{post}\"\n    lib/app_web/controllers/post_controller.ex:100: AppWeb.PostController.show/2\n  ```\n\n  Additionally, interpolated ~p values are encoded via the `Phoenix.Param` protocol.\n  For example, a `%Post{}` struct in your application may derive the `Phoenix.Param`\n  protocol to generate slug-based paths rather than ID based ones. This allows you to\n  use `~p\"/posts/#{post}\"` rather than `~p\"/posts/#{post.slug}\"` throughout your\n  application. See the `Phoenix.Param` documentation for more details.\n\n  Finally, query strings are also supported in verified routes, either in traditional form:\n\n      ~p\"/posts?page=#{page}\"\n\n  Or as a keyword list or map of values:\n\n      params = %{page: 1, direction: \"asc\"}\n      ~p\"/posts?#{params}\"\n\n  Like path segments, query strings params are proper URL encoded and may be interpolated\n  directly into the ~p string.\n\n  To ease url comparisons during tests (e.g. when using `assert_redirect/3`) query params\n  will be sorted. This is controlled by the `phoenix: [sort_verified_routes_query_params: true]`\n  configuration option.\n\n  ## What about named routes?\n\n  Many web frameworks, and early versions of Phoenix, provided a feature called \"named routes\".\n  The idea is that, when you define routes in your web applications, you could give them names\n  too. In Phoenix that was done as follows:\n\n      get \"/login\", SessionController, :create, as: :login\n\n  And now you could generate the route using the `login_path` function.\n\n  Named routes exist to avoid hardcoding routes in your templates, if you wrote `<a href=\"/login\">`\n  and then changed your router, the link would point to a page that no longer exist. By using\n  `login_path`, we make sure it always points to a valid URL in our router. However, named routes\n  come with the downsides of indirection: when you look at the code, it is not immediately clear\n  which URL will be generated. Furthermore, if you have an existing URL and you want to add it\n  to a template, you need to do a reverse lookup and find its name in the router. At the end of\n  the day, named routes are arbitrary names that need to be memorized by developers, adding\n  cognitive overhead.\n\n  Verified routes tackle this problem by allowing the routes to be written as we would read them\n  in a browser, but using the `~p` sigil to guarantee they actually exist at compilation time.\n  They remove the indirection of named routes while keeping their guarantees.\n\n  In any case, if part of your application requires features similar to named routes, then\n  remember you can still leverage Elixir features to achieve the same result. For example,\n  you can define several functions as named routes to be reused across modules:\n\n      def login_path, do: ~p\"/login\"\n      def user_home_path(user), do: ~p\"/users/#{user.username}\"\n\n  ## Options\n\n  To verify routes in your application modules, such as controller, templates, and views,\n  `use Phoenix.VerifiedRoutes`, which supports the following options:\n\n    * `:router` - The required router to verify `~p` paths against\n    * `:endpoint` - Optional endpoint for URL generation\n    * `:statics` - Optional list of static directories to treat as verified paths\n    * `:path_prefixes` - Optional list of path prefixes to be added to every generated path.\n      See \"Path prefixes\" for more information\n\n  For example:\n\n      use Phoenix.VerifiedRoutes,\n        router: AppWeb.Router,\n        endpoint: AppWeb.Endpoint,\n        statics: ~w(images)\n\n  ## Connection/socket-based route generation\n\n  The majority of path and URL generation needs your application will be met\n  with `~p` and `url/1`, where all information necessary to construct the path\n  or URL is provided by the compile-time information stored in the Endpoint\n  and Router passed to `use Phoenix.VerifiedRoutes`.\n\n  That said, there are some circumstances where `path/2`, `path/3`, `url/2`, and `url/3`\n  are required:\n\n    * When the runtime values of the `%Plug.Conn{}`, `%Phoenix.LiveSocket{}`, or a `%URI{}`\n      dictate the formation of the path or URL, which happens under the following scenarios:\n\n      - `Phoenix.Controller.put_router_url/2` is used to override the endpoint's URL\n      - `Phoenix.Controller.put_static_url/2` is used to override the endpoint's static URL\n\n    * When the Router module differs from the one passed to `use Phoenix.VerifiedRoutes`,\n      such as library code, or application code that relies on multiple routers. In such cases,\n      the router module can be provided explicitly to `path/3` and `url/3`.\n\n  ## Tracking warnings\n\n  All static path segments must start with forward slash, and you must have a static segment\n  between dynamic interpolations in order for a route to be verified without warnings.\n  For example, imagine you have these two routes:\n\n      get \"/media/posts/:id\"\n      get \"/media/images/:id\"\n\n  The following route will be verified and emit a warning as it does not match the router:\n\n      ~p\"/media/post/#{post}\"\n\n  However the one below will not, the \"post\" segment is dynamic:\n\n      type = \"post\"\n      ~p\"/media/#{type}/#{post}\"\n\n  If you find yourself needing to generate dynamic URLs which are defined statically\n  in the router, that's a good indicator you should refactor it into one or more\n  function, such as `posts_path/1` and `images_path/1`.\n\n  Like any other compilation warning, the Elixir compiler will warn any time the file\n  that a `~p` resides in changes, or if the router is changed.\n\n  ## Localized routes and path prefixes\n\n  Applications that need to support internationalization (i18n) and localization (l10n)\n  often do so at the URL level. In such cases, there are different approaches one can\n  choose.\n\n  One option is to perform i18n at the domain level. You can have `example.com` (in which\n  you would detect the locale based on the \"Accept-Language\" HTTP header), `en.example.com`,\n  `en-GB.example.com` and so forth. In this case, you would have a plug that looks at the\n  host and at HTTP headers and calls `Gettext.get_locale/1` accordingly. The biggest benefit\n  of this approach is that you don't have to change the routes in your application and\n  verified routes works as is.\n\n  Some applications, however, like to add the locale as part of the URL prefix:\n\n      scope \"/:locale\" do\n        get \"/posts\"\n        get \"/images\"\n      end\n\n  For such cases, VerifiedRoutes allow you to configure a `path_prefixes` option, which\n  is a list of segments to prepend to the URL. For example:\n\n      use Phoenix.VerifiedRoutes,\n        router: AppWeb.Router,\n        endpoint: AppWeb.Endpoint,\n        path_prefixes: [{Gettext, :get_locale, []}]\n\n  The above will prepend `\"/#{Gettext.get_locale()}\"` to every path and url generated with\n  `~p`. If your website has a handful of URLs that do not require the locale prefix, then\n  we suggest defining them in a separate module, where you use `Phoenix.VerifiedRoutes`\n  without the prefix option:\n\n      defmodule UnlocalizedRoutes do\n        use Phoenix.VerifiedRoutes,\n          router: AppWeb.Router,\n          endpoint: AppWeb.Endpoint\n\n        # Since :path_prefixes was not declared,\n        # the code below won't prepend the locale and still be verified\n        def root, do: ~p\"/\"\n      end\n\n  Finally, for even more complex use cases, where the whole URL needs to localized,\n  see projects such as [`routex`](https://hex.pm/packages/routex) and\n  [`ex_cldr_routes`](https://hex.pm/packages/ex_cldr_routes).\n\n  ## Usage with custom plugs\n\n  Sometimes, when we want to do dynamic routing, we will forward to custom plugs.\n  It is possible to make these dynamic routers support `mix phx.routes` and verified\n  routes at compile time by adopting the `Phoenix.VerifiedRoutes` behaviour.\n  For example:\n\n      defmodule MyApp.LocaleRouter do\n        use Plug.Router\n        @behaviour Phoenix.VerifiedRoutes\n\n        # custom routing rules\n\n        # for displaying in `mix phx.routes`\n        def formatted_routes(plug_opts) do\n          for locale <- supported_locales(plug_opts) do\n            %{verb: \"GET\", path: \"/#{locale}/*subpath\"}\n          end\n        end\n\n        def verified_route?(plug_opts, path) do\n          plug_opts\n          |> supported_locales()\n          |> Enum.any?(fn locale ->\n            Enum.at(path, 0) == locale\n          end)\n        end\n      end\n  '''\n  @doc false\n  defstruct router: nil,\n            route: nil,\n            inspected_route: nil,\n            warn_location: nil,\n            test_path: nil\n\n  defmacro __using__(opts) do\n    opts =\n      if Keyword.keyword?(opts) do\n        for {k, v} <- opts do\n          if Macro.quoted_literal?(v) do\n            {k, Macro.prewalk(v, &expand_alias(&1, __CALLER__))}\n          else\n            {k, v}\n          end\n        end\n      else\n        opts\n      end\n\n    quote do\n      unquote(__MODULE__).__using__(__MODULE__, unquote(opts))\n      import unquote(__MODULE__)\n    end\n  end\n\n  @doc false\n  def __using__(mod, opts) do\n    Module.register_attribute(mod, :phoenix_verified_routes, accumulate: true)\n    Module.put_attribute(mod, :before_compile, __MODULE__)\n    Module.put_attribute(mod, :router, Keyword.fetch!(opts, :router))\n    Module.put_attribute(mod, :endpoint, Keyword.get(opts, :endpoint))\n\n    statics =\n      case Keyword.get(opts, :statics, []) do\n        list when is_list(list) -> list\n        other -> raise ArgumentError, \"expected statics to be a list, got: #{inspect(other)}\"\n      end\n\n    path_prefixes =\n      case Keyword.get(opts, :path_prefixes, []) do\n        list when is_list(list) ->\n          list\n\n        other ->\n          raise ArgumentError,\n                \"expected path_prefixes to be a list of zero-arity functions, got: #{inspect(other)}\"\n      end\n\n    if Module.get_attribute(mod, :phoenix_verified_config) do\n      raise \"duplicate call to \\\"use Phoenix.VerifiedRoutes\\\" found, make sure it is used only once per module\"\n    end\n\n    Module.put_attribute(mod, :phoenix_verified_config, %{\n      statics: statics,\n      path_prefixes: path_prefixes\n    })\n  end\n\n  @type plug_opts :: any()\n  @type formatted_route :: %{\n          required(:verb) => String.t(),\n          required(:path) => String.t(),\n          required(:label) => String.t()\n        }\n\n  @doc \"\"\"\n  Returns the necessary information about routes for display in `mix phx.routes`.\n\n  The `plug_opts` is typically only passed when the router is mounted within\n  a `Phoenix.Router`. Otherwise it defaults to `[]`.\n  \"\"\"\n  @callback formatted_routes(plug_opts()) :: [formatted_route()]\n\n  @doc \"\"\"\n  Returns `true` if the path is verified, and false if not.\n\n  The `plug_opts` is typically only passed when the router is mounted within\n  a `Phoenix.Router`. Otherwise it defaults to `[]`.\n  \"\"\"\n  @callback verified_route?(plug_opts(), [String.t()]) :: boolean()\n\n  defmacro __before_compile__(_env) do\n    quote do\n      @after_verify {__MODULE__, :__phoenix_verify_routes__}\n\n      @doc false\n      def __phoenix_verify_routes__(_module) do\n        unquote(__MODULE__).__verify__(@phoenix_verified_routes)\n      end\n    end\n  end\n\n  @doc false\n  def __verify__(routes) when is_list(routes) do\n    Enum.each(routes, fn %__MODULE__{} = route ->\n      test_path = split_test_path(route.test_path)\n\n      unless route.router.verified_route?([], test_path) do\n        IO.warn(\n          \"no route path for #{inspect(route.router)} matches #{route.inspected_route}\",\n          route.warn_location\n        )\n      end\n    end)\n  end\n\n  defp split_test_path(test_path) do\n    test_path\n    |> String.split(\"#\")\n    |> Enum.at(0)\n    |> String.split(\"/\")\n    |> Enum.filter(fn segment -> segment != \"\" end)\n    |> Enum.map(&URI.decode/1)\n  end\n\n  defp expand_alias({:__aliases__, _, _} = alias, env),\n    do: Macro.expand(alias, %{env | function: {:path, 2}})\n\n  defp expand_alias(other, _env), do: other\n\n  @doc ~S'''\n  Generates the router path with route verification.\n\n  Interpolated named parameters are encoded via the `Phoenix.Param` protocol.\n\n  Warns when the provided path does not match against the router specified\n  in `use Phoenix.VerifiedRoutes` or the `@router` module attribute.\n\n  ## Examples\n\n      use Phoenix.VerifiedRoutes, endpoint: MyAppWeb.Endpoint, router: MyAppWeb.Router\n\n      redirect(to: ~p\"/users/top\")\n\n      redirect(to: ~p\"/users/#{@user}\")\n\n      ~H\"\"\"\n      <.link href={~p\"/users?page=#{@page}\"}>profile</.link>\n\n      <.link href={~p\"/users?#{@params}\"}>profile</.link>\n      \"\"\"\n  '''\n  defmacro sigil_p({:<<>>, _meta, _segments} = route, extra) do\n    validate_sigil_p!(extra)\n    endpoint = attr!(__CALLER__, :endpoint)\n    router = attr!(__CALLER__, :router)\n\n    route\n    |> build_route(route, __CALLER__, endpoint, router)\n    |> inject_path(__CALLER__)\n  end\n\n  defp inject_path(\n         {%__MODULE__{} = route, static?, _endpoint_ctx, _route_ast, path_ast, static_ast},\n         env\n       ) do\n    if static? do\n      static_ast\n    else\n      Module.put_attribute(env.module, :phoenix_verified_routes, route)\n      path_ast\n    end\n  end\n\n  defp inject_url(\n         {%__MODULE__{} = route, static?, endpoint_ctx, route_ast, path_ast, _static_ast},\n         env\n       ) do\n    if static? do\n      quote do\n        unquote(__MODULE__).static_url(unquote_splicing([endpoint_ctx, route_ast]))\n      end\n    else\n      Module.put_attribute(env.module, :phoenix_verified_routes, route)\n\n      quote do\n        unquote(__MODULE__).unverified_url(unquote_splicing([endpoint_ctx, path_ast]))\n      end\n    end\n  end\n\n  defp validate_sigil_p!([]), do: :ok\n\n  defp validate_sigil_p!(extra) do\n    raise ArgumentError, \"~p does not support modifiers after closing, got: #{extra}\"\n  end\n\n  defp raise_invalid_route(ast) do\n    raise ArgumentError,\n          \"expected compile-time ~p path string, got: #{Macro.to_string(ast)}\\n\" <>\n            \"Use unverified_path/2 and unverified_url/2 if you need to build an arbitrary path.\"\n  end\n\n  @doc ~S'''\n  Generates the router path with route verification.\n\n  See `sigil_p/2` for more information.\n\n  Warns when the provided path does not match against the router specified\n  in the router argument.\n\n  ## Examples\n\n      import Phoenix.VerifiedRoutes\n\n      redirect(to: path(conn, MyAppWeb.Router, ~p\"/users/top\"))\n\n      redirect(to: path(conn, MyAppWeb.Router, ~p\"/users/#{@user}\"))\n\n      ~H\"\"\"\n      <.link href={path(@uri, MyAppWeb.Router, \"/users?page=#{@page}\")}>profile</.link>\n      <.link href={path(@uri, MyAppWeb.Router, \"/users?#{@params}\")}>profile</.link>\n      \"\"\"\n  '''\n  defmacro path(\n             conn_or_socket_or_endpoint_or_uri,\n             router,\n             {:sigil_p, _, [{:<<>>, _meta, _segments} = route, extra]} = sigil_p\n           ) do\n    validate_sigil_p!(extra)\n\n    route\n    |> build_route(sigil_p, __CALLER__, conn_or_socket_or_endpoint_or_uri, router)\n    |> inject_path(__CALLER__)\n  end\n\n  defmacro path(_endpoint, _router, other), do: raise_invalid_route(other)\n\n  @doc ~S'''\n  Generates the router path with route verification.\n\n  See `sigil_p/2` for more information.\n\n  Warns when the provided path does not match against the router specified\n  in `use Phoenix.VerifiedRoutes` or the `@router` module attribute.\n\n  ## Examples\n\n      import Phoenix.VerifiedRoutes\n\n      redirect(to: path(conn, ~p\"/users/top\"))\n\n      redirect(to: path(conn, ~p\"/users/#{@user}\"))\n\n      ~H\"\"\"\n      <.link href={path(@uri, \"/users?page=#{@page}\")}>profile</.link>\n      <.link href={path(@uri, \"/users?#{@params}\")}>profile</.link>\n      \"\"\"\n  '''\n  defmacro path(\n             conn_or_socket_or_endpoint_or_uri,\n             {:sigil_p, _, [{:<<>>, _meta, _segments} = route, extra]} = sigil_p\n           ) do\n    validate_sigil_p!(extra)\n    router = attr!(__CALLER__, :router)\n\n    route\n    |> build_route(sigil_p, __CALLER__, conn_or_socket_or_endpoint_or_uri, router)\n    |> inject_path(__CALLER__)\n  end\n\n  defmacro path(_conn_or_socket_or_endpoint_or_uri, other), do: raise_invalid_route(other)\n\n  @doc ~S'''\n  Generates the router url with route verification.\n\n  See `sigil_p/2` for more information.\n\n  Warns when the provided path does not match against the router specified\n  in `use Phoenix.VerifiedRoutes` or the `@router` module attribute.\n\n  ## Examples\n\n      use Phoenix.VerifiedRoutes, endpoint: MyAppWeb.Endpoint, router: MyAppWeb.Router\n\n      redirect(to: url(conn, ~p\"/users/top\"))\n\n      redirect(to: url(conn, ~p\"/users/#{@user}\"))\n\n      ~H\"\"\"\n      <.link href={url(@uri, \"/users?#{[page: @page]}\")}>profile</.link>\n      \"\"\"\n\n  The router may also be provided in cases where you want to verify routes for a\n  router other than the one passed to `use Phoenix.VerifiedRoutes`:\n\n      redirect(to: url(conn, OtherRouter, ~p\"/users\"))\n\n  Forwarded routes are also resolved automatically. For example, imagine you\n  have a forward path to an admin router in your main router:\n\n      defmodule AppWeb.Router do\n        ...\n        forward \"/admin\", AppWeb.AdminRouter\n      end\n\n      defmodule AppWeb.AdminRouter do\n        ...\n        get \"/users\", AppWeb.Admin.UserController\n      end\n\n  Forwarded paths in your main application router will be verified as usual,\n  such as `~p\"/admin/users\"`.\n  '''\n  defmacro url({:sigil_p, _, [{:<<>>, _meta, _segments} = route, _]} = sigil_p) do\n    endpoint = attr!(__CALLER__, :endpoint)\n    router = attr!(__CALLER__, :router)\n\n    route\n    |> build_route(sigil_p, __CALLER__, endpoint, router)\n    |> inject_url(__CALLER__)\n  end\n\n  defmacro url(other), do: raise_invalid_route(other)\n\n  @doc \"\"\"\n  Generates the router url with route verification from the connection, socket, or URI.\n\n  See `url/1` for more information.\n  \"\"\"\n  defmacro url(\n             conn_or_socket_or_endpoint_or_uri,\n             {:sigil_p, _, [{:<<>>, _meta, _segments} = route, _]} = sigil_p\n           ) do\n    router = attr!(__CALLER__, :router)\n\n    route\n    |> build_route(sigil_p, __CALLER__, conn_or_socket_or_endpoint_or_uri, router)\n    |> inject_url(__CALLER__)\n  end\n\n  defmacro url(_conn_or_socket_or_endpoint_or_uri, other), do: raise_invalid_route(other)\n\n  @doc \"\"\"\n  Generates the url with route verification from the connection, socket, or URI and router.\n\n  See `url/1` for more information.\n  \"\"\"\n  defmacro url(\n             conn_or_socket_or_endpoint_or_uri,\n             router,\n             {:sigil_p, _, [{:<<>>, _meta, _segments} = route, _]} = sigil_p\n           ) do\n    router = Macro.expand(router, __CALLER__)\n\n    route\n    |> build_route(sigil_p, __CALLER__, conn_or_socket_or_endpoint_or_uri, router)\n    |> inject_url(__CALLER__)\n  end\n\n  defmacro url(_conn_or_socket_or_endpoint_or_uri, _router, other), do: raise_invalid_route(other)\n\n  @doc \"\"\"\n  Generates url to a static asset given its file path.\n\n  See `c:Phoenix.Endpoint.static_url/0` and `c:Phoenix.Endpoint.static_path/1` for more information.\n\n  ## Examples\n\n      iex> static_url(conn, \"/assets/js/app.js\")\n      \"https://example.com/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n\n      iex> static_url(socket, \"/assets/js/app.js\")\n      \"https://example.com/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n\n      iex> static_url(AppWeb.Endpoint, \"/assets/js/app.js\")\n      \"https://example.com/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n  \"\"\"\n  def static_url(conn_or_socket_or_endpoint, path)\n\n  def static_url(%Plug.Conn{private: private}, path) do\n    case private do\n      %{phoenix_static_url: static_url} -> concat_url(static_url, path)\n      %{phoenix_endpoint: endpoint} -> static_url(endpoint, path)\n    end\n  end\n\n  def static_url(%_{endpoint: endpoint}, path) do\n    static_url(endpoint, path)\n  end\n\n  def static_url(endpoint, path) when is_atom(endpoint) do\n    endpoint.static_url() <> endpoint.static_path(path)\n  end\n\n  def static_url(other, path) do\n    raise ArgumentError,\n          \"expected a %Plug.Conn{}, a %Phoenix.Socket{}, a struct with an :endpoint key, \" <>\n            \"or a Phoenix.Endpoint when building static url for #{path}, got: #{inspect(other)}\"\n  end\n\n  @doc \"\"\"\n  Returns the URL for the endpoint from the path without verification.\n\n  ## Examples\n\n      iex> unverified_url(conn, \"/posts\")\n      \"https://example.com/posts\"\n\n      iex> unverified_url(conn, \"/posts\", page: 1)\n      \"https://example.com/posts?page=1\"\n  \"\"\"\n  def unverified_url(conn_or_socket_or_endpoint_or_uri, path, params \\\\ %{})\n      when (is_map(params) or is_list(params)) and is_binary(path) do\n    guarded_unverified_url(conn_or_socket_or_endpoint_or_uri, path, params)\n  end\n\n  defp guarded_unverified_url(%Plug.Conn{private: private}, path, params) do\n    case private do\n      %{phoenix_router_url: url} when is_binary(url) -> concat_url(url, path, params)\n      %{phoenix_endpoint: endpoint} -> concat_url(endpoint.url(), path, params)\n    end\n  end\n\n  defp guarded_unverified_url(%_{endpoint: endpoint}, path, params) do\n    concat_url(endpoint.url(), path, params)\n  end\n\n  defp guarded_unverified_url(%URI{} = uri, path, params) do\n    append_params(URI.to_string(%{uri | path: path}), params)\n  end\n\n  defp guarded_unverified_url(endpoint, path, params) when is_atom(endpoint) do\n    concat_url(endpoint.url(), path, params)\n  end\n\n  defp guarded_unverified_url(other, path, _params) do\n    raise ArgumentError,\n          \"expected a %Plug.Conn{}, a %Phoenix.Socket{}, a %URI{}, a struct with an :endpoint key, \" <>\n            \"or a Phoenix.Endpoint when building url at #{path}, got: #{inspect(other)}\"\n  end\n\n  defp concat_url(url, path) when is_binary(path), do: url <> path\n\n  defp concat_url(url, path, params) when is_binary(path) do\n    append_params(url <> path, params)\n  end\n\n  @doc \"\"\"\n  Generates path to a static asset given its file path.\n\n  See `c:Phoenix.Endpoint.static_path/1` for more information.\n\n  ## Examples\n\n      iex> static_path(conn, \"/assets/js/app.js\")\n      \"/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n\n      iex> static_path(socket, \"assets/js/app.js\")\n      \"/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n\n      iex> static_path(AppWeb.Endpoint, \"assets/js/app.js\")\n      \"/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n\n      iex> static_path(%URI{path: \"/subresource\"}, \"/assets/js/app.js\")\n      \"/subresource/assets/js/app-813dfe33b5c7f8388bccaaa38eec8382.js\"\n  \"\"\"\n  def static_path(conn_or_socket_or_endpoint_or_uri, path)\n\n  def static_path(%Plug.Conn{private: private}, path) do\n    case private do\n      %{phoenix_static_url: _} -> path\n      %{phoenix_endpoint: endpoint} -> endpoint.static_path(path)\n    end\n  end\n\n  def static_path(%URI{} = uri, path) do\n    (uri.path || \"\") <> path\n  end\n\n  def static_path(%_{endpoint: endpoint}, path) do\n    static_path(endpoint, path)\n  end\n\n  def static_path(endpoint, path) when is_atom(endpoint) do\n    endpoint.static_path(path)\n  end\n\n  @doc \"\"\"\n  Returns the path with relevant script name prefixes without verification.\n\n  ## Examples\n\n      iex> unverified_path(conn, AppWeb.Router, \"/posts\")\n      \"/posts\"\n\n      iex> unverified_path(conn, AppWeb.Router, \"/posts\", page: 1)\n      \"/posts?page=1\"\n  \"\"\"\n  def unverified_path(conn_or_socket_or_endpoint_or_uri, router, path, params \\\\ %{})\n\n  def unverified_path(%Plug.Conn{} = conn, router, path, params) do\n    conn\n    |> build_own_forward_path(router, path)\n    |> Kernel.||(build_conn_forward_path(conn, router, path))\n    |> Kernel.||(path_with_script(path, conn.script_name))\n    |> append_params(params)\n  end\n\n  def unverified_path(%URI{} = uri, _router, path, params) do\n    append_params((uri.path || \"\") <> path, params)\n  end\n\n  def unverified_path(%_{endpoint: endpoint}, router, path, params) do\n    unverified_path(endpoint, router, path, params)\n  end\n\n  def unverified_path(endpoint, _router, path, params) when is_atom(endpoint) do\n    append_params(endpoint.path(path), params)\n  end\n\n  def unverified_path(other, router, path, _params) do\n    raise ArgumentError,\n          \"expected a %Plug.Conn{}, a %Phoenix.Socket{}, a %URI{}, a struct with an :endpoint key, \" <>\n            \"or a Phoenix.Endpoint when building path for #{inspect(router)} at #{path}, got: #{inspect(other)}\"\n  end\n\n  defp append_params(path, params) when params == %{} or params == [], do: path\n\n  defp append_params(path, params) when is_map(params) or is_list(params) do\n    path <> \"?\" <> __encode_query__(params)\n  end\n\n  @doc false\n  def __encode_segment__(data) do\n    case data do\n      [] -> \"\"\n      [str | _] when is_binary(str) -> Enum.map_join(data, \"/\", &encode_segment/1)\n      _ -> encode_segment(data)\n    end\n  end\n\n  defp encode_segment(data) do\n    data\n    |> Phoenix.Param.to_param()\n    |> URI.encode(&URI.char_unreserved?/1)\n  end\n\n  # Segments must always start with /\n  defp verify_segment([\"/\" <> _ | _] = segments, route), do: verify_segment(segments, route, [])\n\n  defp verify_segment(_, route) do\n    raise ArgumentError, \"paths must begin with /, got: #{Macro.to_string(route)}\"\n  end\n\n  # separator followed by dynamic\n  defp verify_segment([\"/\" | rest], route, acc), do: verify_segment(rest, route, [\"/\" | acc])\n\n  # we've found a static segment, return to caller with rewritten query if found\n  defp verify_segment([\"/\" <> _ = segment | rest], route, acc) do\n    case {String.split(segment, \"?\"), rest} do\n      {[segment], _} ->\n        verify_segment(rest, route, [URI.encode(segment) | acc])\n\n      {[segment, static_query], dynamic_query} ->\n        {Enum.reverse([URI.encode(segment) | acc]),\n         verify_query(dynamic_query, route, [static_query])}\n    end\n  end\n\n  # we reached the static query string, return to caller\n  defp verify_segment([\"?\" <> query], _route, acc) do\n    {Enum.reverse(acc), [query]}\n  end\n\n  # we reached the dynamic query string, return to call with rewritten query\n  defp verify_segment([\"?\" <> static_query_segment | rest], route, acc) do\n    {Enum.reverse(acc), verify_query(rest, route, [static_query_segment])}\n  end\n\n  defp verify_segment([segment | _], route, _acc) when is_binary(segment) do\n    raise ArgumentError,\n          \"path segments after interpolation must begin with /, got: #{inspect(segment)} in #{Macro.to_string(route)}\"\n  end\n\n  defp verify_segment(\n         [\n           {:\"::\", m1, [{{:., m2, [Kernel, :to_string]}, m3, [dynamic]}, {:binary, _, _} = bin]}\n           | rest\n         ],\n         route,\n         [prev | _] = acc\n       )\n       when is_binary(prev) do\n    rewrite = {:\"::\", m1, [{{:., m2, [__MODULE__, :__encode_segment__]}, m3, [dynamic]}, bin]}\n    verify_segment(rest, route, [rewrite | acc])\n  end\n\n  defp verify_segment([_ | _], route, _acc) do\n    raise ArgumentError,\n          \"a dynamic ~p interpolation must follow a static segment, got: #{Macro.to_string(route)}\"\n  end\n\n  # we've reached the end of the path without finding query, return to caller\n  defp verify_segment([], _route, acc), do: {Enum.reverse(acc), _query = []}\n\n  defp verify_query(\n         [\n           {:\"::\", m1, [{{:., m2, [Kernel, :to_string]}, m3, [arg]}, {:binary, _, _} = bin]}\n           | rest\n         ],\n         route,\n         acc\n       ) do\n    unless is_binary(hd(acc)) do\n      raise ArgumentError,\n            \"interpolated query string params must be separated by &, got: #{Macro.to_string(route)}\"\n    end\n\n    sort_params? = Application.get_env(:phoenix, :sort_verified_routes_query_params, false)\n\n    rewrite =\n      {:\"::\", m1, [{{:., m2, [__MODULE__, :__encode_query__]}, m3, [arg, sort_params?]}, bin]}\n\n    verify_query(rest, route, [rewrite | acc])\n  end\n\n  defp verify_query([], _route, acc), do: Enum.reverse(acc)\n\n  defp verify_query([\"=\" | rest], route, acc) do\n    verify_query(rest, route, [\"=\" | acc])\n  end\n\n  defp verify_query([\"&\" <> _ = param | rest], route, acc) do\n    unless String.contains?(param, \"=\") do\n      raise ArgumentError,\n            \"expected query string param key to end with = or declare a static key value pair, got: #{inspect(param)}\"\n    end\n\n    verify_query(rest, route, [param | acc])\n  end\n\n  defp verify_query(_other, route, _acc) do\n    raise_invalid_query(route)\n  end\n\n  defp raise_invalid_query(route) do\n    raise ArgumentError,\n          \"expected query string param to be compile-time map or keyword list, got: #{Macro.to_string(route)}\"\n  end\n\n  @doc \"\"\"\n  Generates an integrity hash to a static asset given its file path.\n\n  See `c:Phoenix.Endpoint.static_integrity/1` for more information.\n\n  ## Examples\n\n      iex> static_integrity(conn, \"/assets/js/app.js\")\n      \"813dfe33b5c7f8388bccaaa38eec8382\"\n\n      iex> static_integrity(socket, \"/assets/js/app.js\")\n      \"813dfe33b5c7f8388bccaaa38eec8382\"\n\n      iex> static_integrity(AppWeb.Endpoint, \"/assets/js/app.js\")\n      \"813dfe33b5c7f8388bccaaa38eec8382\"\n  \"\"\"\n  def static_integrity(conn_or_socket_or_endpoint, path)\n\n  def static_integrity(%Plug.Conn{private: %{phoenix_endpoint: endpoint}}, path) do\n    static_integrity(endpoint, path)\n  end\n\n  def static_integrity(%_{endpoint: endpoint}, path) do\n    static_integrity(endpoint, path)\n  end\n\n  def static_integrity(endpoint, path) when is_atom(endpoint) do\n    endpoint.static_integrity(path)\n  end\n\n  @doc false\n  def __encode_query__(dict, sort? \\\\ false)\n\n  def __encode_query__(dict, sort?)\n      when is_list(dict) or (is_map(dict) and not is_struct(dict)) do\n    case Plug.Conn.Query.encode(dict, &to_param/1) do\n      \"\" -> \"\"\n      query_str -> maybe_sort_query(query_str, sort?)\n    end\n  end\n\n  def __encode_query__(val, _sort?), do: val |> to_param() |> URI.encode_www_form()\n\n  defp maybe_sort_query(query_str, false), do: query_str\n\n  defp maybe_sort_query(query, true),\n    do: query |> String.split(\"&\") |> Enum.sort() |> Enum.join(\"&\")\n\n  defp to_param(int) when is_integer(int), do: Integer.to_string(int)\n  defp to_param(bin) when is_binary(bin), do: bin\n  defp to_param(false), do: \"false\"\n  defp to_param(true), do: \"true\"\n  defp to_param(data), do: Phoenix.Param.to_param(data)\n\n  defp build_route(route_ast, sigil_p, env, endpoint_ctx, router) do\n    config = Module.get_attribute(env.module, :phoenix_verified_config, [])\n\n    router =\n      case Macro.expand(router, env) do\n        mod when is_atom(mod) ->\n          mod\n\n        other ->\n          raise ArgumentError, \"\"\"\n          expected router to be to module, got: #{inspect(other)}\n\n          If your router is not defined at compile-time, use unverified_path/3 instead.\n          \"\"\"\n      end\n\n    {static?, meta, test_path, path_ast, static_ast} =\n      rewrite_path(route_ast, endpoint_ctx, router, config)\n\n    route = %__MODULE__{\n      router: router,\n      warn_location: warn_location(meta, env),\n      inspected_route: Macro.to_string(sigil_p),\n      test_path: test_path\n    }\n\n    {route, static?, endpoint_ctx, route_ast, path_ast, static_ast}\n  end\n\n  defp warn_location(meta, %{line: line, file: file, function: function, module: module}) do\n    column = if column = meta[:column], do: column + 2\n    [line: line, function: function, module: module, file: file, column: column]\n  end\n\n  defp rewrite_path(route, endpoint, router, config) do\n    {:<<>>, meta, segments} = route\n    {path_rewrite, query_rewrite} = verify_segment(segments, route)\n\n    path_rewrite =\n      if config.path_prefixes != [] and\n           static_path?(path_rewrite |> Enum.slice(0, 1) |> materialize_path(), config.statics) do\n        path_rewrite\n      else\n        compile_prefixes(config.path_prefixes, meta) ++ path_rewrite\n      end\n\n    rewrite_route =\n      if query_rewrite == [] do\n        {:<<>>, meta, path_rewrite}\n      else\n        quote generated: true do\n          query_str = unquote({:<<>>, meta, query_rewrite})\n          path_str = unquote({:<<>>, meta, path_rewrite})\n\n          if query_str == \"\" do\n            path_str\n          else\n            path_str <> \"?\" <> query_str\n          end\n        end\n      end\n\n    test_path = materialize_path(path_rewrite)\n    static? = static_path?(test_path, config.statics)\n\n    path_ast =\n      quote generated: true do\n        unquote(__MODULE__).unverified_path(unquote_splicing([endpoint, router, rewrite_route]))\n      end\n\n    static_ast =\n      quote generated: true do\n        unquote(__MODULE__).static_path(unquote_splicing([endpoint, rewrite_route]))\n      end\n\n    {static?, meta, test_path, path_ast, static_ast}\n  end\n\n  defp materialize_path(path) do\n    Enum.map_join(path, &if(is_binary(&1), do: &1, else: \"1\"))\n  end\n\n  defp compile_prefixes(path_prefixes, meta) do\n    Enum.flat_map(path_prefixes, fn\n      {module, fun, args} when is_atom(module) and is_atom(fun) and is_list(args) ->\n        [\n          \"/\",\n          {:\"::\", meta,\n           [{{:., meta, [module, fun]}, meta, Macro.escape(args)}, {:binary, meta, nil}]}\n        ]\n\n      other ->\n        raise ArgumentError,\n              \":path_prefixes option in VerifiedRoutes must be a {mod, fun, args} and return a string, got: #{inspect(other)}\"\n    end)\n  end\n\n  defp attr!(%{function: nil}, _) do\n    raise \"Phoenix.VerifiedRoutes can only be used inside functions, please move your usage of ~p to functions\"\n  end\n\n  defp attr!(env, :endpoint) do\n    Module.get_attribute(env.module, :endpoint) ||\n      raise \"\"\"\n      expected @endpoint to be set. For dynamic endpoint resolution, use path/2 instead.\n\n      for example:\n\n          path(conn_or_socket, ~p\"/my-path\")\n      \"\"\"\n  end\n\n  defp attr!(env, name) do\n    Module.get_attribute(env.module, name) || raise \"expected @#{name} module attribute to be set\"\n  end\n\n  defp static_path?(path, statics) do\n    Enum.find(statics, &String.starts_with?(path, \"/\" <> &1))\n  end\n\n  defp build_own_forward_path(conn, router, path) do\n    case conn.private do\n      %{^router => local_script} when is_list(local_script) ->\n        path_with_script(path, local_script)\n\n      %{} ->\n        nil\n    end\n  end\n\n  defp build_conn_forward_path(%Plug.Conn{} = conn, router, path) do\n    with %{phoenix_router: phx_router} <- conn.private,\n         %{^phx_router => script_name} when is_list(script_name) <- conn.private,\n         local_script when is_list(local_script) <- phx_router.__forward__(router) do\n      path_with_script(path, script_name ++ local_script)\n    else\n      _ -> nil\n    end\n  end\n\n  defp path_with_script(path, []), do: path\n  defp path_with_script(path, script), do: \"/\" <> Enum.join(script, \"/\") <> path\nend\n"
  },
  {
    "path": "lib/phoenix.ex",
    "content": "defmodule Phoenix do\n  @moduledoc \"\"\"\n  This is the documentation for the Phoenix project.\n\n  To get started, see our [overview guides](overview.html).\n  \"\"\"\n  use Application\n\n  @doc false\n  def start(_type, _args) do\n    # Warm up caches\n    _ = Phoenix.Template.engines()\n    _ = Phoenix.Template.format_encoder(\"index.html\")\n    warn_on_missing_json_library()\n\n    # Configure proper system flags from Phoenix only\n    if stacktrace_depth = Application.get_env(:phoenix, :stacktrace_depth) do\n      :erlang.system_flag(:backtrace_depth, stacktrace_depth)\n    end\n\n    if filter = Application.get_env(:phoenix, :filter_parameters) do\n      Application.put_env(:phoenix, :filter_parameters, Phoenix.Logger.compile_filter(filter))\n    end\n\n    if Application.fetch_env!(:phoenix, :logger) do\n      Phoenix.Logger.install()\n    end\n\n    children = [\n      # Code reloading must be serial across all Phoenix apps\n      Phoenix.CodeReloader.Server,\n      {DynamicSupervisor, name: Phoenix.Transports.LongPoll.Supervisor, strategy: :one_for_one}\n    ]\n\n    Supervisor.start_link(children, strategy: :one_for_one, name: Phoenix.Supervisor)\n  end\n\n  @doc \"\"\"\n  Returns the configured JSON encoding library for Phoenix.\n\n  To customize the JSON library, including the following\n  in your `config/config.exs`:\n\n      config :phoenix, :json_library, AlternativeJsonLibrary\n\n  \"\"\"\n  def json_library do\n    Application.get_env(:phoenix, :json_library, Jason)\n  end\n\n  @doc \"\"\"\n  Returns the `:plug_init_mode` that controls when plugs are\n  initialized.\n\n  We recommend to set it to `:runtime` in development for\n  compilation time improvements. It must be `:compile` in\n  production (the default).\n\n  This option is passed as the `:init_mode` to `Plug.Builder.compile/3`.\n  \"\"\"\n  def plug_init_mode do\n    Application.get_env(:phoenix, :plug_init_mode, :compile)\n  end\n\n  defp warn_on_missing_json_library do\n    configured_lib = Application.get_env(:phoenix, :json_library)\n\n    if configured_lib && not Code.ensure_loaded?(configured_lib) do\n      IO.warn(\"\"\"\n      found #{inspect(configured_lib)} in your application configuration\n      for Phoenix JSON encoding, but module #{inspect(configured_lib)} is not available.\n      Ensure #{inspect(configured_lib)} is listed as a dependency in mix.exs.\n      \"\"\")\n    end\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule Phoenix.MixProject do\n  use Mix.Project\n\n  if Mix.env() != :prod do\n    for path <- :code.get_path(),\n        Regex.match?(~r/phx_new-[\\w\\.\\-]+\\/ebin$/, List.to_string(path)) do\n      Code.delete_path(path)\n    end\n  end\n\n  @version \"1.8.5\"\n  @scm_url \"https://github.com/phoenixframework/phoenix\"\n\n  # If the elixir requirement is updated, we need to make the installer\n  # use at least the minimum requirement used here. Although often the\n  # installer is ahead of Phoenix itself.\n  @elixir_requirement \"~> 1.15\"\n\n  def project do\n    [\n      app: :phoenix,\n      version: @version,\n      elixir: @elixir_requirement,\n      deps: deps(),\n      package: package(),\n      consolidate_protocols: Mix.env() != :test,\n      xref: [\n        exclude: [\n          {IEx, :started?, 0},\n          Ecto.Type,\n          :ranch,\n          :cowboy_req,\n          Plug.Cowboy.Conn,\n          Plug.Cowboy,\n          :httpc,\n          :public_key\n        ]\n      ],\n      elixirc_paths: elixirc_paths(Mix.env()),\n      name: \"Phoenix\",\n      docs: &docs/0,\n      aliases: aliases(),\n      source_url: @scm_url,\n      homepage_url: \"https://www.phoenixframework.org\",\n      description: \"Peace of mind from prototype to production\",\n      test_ignore_filters: [\n        &String.starts_with?(&1, \"test/fixtures/\"),\n        &String.starts_with?(&1, \"test/support/\")\n      ]\n    ]\n  end\n\n  def cli do\n    [\n      preferred_envs: [docs: :docs]\n    ]\n  end\n\n  defp elixirc_paths(:docs), do: [\"lib\", \"installer/lib\"]\n  defp elixirc_paths(_), do: [\"lib\"]\n\n  defp extra_applications(:test), do: [:inets]\n  defp extra_applications(_), do: []\n\n  def application do\n    [\n      mod: {Phoenix, []},\n      extra_applications: extra_applications(Mix.env()) ++ [:logger, :eex, :crypto, :public_key],\n      env: [\n        logger: true,\n        stacktrace_depth: nil,\n        filter_parameters: [\"password\"],\n        serve_endpoints: false,\n        gzippable_exts: ~w(.js .map .css .txt .text .html .json .svg .eot .ttf),\n        static_compressors: [Phoenix.Digester.Gzip]\n      ]\n    ]\n  end\n\n  defp deps do\n    [\n      {:plug, \"~> 1.14\"},\n      {:plug_crypto, \"~> 1.2 or ~> 2.0\"},\n      {:telemetry, \"~> 0.4 or ~> 1.0\"},\n      {:phoenix_pubsub, \"~> 2.1\"},\n      {:phoenix_template, \"~> 1.0\"},\n      {:websock_adapter, \"~> 0.5.3\"},\n\n      # TODO Drop phoenix_view as an optional dependency in Phoenix v2.0\n      {:phoenix_view, \"~> 2.0\", optional: true},\n\n      # Optional deps\n      {:plug_cowboy, \"~> 2.7\", optional: true},\n      {:bandit, \"~> 1.0\", optional: true},\n      {:jason, \"~> 1.0\", optional: true},\n\n      # Docs dependencies (some for cross references)\n      {:ex_doc, \"~> 0.38\", only: :docs},\n      {:ecto, \"~> 3.0\", only: :docs},\n      {:ecto_sql, \"~> 3.10\", only: :docs},\n      {:gettext, \"~> 1.0\", only: :docs},\n      {:telemetry_poller, \"~> 1.0\", only: :docs},\n      {:telemetry_metrics, \"~> 1.0\", only: :docs},\n      {:makeup_elixir, \"~> 1.0.1 or ~> 1.1\", only: :docs},\n      {:makeup_eex, \"~> 2.0\", only: :docs},\n      {:makeup_syntect, \"~> 0.1.0\", only: :docs},\n      # Test dependencies\n      {:phoenix_html, \"~> 4.0\", only: [:docs, :test]},\n      {:phx_new, path: \"./installer\", only: [:docs, :test]},\n      {:mint, \"~> 1.4\", only: :test},\n      {:mint_web_socket, \"~> 1.0.0\", only: :test},\n\n      # Dev dependencies\n      {:esbuild, \"~> 0.8\", only: :dev}\n    ]\n  end\n\n  defp package do\n    [\n      maintainers: [\"Chris McCord\", \"José Valim\", \"Gary Rennie\", \"Jason Stiebs\"],\n      licenses: [\"MIT\"],\n      links: %{\n        \"GitHub\" => @scm_url,\n        \"Changelog\" => \"https://hexdocs.pm/phoenix/changelog.html\"\n      },\n      files: ~w(\n          assets/js lib priv usage-rules CHANGELOG.md LICENSE.md mix.exs package.json README.md .formatter.exs\n          installer/templates/phx_web/components/core_components.ex\n        )\n    ]\n  end\n\n  defp docs do\n    [\n      search: [\n        %{\n          name: \"Latest\",\n          help:\n            \"Search latest versions of Plug, Phoenix, Phoenix.{HTML, LiveView, PubSub, Template}\",\n          packages: [\n            :plug,\n            :phoenix,\n            :phoenix_html,\n            :phoenix_live_view,\n            :phoenix_pubsub,\n            :phoenix_template\n          ]\n        },\n        %{\n          name: \"Current version\",\n          help: \"Search only this project\"\n        }\n      ],\n      source_ref: \"v#{@version}\",\n      main: \"overview\",\n      logo: \"logo.png\",\n      extra_section: \"GUIDES\",\n      assets: %{\"guides/assets\" => \"assets\"},\n      formatters: [\"html\", \"epub\"],\n      groups_for_modules: groups_for_modules(),\n      extras: extras(),\n      groups_for_extras: groups_for_extras(),\n      groups_for_docs: [\n        Reflection: &(&1[:type] == :reflection)\n      ],\n      skip_undefined_reference_warnings_on: [\"CHANGELOG.md\"]\n    ]\n  end\n\n  defp extras do\n    [\n      \"guides/introduction/overview.md\",\n      \"guides/introduction/installation.md\",\n      \"guides/introduction/up_and_running.md\",\n      \"guides/introduction/community.md\",\n      \"guides/introduction/packages_glossary.md\",\n      \"guides/directory_structure.md\",\n      \"guides/request_lifecycle.md\",\n      \"guides/plug.md\",\n      \"guides/routing.md\",\n      \"guides/controllers.md\",\n      \"guides/components.md\",\n      \"guides/ecto.md\",\n      \"guides/json_and_apis.md\",\n      \"guides/live_view.md\",\n      \"guides/asset_management.md\",\n      \"guides/telemetry.md\",\n      \"guides/security.md\",\n      \"guides/authn_authz/authn_authz.md\",\n      \"guides/authn_authz/mix_phx_gen_auth.md\",\n      \"guides/authn_authz/scopes.md\",\n      \"guides/authn_authz/api_authentication.md\",\n      \"guides/data_modelling/contexts.md\",\n      \"guides/data_modelling/your_first_context.md\",\n      \"guides/data_modelling/in_context_relationships.md\",\n      \"guides/data_modelling/cross_context_boundaries.md\",\n      \"guides/data_modelling/more_examples.md\",\n      \"guides/data_modelling/faq.md\",\n      \"guides/real_time/channels.md\",\n      \"guides/real_time/presence.md\",\n      \"guides/testing/testing.md\",\n      \"guides/testing/testing_contexts.md\",\n      \"guides/testing/testing_controllers.md\",\n      \"guides/testing/testing_channels.md\",\n      \"guides/deployment/deployment.md\",\n      \"guides/deployment/releases.md\",\n      \"guides/deployment/fly.md\",\n      \"guides/deployment/gigalixir.md\",\n      \"guides/deployment/heroku.md\",\n      \"guides/howto/custom_error_pages.md\",\n      \"guides/howto/file_uploads.md\",\n      \"guides/howto/swapping_databases.md\",\n      \"guides/howto/using_ssl.md\",\n      \"guides/howto/writing_a_channels_client.md\",\n      \"guides/cheatsheets/router.cheatmd\",\n      \"CHANGELOG.md\",\n      \"JS Documentation\": [url: \"js/index.html\"]\n    ]\n  end\n\n  defp groups_for_extras do\n    [\n      Introduction: ~r/guides\\/introduction\\/.?/,\n      \"Core Concepts\": ~r/guides\\/[^\\/]+\\.md/,\n      \"Data Modelling\": ~r/guides\\/data_modelling\\/.?/,\n      \"Authn and Authz\": ~r/guides\\/authn_authz\\/.?/,\n      \"Real-time\": ~r/guides\\/real_time\\/.?/,\n      Testing: ~r/guides\\/testing\\/.?/,\n      Deployment: ~r/guides\\/deployment\\/.?/,\n      Cheatsheets: ~r/guides\\/cheatsheets\\/.?/,\n      \"How-to's\": ~r/guides\\/howto\\/.?/\n    ]\n  end\n\n  defp groups_for_modules do\n    # Ungrouped Modules:\n    #\n    # Phoenix\n    # Phoenix.Channel\n    # Phoenix.Controller\n    # Phoenix.Endpoint\n    # Phoenix.Naming\n    # Phoenix.Logger\n    # Phoenix.Param\n    # Phoenix.Presence\n    # Phoenix.Router\n    # Phoenix.Socket\n    # Phoenix.Token\n    # Phoenix.VerifiedRoutes\n\n    [\n      Testing: [\n        Phoenix.ChannelTest,\n        Phoenix.ConnTest\n      ],\n      \"Adapters and Plugs\": [\n        Phoenix.CodeReloader,\n        Phoenix.Endpoint.Cowboy2Adapter,\n        Phoenix.Endpoint.SyncCodeReloadPlug\n      ],\n      Digester: [\n        Phoenix.Digester.Compressor,\n        Phoenix.Digester.Gzip\n      ],\n      Socket: [\n        Phoenix.Socket.Broadcast,\n        Phoenix.Socket.Message,\n        Phoenix.Socket.Reply,\n        Phoenix.Socket.Serializer,\n        Phoenix.Socket.Transport\n      ]\n    ]\n  end\n\n  defp aliases do\n    [\n      docs: [\"docs\", &generate_js_docs/1],\n      \"assets.build\": [\"esbuild module\", \"esbuild cdn\", \"esbuild cdn_min\", \"esbuild main\"],\n      \"assets.watch\": \"esbuild module --watch\",\n      \"archive.build\": &raise_on_archive_build/1,\n      # copy core_components before compiling / publishing\n      compile: [&copy_core_components/1, \"compile\"],\n      \"hex.publish\": [&copy_core_components/1, \"hex.publish\"]\n    ]\n  end\n\n  defp generate_js_docs(_) do\n    Mix.Task.run(\"app.start\")\n    {_, 0} = System.cmd(\"npm\", [\"install\"], into: IO.stream())\n    {_, 0} = System.cmd(\"npm\", [\"run\", \"docs\"], into: IO.stream())\n  end\n\n  defp raise_on_archive_build(_) do\n    Mix.raise(\"\"\"\n    You are trying to install \"phoenix\" as an archive, which is not supported. \\\n    You probably meant to install \"phx_new\" instead\n    \"\"\")\n  end\n\n  defp copy_core_components(_) do\n    source =\n      Path.join(__DIR__, \"installer/templates/phx_web/components/core_components.ex.eex\")\n\n    destination_dir = Path.join([__DIR__, \"priv\", \"templates\", \"phx.gen.live\"])\n    destination = Path.join(destination_dir, \"core_components.ex.eex\")\n    File.cp!(source, destination)\n  end\nend\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"phoenix\",\n  \"version\": \"1.8.5\",\n  \"description\": \"The official JavaScript client for the Phoenix web framework.\",\n  \"license\": \"MIT\",\n  \"module\": \"./priv/static/phoenix.mjs\",\n  \"main\": \"./priv/static/phoenix.cjs.js\",\n  \"unpkg\": \"./priv/static/phoenix.min.js\",\n  \"jsdelivr\": \"./priv/static/phoenix.min.js\",\n  \"exports\": {\n    \"import\": \"./priv/static/phoenix.mjs\",\n    \"require\": \"./priv/static/phoenix.cjs.js\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/phoenixframework/phoenix.git\"\n  },\n  \"author\": \"Chris McCord <chris@chrismccord.com> (https://www.phoenixframework.org)\",\n  \"files\": [\n    \"README.md\",\n    \"LICENSE.md\",\n    \"package.json\",\n    \"priv/static/*\",\n    \"assets/js/phoenix/*\"\n  ],\n  \"devDependencies\": {\n    \"@babel/cli\": \"7.28.6\",\n    \"@babel/core\": \"7.29.0\",\n    \"@babel/preset-env\": \"7.29.0\",\n    \"@eslint/js\": \"^10.0.1\",\n    \"@stylistic/eslint-plugin\": \"^5.0.0\",\n    \"documentation\": \"^14.0.3\",\n    \"eslint\": \"10.0.2\",\n    \"eslint-plugin-jest\": \"29.15.0\",\n    \"jest\": \"^30.0.0\",\n    \"jest-environment-jsdom\": \"^30.0.0\",\n    \"jsdom\": \"^28.1.0\",\n    \"mock-socket\": \"^9.3.1\"\n  },\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test.coverage\": \"jest --coverage\",\n    \"test.watch\": \"jest --watch\",\n    \"docs\": \"documentation build assets/js/phoenix/index.js -f html -o doc/js\"\n  }\n}\n"
  },
  {
    "path": "priv/static/phoenix.cjs.js",
    "content": "var __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n  for (var name in all)\n    __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n  if (from && typeof from === \"object\" || typeof from === \"function\") {\n    for (let key of __getOwnPropNames(from))\n      if (!__hasOwnProp.call(to, key) && key !== except)\n        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n  }\n  return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// js/phoenix/index.js\nvar phoenix_exports = {};\n__export(phoenix_exports, {\n  Channel: () => Channel,\n  LongPoll: () => LongPoll,\n  Presence: () => Presence,\n  Serializer: () => serializer_default,\n  Socket: () => Socket\n});\nmodule.exports = __toCommonJS(phoenix_exports);\n\n// js/phoenix/utils.js\nvar closure = (value) => {\n  if (typeof value === \"function\") {\n    return value;\n  } else {\n    let closure2 = function() {\n      return value;\n    };\n    return closure2;\n  }\n};\n\n// js/phoenix/constants.js\nvar globalSelf = typeof self !== \"undefined\" ? self : null;\nvar phxWindow = typeof window !== \"undefined\" ? window : null;\nvar global = globalSelf || phxWindow || globalThis;\nvar DEFAULT_VSN = \"2.0.0\";\nvar SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };\nvar DEFAULT_TIMEOUT = 1e4;\nvar WS_CLOSE_NORMAL = 1e3;\nvar CHANNEL_STATES = {\n  closed: \"closed\",\n  errored: \"errored\",\n  joined: \"joined\",\n  joining: \"joining\",\n  leaving: \"leaving\"\n};\nvar CHANNEL_EVENTS = {\n  close: \"phx_close\",\n  error: \"phx_error\",\n  join: \"phx_join\",\n  reply: \"phx_reply\",\n  leave: \"phx_leave\"\n};\nvar TRANSPORTS = {\n  longpoll: \"longpoll\",\n  websocket: \"websocket\"\n};\nvar XHR_STATES = {\n  complete: 4\n};\nvar AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\";\n\n// js/phoenix/push.js\nvar Push = class {\n  constructor(channel, event, payload, timeout) {\n    this.channel = channel;\n    this.event = event;\n    this.payload = payload || function() {\n      return {};\n    };\n    this.receivedResp = null;\n    this.timeout = timeout;\n    this.timeoutTimer = null;\n    this.recHooks = [];\n    this.sent = false;\n  }\n  /**\n   *\n   * @param {number} timeout\n   */\n  resend(timeout) {\n    this.timeout = timeout;\n    this.reset();\n    this.send();\n  }\n  /**\n   *\n   */\n  send() {\n    if (this.hasReceived(\"timeout\")) {\n      return;\n    }\n    this.startTimeout();\n    this.sent = true;\n    this.channel.socket.push({\n      topic: this.channel.topic,\n      event: this.event,\n      payload: this.payload(),\n      ref: this.ref,\n      join_ref: this.channel.joinRef()\n    });\n  }\n  /**\n   *\n   * @param {*} status\n   * @param {*} callback\n   */\n  receive(status, callback) {\n    if (this.hasReceived(status)) {\n      callback(this.receivedResp.response);\n    }\n    this.recHooks.push({ status, callback });\n    return this;\n  }\n  /**\n   * @private\n   */\n  reset() {\n    this.cancelRefEvent();\n    this.ref = null;\n    this.refEvent = null;\n    this.receivedResp = null;\n    this.sent = false;\n  }\n  /**\n   * @private\n   */\n  matchReceive({ status, response, _ref }) {\n    this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));\n  }\n  /**\n   * @private\n   */\n  cancelRefEvent() {\n    if (!this.refEvent) {\n      return;\n    }\n    this.channel.off(this.refEvent);\n  }\n  /**\n   * @private\n   */\n  cancelTimeout() {\n    clearTimeout(this.timeoutTimer);\n    this.timeoutTimer = null;\n  }\n  /**\n   * @private\n   */\n  startTimeout() {\n    if (this.timeoutTimer) {\n      this.cancelTimeout();\n    }\n    this.ref = this.channel.socket.makeRef();\n    this.refEvent = this.channel.replyEventName(this.ref);\n    this.channel.on(this.refEvent, (payload) => {\n      this.cancelRefEvent();\n      this.cancelTimeout();\n      this.receivedResp = payload;\n      this.matchReceive(payload);\n    });\n    this.timeoutTimer = setTimeout(() => {\n      this.trigger(\"timeout\", {});\n    }, this.timeout);\n  }\n  /**\n   * @private\n   */\n  hasReceived(status) {\n    return this.receivedResp && this.receivedResp.status === status;\n  }\n  /**\n   * @private\n   */\n  trigger(status, response) {\n    this.channel.trigger(this.refEvent, { status, response });\n  }\n};\n\n// js/phoenix/timer.js\nvar Timer = class {\n  constructor(callback, timerCalc) {\n    this.callback = callback;\n    this.timerCalc = timerCalc;\n    this.timer = null;\n    this.tries = 0;\n  }\n  reset() {\n    this.tries = 0;\n    clearTimeout(this.timer);\n  }\n  /**\n   * Cancels any previous scheduleTimeout and schedules callback\n   */\n  scheduleTimeout() {\n    clearTimeout(this.timer);\n    this.timer = setTimeout(() => {\n      this.tries = this.tries + 1;\n      this.callback();\n    }, this.timerCalc(this.tries + 1));\n  }\n};\n\n// js/phoenix/channel.js\nvar Channel = class {\n  constructor(topic, params, socket) {\n    this.state = CHANNEL_STATES.closed;\n    this.topic = topic;\n    this.params = closure(params || {});\n    this.socket = socket;\n    this.bindings = [];\n    this.bindingRef = 0;\n    this.timeout = this.socket.timeout;\n    this.joinedOnce = false;\n    this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);\n    this.pushBuffer = [];\n    this.stateChangeRefs = [];\n    this.rejoinTimer = new Timer(() => {\n      if (this.socket.isConnected()) {\n        this.rejoin();\n      }\n    }, this.socket.rejoinAfterMs);\n    this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));\n    this.stateChangeRefs.push(\n      this.socket.onOpen(() => {\n        this.rejoinTimer.reset();\n        if (this.isErrored()) {\n          this.rejoin();\n        }\n      })\n    );\n    this.joinPush.receive(\"ok\", () => {\n      this.state = CHANNEL_STATES.joined;\n      this.rejoinTimer.reset();\n      this.pushBuffer.forEach((pushEvent) => pushEvent.send());\n      this.pushBuffer = [];\n    });\n    this.joinPush.receive(\"error\", () => {\n      this.state = CHANNEL_STATES.errored;\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.onClose(() => {\n      this.rejoinTimer.reset();\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`);\n      this.state = CHANNEL_STATES.closed;\n      this.socket.remove(this);\n    });\n    this.onError((reason) => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason);\n      if (this.isJoining()) {\n        this.joinPush.reset();\n      }\n      this.state = CHANNEL_STATES.errored;\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.joinPush.receive(\"timeout\", () => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);\n      let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);\n      leavePush.send();\n      this.state = CHANNEL_STATES.errored;\n      this.joinPush.reset();\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n      this.trigger(this.replyEventName(ref), payload);\n    });\n  }\n  /**\n   * Join the channel\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  join(timeout = this.timeout) {\n    if (this.joinedOnce) {\n      throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\");\n    } else {\n      this.timeout = timeout;\n      this.joinedOnce = true;\n      this.rejoin();\n      return this.joinPush;\n    }\n  }\n  /**\n   * Hook into channel close\n   * @param {Function} callback\n   */\n  onClose(callback) {\n    this.on(CHANNEL_EVENTS.close, callback);\n  }\n  /**\n   * Hook into channel errors\n   * @param {Function} callback\n   */\n  onError(callback) {\n    return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason));\n  }\n  /**\n   * Subscribes on channel events\n   *\n   * Subscription returns a ref counter, which can be used later to\n   * unsubscribe the exact event listener\n   *\n   * @example\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * const ref2 = channel.on(\"event\", do_other_stuff)\n   * channel.off(\"event\", ref1)\n   * // Since unsubscription, do_stuff won't fire,\n   * // while do_other_stuff will keep firing on the \"event\"\n   *\n   * @param {string} event\n   * @param {Function} callback\n   * @returns {integer} ref\n   */\n  on(event, callback) {\n    let ref = this.bindingRef++;\n    this.bindings.push({ event, ref, callback });\n    return ref;\n  }\n  /**\n   * Unsubscribes off of channel events\n   *\n   * Use the ref returned from a channel.on() to unsubscribe one\n   * handler, or pass nothing for the ref to unsubscribe all\n   * handlers for the given event.\n   *\n   * @example\n   * // Unsubscribe the do_stuff handler\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * channel.off(\"event\", ref1)\n   *\n   * // Unsubscribe all handlers from event\n   * channel.off(\"event\")\n   *\n   * @param {string} event\n   * @param {integer} ref\n   */\n  off(event, ref) {\n    this.bindings = this.bindings.filter((bind) => {\n      return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref));\n    });\n  }\n  /**\n   * @private\n   */\n  canPush() {\n    return this.socket.isConnected() && this.isJoined();\n  }\n  /**\n   * Sends a message `event` to phoenix with the payload `payload`.\n   * Phoenix receives this in the `handle_in(event, payload, socket)`\n   * function. if phoenix replies or it times out (default 10000ms),\n   * then optionally the reply can be received.\n   *\n   * @example\n   * channel.push(\"event\")\n   *   .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n   *   .receive(\"error\", err => console.log(\"phoenix errored\", err))\n   *   .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n   * @param {string} event\n   * @param {Object} payload\n   * @param {number} [timeout]\n   * @returns {Push}\n   */\n  push(event, payload, timeout = this.timeout) {\n    payload = payload || {};\n    if (!this.joinedOnce) {\n      throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);\n    }\n    let pushEvent = new Push(this, event, function() {\n      return payload;\n    }, timeout);\n    if (this.canPush()) {\n      pushEvent.send();\n    } else {\n      pushEvent.startTimeout();\n      this.pushBuffer.push(pushEvent);\n    }\n    return pushEvent;\n  }\n  /** Leaves the channel\n   *\n   * Unsubscribes from server events, and\n   * instructs channel to terminate on server\n   *\n   * Triggers onClose() hooks\n   *\n   * To receive leave acknowledgements, use the `receive`\n   * hook to bind to the server ack, ie:\n   *\n   * @example\n   * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n   *\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  leave(timeout = this.timeout) {\n    this.rejoinTimer.reset();\n    this.joinPush.cancelTimeout();\n    this.state = CHANNEL_STATES.leaving;\n    let onClose = () => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`);\n      this.trigger(CHANNEL_EVENTS.close, \"leave\");\n    };\n    let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);\n    leavePush.receive(\"ok\", () => onClose()).receive(\"timeout\", () => onClose());\n    leavePush.send();\n    if (!this.canPush()) {\n      leavePush.trigger(\"ok\", {});\n    }\n    return leavePush;\n  }\n  /**\n   * Overridable message hook\n   *\n   * Receives all events for specialized message handling\n   * before dispatching to the channel callbacks.\n   *\n   * Must return the payload, modified or unmodified\n   * @param {string} event\n   * @param {Object} payload\n   * @param {integer} ref\n   * @returns {Object}\n   */\n  onMessage(_event, payload, _ref) {\n    return payload;\n  }\n  /**\n   * @private\n   */\n  isMember(topic, event, payload, joinRef) {\n    if (this.topic !== topic) {\n      return false;\n    }\n    if (joinRef && joinRef !== this.joinRef()) {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", { topic, event, payload, joinRef });\n      return false;\n    } else {\n      return true;\n    }\n  }\n  /**\n   * @private\n   */\n  joinRef() {\n    return this.joinPush.ref;\n  }\n  /**\n   * @private\n   */\n  rejoin(timeout = this.timeout) {\n    if (this.isLeaving()) {\n      return;\n    }\n    this.socket.leaveOpenTopic(this.topic);\n    this.state = CHANNEL_STATES.joining;\n    this.joinPush.resend(timeout);\n  }\n  /**\n   * @private\n   */\n  trigger(event, payload, ref, joinRef) {\n    let handledPayload = this.onMessage(event, payload, ref, joinRef);\n    if (payload && !handledPayload) {\n      throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\");\n    }\n    let eventBindings = this.bindings.filter((bind) => bind.event === event);\n    for (let i = 0; i < eventBindings.length; i++) {\n      let bind = eventBindings[i];\n      bind.callback(handledPayload, ref, joinRef || this.joinRef());\n    }\n  }\n  /**\n   * @private\n   */\n  replyEventName(ref) {\n    return `chan_reply_${ref}`;\n  }\n  /**\n   * @private\n   */\n  isClosed() {\n    return this.state === CHANNEL_STATES.closed;\n  }\n  /**\n   * @private\n   */\n  isErrored() {\n    return this.state === CHANNEL_STATES.errored;\n  }\n  /**\n   * @private\n   */\n  isJoined() {\n    return this.state === CHANNEL_STATES.joined;\n  }\n  /**\n   * @private\n   */\n  isJoining() {\n    return this.state === CHANNEL_STATES.joining;\n  }\n  /**\n   * @private\n   */\n  isLeaving() {\n    return this.state === CHANNEL_STATES.leaving;\n  }\n};\n\n// js/phoenix/ajax.js\nvar Ajax = class {\n  static request(method, endPoint, headers, body, timeout, ontimeout, callback) {\n    if (global.XDomainRequest) {\n      let req = new global.XDomainRequest();\n      return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);\n    } else if (global.XMLHttpRequest) {\n      let req = new global.XMLHttpRequest();\n      return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback);\n    } else if (global.fetch && global.AbortController) {\n      return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback);\n    } else {\n      throw new Error(\"No suitable XMLHttpRequest implementation found\");\n    }\n  }\n  static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) {\n    let options = {\n      method,\n      headers,\n      body\n    };\n    let controller = null;\n    if (timeout) {\n      controller = new AbortController();\n      const _timeoutId = setTimeout(() => controller.abort(), timeout);\n      options.signal = controller.signal;\n    }\n    global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => {\n      if (err.name === \"AbortError\" && ontimeout) {\n        ontimeout();\n      } else {\n        callback && callback(null);\n      }\n    });\n    return controller;\n  }\n  static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {\n    req.timeout = timeout;\n    req.open(method, endPoint);\n    req.onload = () => {\n      let response = this.parseJSON(req.responseText);\n      callback && callback(response);\n    };\n    if (ontimeout) {\n      req.ontimeout = ontimeout;\n    }\n    req.onprogress = () => {\n    };\n    req.send(body);\n    return req;\n  }\n  static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) {\n    req.open(method, endPoint, true);\n    req.timeout = timeout;\n    for (let [key, value] of Object.entries(headers)) {\n      req.setRequestHeader(key, value);\n    }\n    req.onerror = () => callback && callback(null);\n    req.onreadystatechange = () => {\n      if (req.readyState === XHR_STATES.complete && callback) {\n        let response = this.parseJSON(req.responseText);\n        callback(response);\n      }\n    };\n    if (ontimeout) {\n      req.ontimeout = ontimeout;\n    }\n    req.send(body);\n    return req;\n  }\n  static parseJSON(resp) {\n    if (!resp || resp === \"\") {\n      return null;\n    }\n    try {\n      return JSON.parse(resp);\n    } catch {\n      console && console.log(\"failed to parse JSON response\", resp);\n      return null;\n    }\n  }\n  static serialize(obj, parentKey) {\n    let queryStr = [];\n    for (var key in obj) {\n      if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n        continue;\n      }\n      let paramKey = parentKey ? `${parentKey}[${key}]` : key;\n      let paramVal = obj[key];\n      if (typeof paramVal === \"object\") {\n        queryStr.push(this.serialize(paramVal, paramKey));\n      } else {\n        queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal));\n      }\n    }\n    return queryStr.join(\"&\");\n  }\n  static appendParams(url, params) {\n    if (Object.keys(params).length === 0) {\n      return url;\n    }\n    let prefix = url.match(/\\?/) ? \"&\" : \"?\";\n    return `${url}${prefix}${this.serialize(params)}`;\n  }\n};\n\n// js/phoenix/longpoll.js\nvar arrayBufferToBase64 = (buffer) => {\n  let binary = \"\";\n  let bytes = new Uint8Array(buffer);\n  let len = bytes.byteLength;\n  for (let i = 0; i < len; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return btoa(binary);\n};\nvar LongPoll = class {\n  constructor(endPoint, protocols) {\n    if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) {\n      this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length));\n    }\n    this.endPoint = null;\n    this.token = null;\n    this.skipHeartbeat = true;\n    this.reqs = /* @__PURE__ */ new Set();\n    this.awaitingBatchAck = false;\n    this.currentBatch = null;\n    this.currentBatchTimer = null;\n    this.batchBuffer = [];\n    this.onopen = function() {\n    };\n    this.onerror = function() {\n    };\n    this.onmessage = function() {\n    };\n    this.onclose = function() {\n    };\n    this.pollEndpoint = this.normalizeEndpoint(endPoint);\n    this.readyState = SOCKET_STATES.connecting;\n    setTimeout(() => this.poll(), 0);\n  }\n  normalizeEndpoint(endPoint) {\n    return endPoint.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\").replace(new RegExp(\"(.*)/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll);\n  }\n  endpointURL() {\n    return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n  }\n  closeAndRetry(code, reason, wasClean) {\n    this.close(code, reason, wasClean);\n    this.readyState = SOCKET_STATES.connecting;\n  }\n  ontimeout() {\n    this.onerror(\"timeout\");\n    this.closeAndRetry(1005, \"timeout\", false);\n  }\n  isActive() {\n    return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting;\n  }\n  poll() {\n    const headers = { \"Accept\": \"application/json\" };\n    if (this.authToken) {\n      headers[\"X-Phoenix-AuthToken\"] = this.authToken;\n    }\n    this.ajax(\"GET\", headers, null, () => this.ontimeout(), (resp) => {\n      if (resp) {\n        var { status, token, messages } = resp;\n        if (status === 410 && this.token !== null) {\n          this.onerror(410);\n          this.closeAndRetry(3410, \"session_gone\", false);\n          return;\n        }\n        this.token = token;\n      } else {\n        status = 0;\n      }\n      switch (status) {\n        case 200:\n          messages.forEach((msg) => {\n            setTimeout(() => this.onmessage({ data: msg }), 0);\n          });\n          this.poll();\n          break;\n        case 204:\n          this.poll();\n          break;\n        case 410:\n          this.readyState = SOCKET_STATES.open;\n          this.onopen({});\n          this.poll();\n          break;\n        case 403:\n          this.onerror(403);\n          this.close(1008, \"forbidden\", false);\n          break;\n        case 0:\n        case 500:\n          this.onerror(500);\n          this.closeAndRetry(1011, \"internal server error\", 500);\n          break;\n        default:\n          throw new Error(`unhandled poll status ${status}`);\n      }\n    });\n  }\n  // we collect all pushes within the current event loop by\n  // setTimeout 0, which optimizes back-to-back procedural\n  // pushes against an empty buffer\n  send(body) {\n    if (typeof body !== \"string\") {\n      body = arrayBufferToBase64(body);\n    }\n    if (this.currentBatch) {\n      this.currentBatch.push(body);\n    } else if (this.awaitingBatchAck) {\n      this.batchBuffer.push(body);\n    } else {\n      this.currentBatch = [body];\n      this.currentBatchTimer = setTimeout(() => {\n        this.batchSend(this.currentBatch);\n        this.currentBatch = null;\n      }, 0);\n    }\n  }\n  batchSend(messages) {\n    this.awaitingBatchAck = true;\n    this.ajax(\"POST\", { \"Content-Type\": \"application/x-ndjson\" }, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), (resp) => {\n      this.awaitingBatchAck = false;\n      if (!resp || resp.status !== 200) {\n        this.onerror(resp && resp.status);\n        this.closeAndRetry(1011, \"internal server error\", false);\n      } else if (this.batchBuffer.length > 0) {\n        this.batchSend(this.batchBuffer);\n        this.batchBuffer = [];\n      }\n    });\n  }\n  close(code, reason, wasClean) {\n    for (let req of this.reqs) {\n      req.abort();\n    }\n    this.readyState = SOCKET_STATES.closed;\n    let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean });\n    this.batchBuffer = [];\n    clearTimeout(this.currentBatchTimer);\n    this.currentBatchTimer = null;\n    if (typeof CloseEvent !== \"undefined\") {\n      this.onclose(new CloseEvent(\"close\", opts));\n    } else {\n      this.onclose(opts);\n    }\n  }\n  ajax(method, headers, body, onCallerTimeout, callback) {\n    let req;\n    let ontimeout = () => {\n      this.reqs.delete(req);\n      onCallerTimeout();\n    };\n    req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => {\n      this.reqs.delete(req);\n      if (this.isActive()) {\n        callback(resp);\n      }\n    });\n    this.reqs.add(req);\n  }\n};\n\n// js/phoenix/presence.js\nvar Presence = class _Presence {\n  constructor(channel, opts = {}) {\n    let events = opts.events || { state: \"presence_state\", diff: \"presence_diff\" };\n    this.state = {};\n    this.pendingDiffs = [];\n    this.channel = channel;\n    this.joinRef = null;\n    this.caller = {\n      onJoin: function() {\n      },\n      onLeave: function() {\n      },\n      onSync: function() {\n      }\n    };\n    this.channel.on(events.state, (newState) => {\n      let { onJoin, onLeave, onSync } = this.caller;\n      this.joinRef = this.channel.joinRef();\n      this.state = _Presence.syncState(this.state, newState, onJoin, onLeave);\n      this.pendingDiffs.forEach((diff) => {\n        this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n      });\n      this.pendingDiffs = [];\n      onSync();\n    });\n    this.channel.on(events.diff, (diff) => {\n      let { onJoin, onLeave, onSync } = this.caller;\n      if (this.inPendingSyncState()) {\n        this.pendingDiffs.push(diff);\n      } else {\n        this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n        onSync();\n      }\n    });\n  }\n  onJoin(callback) {\n    this.caller.onJoin = callback;\n  }\n  onLeave(callback) {\n    this.caller.onLeave = callback;\n  }\n  onSync(callback) {\n    this.caller.onSync = callback;\n  }\n  list(by) {\n    return _Presence.list(this.state, by);\n  }\n  inPendingSyncState() {\n    return !this.joinRef || this.joinRef !== this.channel.joinRef();\n  }\n  // lower-level public static API\n  /**\n   * Used to sync the list of presences on the server\n   * with the client's state. An optional `onJoin` and `onLeave` callback can\n   * be provided to react to changes in the client's local presences across\n   * disconnects and reconnects with the server.\n   *\n   * @returns {Presence}\n   */\n  static syncState(currentState, newState, onJoin, onLeave) {\n    let state = this.clone(currentState);\n    let joins = {};\n    let leaves = {};\n    this.map(state, (key, presence) => {\n      if (!newState[key]) {\n        leaves[key] = presence;\n      }\n    });\n    this.map(newState, (key, newPresence) => {\n      let currentPresence = state[key];\n      if (currentPresence) {\n        let newRefs = newPresence.metas.map((m) => m.phx_ref);\n        let curRefs = currentPresence.metas.map((m) => m.phx_ref);\n        let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0);\n        let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0);\n        if (joinedMetas.length > 0) {\n          joins[key] = newPresence;\n          joins[key].metas = joinedMetas;\n        }\n        if (leftMetas.length > 0) {\n          leaves[key] = this.clone(currentPresence);\n          leaves[key].metas = leftMetas;\n        }\n      } else {\n        joins[key] = newPresence;\n      }\n    });\n    return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);\n  }\n  /**\n   *\n   * Used to sync a diff of presence join and leave\n   * events from the server, as they happen. Like `syncState`, `syncDiff`\n   * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n   * joining or leaving from a device.\n   *\n   * @returns {Presence}\n   */\n  static syncDiff(state, diff, onJoin, onLeave) {\n    let { joins, leaves } = this.clone(diff);\n    if (!onJoin) {\n      onJoin = function() {\n      };\n    }\n    if (!onLeave) {\n      onLeave = function() {\n      };\n    }\n    this.map(joins, (key, newPresence) => {\n      let currentPresence = state[key];\n      state[key] = this.clone(newPresence);\n      if (currentPresence) {\n        let joinedRefs = state[key].metas.map((m) => m.phx_ref);\n        let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0);\n        state[key].metas.unshift(...curMetas);\n      }\n      onJoin(key, currentPresence, newPresence);\n    });\n    this.map(leaves, (key, leftPresence) => {\n      let currentPresence = state[key];\n      if (!currentPresence) {\n        return;\n      }\n      let refsToRemove = leftPresence.metas.map((m) => m.phx_ref);\n      currentPresence.metas = currentPresence.metas.filter((p) => {\n        return refsToRemove.indexOf(p.phx_ref) < 0;\n      });\n      onLeave(key, currentPresence, leftPresence);\n      if (currentPresence.metas.length === 0) {\n        delete state[key];\n      }\n    });\n    return state;\n  }\n  /**\n   * Returns the array of presences, with selected metadata.\n   *\n   * @param {Object} presences\n   * @param {Function} chooser\n   *\n   * @returns {Presence}\n   */\n  static list(presences, chooser) {\n    if (!chooser) {\n      chooser = function(key, pres) {\n        return pres;\n      };\n    }\n    return this.map(presences, (key, presence) => {\n      return chooser(key, presence);\n    });\n  }\n  // private\n  static map(obj, func) {\n    return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));\n  }\n  static clone(obj) {\n    return JSON.parse(JSON.stringify(obj));\n  }\n};\n\n// js/phoenix/serializer.js\nvar serializer_default = {\n  HEADER_LENGTH: 1,\n  META_LENGTH: 4,\n  KINDS: { push: 0, reply: 1, broadcast: 2 },\n  encode(msg, callback) {\n    if (msg.payload.constructor === ArrayBuffer) {\n      return callback(this.binaryEncode(msg));\n    } else {\n      let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];\n      return callback(JSON.stringify(payload));\n    }\n  },\n  decode(rawPayload, callback) {\n    if (rawPayload.constructor === ArrayBuffer) {\n      return callback(this.binaryDecode(rawPayload));\n    } else {\n      let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);\n      return callback({ join_ref, ref, topic, event, payload });\n    }\n  },\n  // private\n  binaryEncode(message) {\n    let { join_ref, ref, event, topic, payload } = message;\n    let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length;\n    let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);\n    let view = new DataView(header);\n    let offset = 0;\n    view.setUint8(offset++, this.KINDS.push);\n    view.setUint8(offset++, join_ref.length);\n    view.setUint8(offset++, ref.length);\n    view.setUint8(offset++, topic.length);\n    view.setUint8(offset++, event.length);\n    Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    var combined = new Uint8Array(header.byteLength + payload.byteLength);\n    combined.set(new Uint8Array(header), 0);\n    combined.set(new Uint8Array(payload), header.byteLength);\n    return combined.buffer;\n  },\n  binaryDecode(buffer) {\n    let view = new DataView(buffer);\n    let kind = view.getUint8(0);\n    let decoder = new TextDecoder();\n    switch (kind) {\n      case this.KINDS.push:\n        return this.decodePush(buffer, view, decoder);\n      case this.KINDS.reply:\n        return this.decodeReply(buffer, view, decoder);\n      case this.KINDS.broadcast:\n        return this.decodeBroadcast(buffer, view, decoder);\n    }\n  },\n  decodePush(buffer, view, decoder) {\n    let joinRefSize = view.getUint8(1);\n    let topicSize = view.getUint8(2);\n    let eventSize = view.getUint8(3);\n    let offset = this.HEADER_LENGTH + this.META_LENGTH - 1;\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n    offset = offset + joinRefSize;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    return { join_ref: joinRef, ref: null, topic, event, payload: data };\n  },\n  decodeReply(buffer, view, decoder) {\n    let joinRefSize = view.getUint8(1);\n    let refSize = view.getUint8(2);\n    let topicSize = view.getUint8(3);\n    let eventSize = view.getUint8(4);\n    let offset = this.HEADER_LENGTH + this.META_LENGTH;\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n    offset = offset + joinRefSize;\n    let ref = decoder.decode(buffer.slice(offset, offset + refSize));\n    offset = offset + refSize;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    let payload = { status: event, response: data };\n    return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload };\n  },\n  decodeBroadcast(buffer, view, decoder) {\n    let topicSize = view.getUint8(1);\n    let eventSize = view.getUint8(2);\n    let offset = this.HEADER_LENGTH + 2;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    return { join_ref: null, ref: null, topic, event, payload: data };\n  }\n};\n\n// js/phoenix/socket.js\nvar Socket = class {\n  constructor(endPoint, opts = {}) {\n    this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n    this.channels = [];\n    this.sendBuffer = [];\n    this.ref = 0;\n    this.fallbackRef = null;\n    this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n    this.transport = opts.transport || global.WebSocket || LongPoll;\n    this.primaryPassedHealthCheck = false;\n    this.longPollFallbackMs = opts.longPollFallbackMs;\n    this.fallbackTimer = null;\n    this.sessionStore = opts.sessionStorage || global && global.sessionStorage;\n    this.establishedConnections = 0;\n    this.defaultEncoder = serializer_default.encode.bind(serializer_default);\n    this.defaultDecoder = serializer_default.decode.bind(serializer_default);\n    this.closeWasClean = true;\n    this.disconnecting = false;\n    this.binaryType = opts.binaryType || \"arraybuffer\";\n    this.connectClock = 1;\n    this.pageHidden = false;\n    if (this.transport !== LongPoll) {\n      this.encode = opts.encode || this.defaultEncoder;\n      this.decode = opts.decode || this.defaultDecoder;\n    } else {\n      this.encode = this.defaultEncoder;\n      this.decode = this.defaultDecoder;\n    }\n    let awaitingConnectionOnPageShow = null;\n    if (phxWindow && phxWindow.addEventListener) {\n      phxWindow.addEventListener(\"pagehide\", (_e) => {\n        if (this.conn) {\n          this.disconnect();\n          awaitingConnectionOnPageShow = this.connectClock;\n        }\n      });\n      phxWindow.addEventListener(\"pageshow\", (_e) => {\n        if (awaitingConnectionOnPageShow === this.connectClock) {\n          awaitingConnectionOnPageShow = null;\n          this.connect();\n        }\n      });\n      phxWindow.addEventListener(\"visibilitychange\", () => {\n        if (document.visibilityState === \"hidden\") {\n          this.pageHidden = true;\n        } else {\n          this.pageHidden = false;\n          if (!this.isConnected() && !this.closeWasClean) {\n            this.teardown(() => this.connect());\n          }\n        }\n      });\n    }\n    this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4;\n    this.rejoinAfterMs = (tries) => {\n      if (opts.rejoinAfterMs) {\n        return opts.rejoinAfterMs(tries);\n      } else {\n        return [1e3, 2e3, 5e3][tries - 1] || 1e4;\n      }\n    };\n    this.reconnectAfterMs = (tries) => {\n      if (opts.reconnectAfterMs) {\n        return opts.reconnectAfterMs(tries);\n      } else {\n        return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3;\n      }\n    };\n    this.logger = opts.logger || null;\n    if (!this.logger && opts.debug) {\n      this.logger = (kind, msg, data) => {\n        console.log(`${kind}: ${msg}`, data);\n      };\n    }\n    this.longpollerTimeout = opts.longpollerTimeout || 2e4;\n    this.params = closure(opts.params || {});\n    this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;\n    this.vsn = opts.vsn || DEFAULT_VSN;\n    this.heartbeatTimeoutTimer = null;\n    this.heartbeatTimer = null;\n    this.pendingHeartbeatRef = null;\n    this.reconnectTimer = new Timer(() => {\n      if (this.pageHidden) {\n        this.log(\"Not reconnecting as page is hidden!\");\n        this.teardown();\n        return;\n      }\n      this.teardown(() => this.connect());\n    }, this.reconnectAfterMs);\n    this.authToken = opts.authToken;\n  }\n  /**\n   * Returns the LongPoll transport reference\n   */\n  getLongPollTransport() {\n    return LongPoll;\n  }\n  /**\n   * Disconnects and replaces the active transport\n   *\n   * @param {Function} newTransport - The new transport class to instantiate\n   *\n   */\n  replaceTransport(newTransport) {\n    this.connectClock++;\n    this.closeWasClean = true;\n    clearTimeout(this.fallbackTimer);\n    this.reconnectTimer.reset();\n    if (this.conn) {\n      this.conn.close();\n      this.conn = null;\n    }\n    this.transport = newTransport;\n  }\n  /**\n   * Returns the socket protocol\n   *\n   * @returns {string}\n   */\n  protocol() {\n    return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n  }\n  /**\n   * The fully qualified socket url\n   *\n   * @returns {string}\n   */\n  endPointURL() {\n    let uri = Ajax.appendParams(\n      Ajax.appendParams(this.endPoint, this.params()),\n      { vsn: this.vsn }\n    );\n    if (uri.charAt(0) !== \"/\") {\n      return uri;\n    }\n    if (uri.charAt(1) === \"/\") {\n      return `${this.protocol()}:${uri}`;\n    }\n    return `${this.protocol()}://${location.host}${uri}`;\n  }\n  /**\n   * Disconnects the socket\n   *\n   * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n   *\n   * @param {Function} callback - Optional callback which is called after socket is disconnected.\n   * @param {integer} code - A status code for disconnection (Optional).\n   * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n   */\n  disconnect(callback, code, reason) {\n    this.connectClock++;\n    this.disconnecting = true;\n    this.closeWasClean = true;\n    clearTimeout(this.fallbackTimer);\n    this.reconnectTimer.reset();\n    this.teardown(() => {\n      this.disconnecting = false;\n      callback && callback();\n    }, code, reason);\n  }\n  /**\n   *\n   * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n   *\n   * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n   * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n   */\n  connect(params) {\n    if (params) {\n      console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\");\n      this.params = closure(params);\n    }\n    if (this.conn && !this.disconnecting) {\n      return;\n    }\n    if (this.longPollFallbackMs && this.transport !== LongPoll) {\n      this.connectWithFallback(LongPoll, this.longPollFallbackMs);\n    } else {\n      this.transportConnect();\n    }\n  }\n  /**\n   * Logs the message. Override `this.logger` for specialized logging. noops by default\n   * @param {string} kind\n   * @param {string} msg\n   * @param {Object} data\n   */\n  log(kind, msg, data) {\n    this.logger && this.logger(kind, msg, data);\n  }\n  /**\n   * Returns true if a logger has been set on this socket.\n   */\n  hasLogger() {\n    return this.logger !== null;\n  }\n  /**\n   * Registers callbacks for connection open events\n   *\n   * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n   *\n   * @param {Function} callback\n   */\n  onOpen(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.open.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection close events\n   * @param {Function} callback\n   */\n  onClose(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.close.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection error events\n   *\n   * @example socket.onError(function(error){ alert(\"An error occurred\") })\n   *\n   * @param {Function} callback\n   */\n  onError(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.error.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection message events\n   * @param {Function} callback\n   */\n  onMessage(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.message.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Pings the server and invokes the callback with the RTT in milliseconds\n   * @param {Function} callback\n   *\n   * Returns true if the ping was pushed or false if unable to be pushed.\n   */\n  ping(callback) {\n    if (!this.isConnected()) {\n      return false;\n    }\n    let ref = this.makeRef();\n    let startTime = Date.now();\n    this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref });\n    let onMsgRef = this.onMessage((msg) => {\n      if (msg.ref === ref) {\n        this.off([onMsgRef]);\n        callback(Date.now() - startTime);\n      }\n    });\n    return true;\n  }\n  /**\n   * @private\n   *\n   * @param {Function}\n   */\n  transportName(transport) {\n    switch (transport) {\n      case LongPoll:\n        return \"LongPoll\";\n      default:\n        return transport.name;\n    }\n  }\n  /**\n   * @private\n   */\n  transportConnect() {\n    this.connectClock++;\n    this.closeWasClean = false;\n    let protocols = void 0;\n    if (this.authToken) {\n      protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`];\n    }\n    this.conn = new this.transport(this.endPointURL(), protocols);\n    this.conn.binaryType = this.binaryType;\n    this.conn.timeout = this.longpollerTimeout;\n    this.conn.onopen = () => this.onConnOpen();\n    this.conn.onerror = (error) => this.onConnError(error);\n    this.conn.onmessage = (event) => this.onConnMessage(event);\n    this.conn.onclose = (event) => this.onConnClose(event);\n  }\n  getSession(key) {\n    return this.sessionStore && this.sessionStore.getItem(key);\n  }\n  storeSession(key, val) {\n    this.sessionStore && this.sessionStore.setItem(key, val);\n  }\n  connectWithFallback(fallbackTransport, fallbackThreshold = 2500) {\n    clearTimeout(this.fallbackTimer);\n    let established = false;\n    let primaryTransport = true;\n    let openRef, errorRef;\n    let fallbackTransportName = this.transportName(fallbackTransport);\n    let fallback = (reason) => {\n      this.log(\"transport\", `falling back to ${fallbackTransportName}...`, reason);\n      this.off([openRef, errorRef]);\n      primaryTransport = false;\n      this.replaceTransport(fallbackTransport);\n      this.transportConnect();\n    };\n    if (this.getSession(`phx:fallback:${fallbackTransportName}`)) {\n      return fallback(\"memorized\");\n    }\n    this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n    errorRef = this.onError((reason) => {\n      this.log(\"transport\", \"error\", reason);\n      if (primaryTransport && !established) {\n        clearTimeout(this.fallbackTimer);\n        fallback(reason);\n      }\n    });\n    if (this.fallbackRef) {\n      this.off([this.fallbackRef]);\n    }\n    this.fallbackRef = this.onOpen(() => {\n      established = true;\n      if (!primaryTransport) {\n        let fallbackTransportName2 = this.transportName(fallbackTransport);\n        if (!this.primaryPassedHealthCheck) {\n          this.storeSession(`phx:fallback:${fallbackTransportName2}`, \"true\");\n        }\n        return this.log(\"transport\", `established ${fallbackTransportName2} fallback`);\n      }\n      clearTimeout(this.fallbackTimer);\n      this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n      this.ping((rtt) => {\n        this.log(\"transport\", \"connected to primary after\", rtt);\n        this.primaryPassedHealthCheck = true;\n        clearTimeout(this.fallbackTimer);\n      });\n    });\n    this.transportConnect();\n  }\n  clearHeartbeats() {\n    clearTimeout(this.heartbeatTimer);\n    clearTimeout(this.heartbeatTimeoutTimer);\n  }\n  onConnOpen() {\n    if (this.hasLogger()) this.log(\"transport\", `${this.transportName(this.transport)} connected to ${this.endPointURL()}`);\n    this.closeWasClean = false;\n    this.disconnecting = false;\n    this.establishedConnections++;\n    this.flushSendBuffer();\n    this.reconnectTimer.reset();\n    this.resetHeartbeat();\n    this.stateChangeCallbacks.open.forEach(([, callback]) => callback());\n  }\n  /**\n   * @private\n   */\n  heartbeatTimeout() {\n    if (this.pendingHeartbeatRef) {\n      this.pendingHeartbeatRef = null;\n      if (this.hasLogger()) {\n        this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\");\n      }\n      this.triggerChanError();\n      this.closeWasClean = false;\n      this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\");\n    }\n  }\n  resetHeartbeat() {\n    if (this.conn && this.conn.skipHeartbeat) {\n      return;\n    }\n    this.pendingHeartbeatRef = null;\n    this.clearHeartbeats();\n    this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n  }\n  teardown(callback, code, reason) {\n    if (!this.conn) {\n      return callback && callback();\n    }\n    const connToClose = this.conn;\n    this.waitForBufferDone(connToClose, () => {\n      if (code) {\n        connToClose.close(code, reason || \"\");\n      } else {\n        connToClose.close();\n      }\n      this.waitForSocketClosed(connToClose, () => {\n        if (this.conn === connToClose) {\n          this.conn.onopen = function() {\n          };\n          this.conn.onerror = function() {\n          };\n          this.conn.onmessage = function() {\n          };\n          this.conn.onclose = function() {\n          };\n          this.conn = null;\n        }\n        callback && callback();\n      });\n    });\n  }\n  waitForBufferDone(conn, callback, tries = 1) {\n    if (tries === 5 || !conn.bufferedAmount) {\n      callback();\n      return;\n    }\n    setTimeout(() => {\n      this.waitForBufferDone(conn, callback, tries + 1);\n    }, 150 * tries);\n  }\n  waitForSocketClosed(conn, callback, tries = 1) {\n    if (tries === 5 || conn.readyState === SOCKET_STATES.closed) {\n      callback();\n      return;\n    }\n    setTimeout(() => {\n      this.waitForSocketClosed(conn, callback, tries + 1);\n    }, 150 * tries);\n  }\n  onConnClose(event) {\n    if (this.conn) this.conn.onclose = () => {\n    };\n    let closeCode = event && event.code;\n    if (this.hasLogger()) this.log(\"transport\", \"close\", event);\n    this.triggerChanError();\n    this.clearHeartbeats();\n    if (!this.closeWasClean && closeCode !== 1e3) {\n      this.reconnectTimer.scheduleTimeout();\n    }\n    this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));\n  }\n  /**\n   * @private\n   */\n  onConnError(error) {\n    if (this.hasLogger()) this.log(\"transport\", error);\n    let transportBefore = this.transport;\n    let establishedBefore = this.establishedConnections;\n    this.stateChangeCallbacks.error.forEach(([, callback]) => {\n      callback(error, transportBefore, establishedBefore);\n    });\n    if (transportBefore === this.transport || establishedBefore > 0) {\n      this.triggerChanError();\n    }\n  }\n  /**\n   * @private\n   */\n  triggerChanError() {\n    this.channels.forEach((channel) => {\n      if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {\n        channel.trigger(CHANNEL_EVENTS.error);\n      }\n    });\n  }\n  /**\n   * @returns {string}\n   */\n  connectionState() {\n    switch (this.conn && this.conn.readyState) {\n      case SOCKET_STATES.connecting:\n        return \"connecting\";\n      case SOCKET_STATES.open:\n        return \"open\";\n      case SOCKET_STATES.closing:\n        return \"closing\";\n      default:\n        return \"closed\";\n    }\n  }\n  /**\n   * @returns {boolean}\n   */\n  isConnected() {\n    return this.connectionState() === \"open\";\n  }\n  /**\n   * @private\n   *\n   * @param {Channel}\n   */\n  remove(channel) {\n    this.off(channel.stateChangeRefs);\n    this.channels = this.channels.filter((c) => c !== channel);\n  }\n  /**\n   * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n   *\n   * @param {refs} - list of refs returned by calls to\n   *                 `onOpen`, `onClose`, `onError,` and `onMessage`\n   */\n  off(refs) {\n    for (let key in this.stateChangeCallbacks) {\n      this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n        return refs.indexOf(ref) === -1;\n      });\n    }\n  }\n  /**\n   * Initiates a new channel for the given topic\n   *\n   * @param {string} topic\n   * @param {Object} chanParams - Parameters for the channel\n   * @returns {Channel}\n   */\n  channel(topic, chanParams = {}) {\n    let chan = new Channel(topic, chanParams, this);\n    this.channels.push(chan);\n    return chan;\n  }\n  /**\n   * @param {Object} data\n   */\n  push(data) {\n    if (this.hasLogger()) {\n      let { topic, event, payload, ref, join_ref } = data;\n      this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload);\n    }\n    if (this.isConnected()) {\n      this.encode(data, (result) => this.conn.send(result));\n    } else {\n      this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result)));\n    }\n  }\n  /**\n   * Return the next message ref, accounting for overflows\n   * @returns {string}\n   */\n  makeRef() {\n    let newRef = this.ref + 1;\n    if (newRef === this.ref) {\n      this.ref = 0;\n    } else {\n      this.ref = newRef;\n    }\n    return this.ref.toString();\n  }\n  sendHeartbeat() {\n    if (this.pendingHeartbeatRef && !this.isConnected()) {\n      return;\n    }\n    this.pendingHeartbeatRef = this.makeRef();\n    this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef });\n    this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs);\n  }\n  flushSendBuffer() {\n    if (this.isConnected() && this.sendBuffer.length > 0) {\n      this.sendBuffer.forEach((callback) => callback());\n      this.sendBuffer = [];\n    }\n  }\n  onConnMessage(rawMessage) {\n    this.decode(rawMessage.data, (msg) => {\n      let { topic, event, payload, ref, join_ref } = msg;\n      if (ref && ref === this.pendingHeartbeatRef) {\n        this.clearHeartbeats();\n        this.pendingHeartbeatRef = null;\n        this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n      }\n      if (this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload);\n      for (let i = 0; i < this.channels.length; i++) {\n        const channel = this.channels[i];\n        if (!channel.isMember(topic, event, payload, join_ref)) {\n          continue;\n        }\n        channel.trigger(event, payload, ref, join_ref);\n      }\n      for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {\n        let [, callback] = this.stateChangeCallbacks.message[i];\n        callback(msg);\n      }\n    });\n  }\n  leaveOpenTopic(topic) {\n    let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining()));\n    if (dupChannel) {\n      if (this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`);\n      dupChannel.leave();\n    }\n  }\n};\n//# sourceMappingURL=phoenix.cjs.js.map\n"
  },
  {
    "path": "priv/static/phoenix.js",
    "content": "var Phoenix = (() => {\n  var __defProp = Object.defineProperty;\n  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;\n  var __getOwnPropNames = Object.getOwnPropertyNames;\n  var __hasOwnProp = Object.prototype.hasOwnProperty;\n  var __export = (target, all) => {\n    for (var name in all)\n      __defProp(target, name, { get: all[name], enumerable: true });\n  };\n  var __copyProps = (to, from, except, desc) => {\n    if (from && typeof from === \"object\" || typeof from === \"function\") {\n      for (let key of __getOwnPropNames(from))\n        if (!__hasOwnProp.call(to, key) && key !== except)\n          __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n    }\n    return to;\n  };\n  var __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n  // js/phoenix/index.js\n  var phoenix_exports = {};\n  __export(phoenix_exports, {\n    Channel: () => Channel,\n    LongPoll: () => LongPoll,\n    Presence: () => Presence,\n    Serializer: () => serializer_default,\n    Socket: () => Socket\n  });\n\n  // js/phoenix/utils.js\n  var closure = (value) => {\n    if (typeof value === \"function\") {\n      return value;\n    } else {\n      let closure2 = function() {\n        return value;\n      };\n      return closure2;\n    }\n  };\n\n  // js/phoenix/constants.js\n  var globalSelf = typeof self !== \"undefined\" ? self : null;\n  var phxWindow = typeof window !== \"undefined\" ? window : null;\n  var global = globalSelf || phxWindow || globalThis;\n  var DEFAULT_VSN = \"2.0.0\";\n  var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };\n  var DEFAULT_TIMEOUT = 1e4;\n  var WS_CLOSE_NORMAL = 1e3;\n  var CHANNEL_STATES = {\n    closed: \"closed\",\n    errored: \"errored\",\n    joined: \"joined\",\n    joining: \"joining\",\n    leaving: \"leaving\"\n  };\n  var CHANNEL_EVENTS = {\n    close: \"phx_close\",\n    error: \"phx_error\",\n    join: \"phx_join\",\n    reply: \"phx_reply\",\n    leave: \"phx_leave\"\n  };\n  var TRANSPORTS = {\n    longpoll: \"longpoll\",\n    websocket: \"websocket\"\n  };\n  var XHR_STATES = {\n    complete: 4\n  };\n  var AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\";\n\n  // js/phoenix/push.js\n  var Push = class {\n    constructor(channel, event, payload, timeout) {\n      this.channel = channel;\n      this.event = event;\n      this.payload = payload || function() {\n        return {};\n      };\n      this.receivedResp = null;\n      this.timeout = timeout;\n      this.timeoutTimer = null;\n      this.recHooks = [];\n      this.sent = false;\n    }\n    /**\n     *\n     * @param {number} timeout\n     */\n    resend(timeout) {\n      this.timeout = timeout;\n      this.reset();\n      this.send();\n    }\n    /**\n     *\n     */\n    send() {\n      if (this.hasReceived(\"timeout\")) {\n        return;\n      }\n      this.startTimeout();\n      this.sent = true;\n      this.channel.socket.push({\n        topic: this.channel.topic,\n        event: this.event,\n        payload: this.payload(),\n        ref: this.ref,\n        join_ref: this.channel.joinRef()\n      });\n    }\n    /**\n     *\n     * @param {*} status\n     * @param {*} callback\n     */\n    receive(status, callback) {\n      if (this.hasReceived(status)) {\n        callback(this.receivedResp.response);\n      }\n      this.recHooks.push({ status, callback });\n      return this;\n    }\n    /**\n     * @private\n     */\n    reset() {\n      this.cancelRefEvent();\n      this.ref = null;\n      this.refEvent = null;\n      this.receivedResp = null;\n      this.sent = false;\n    }\n    /**\n     * @private\n     */\n    matchReceive({ status, response, _ref }) {\n      this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));\n    }\n    /**\n     * @private\n     */\n    cancelRefEvent() {\n      if (!this.refEvent) {\n        return;\n      }\n      this.channel.off(this.refEvent);\n    }\n    /**\n     * @private\n     */\n    cancelTimeout() {\n      clearTimeout(this.timeoutTimer);\n      this.timeoutTimer = null;\n    }\n    /**\n     * @private\n     */\n    startTimeout() {\n      if (this.timeoutTimer) {\n        this.cancelTimeout();\n      }\n      this.ref = this.channel.socket.makeRef();\n      this.refEvent = this.channel.replyEventName(this.ref);\n      this.channel.on(this.refEvent, (payload) => {\n        this.cancelRefEvent();\n        this.cancelTimeout();\n        this.receivedResp = payload;\n        this.matchReceive(payload);\n      });\n      this.timeoutTimer = setTimeout(() => {\n        this.trigger(\"timeout\", {});\n      }, this.timeout);\n    }\n    /**\n     * @private\n     */\n    hasReceived(status) {\n      return this.receivedResp && this.receivedResp.status === status;\n    }\n    /**\n     * @private\n     */\n    trigger(status, response) {\n      this.channel.trigger(this.refEvent, { status, response });\n    }\n  };\n\n  // js/phoenix/timer.js\n  var Timer = class {\n    constructor(callback, timerCalc) {\n      this.callback = callback;\n      this.timerCalc = timerCalc;\n      this.timer = null;\n      this.tries = 0;\n    }\n    reset() {\n      this.tries = 0;\n      clearTimeout(this.timer);\n    }\n    /**\n     * Cancels any previous scheduleTimeout and schedules callback\n     */\n    scheduleTimeout() {\n      clearTimeout(this.timer);\n      this.timer = setTimeout(() => {\n        this.tries = this.tries + 1;\n        this.callback();\n      }, this.timerCalc(this.tries + 1));\n    }\n  };\n\n  // js/phoenix/channel.js\n  var Channel = class {\n    constructor(topic, params, socket) {\n      this.state = CHANNEL_STATES.closed;\n      this.topic = topic;\n      this.params = closure(params || {});\n      this.socket = socket;\n      this.bindings = [];\n      this.bindingRef = 0;\n      this.timeout = this.socket.timeout;\n      this.joinedOnce = false;\n      this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);\n      this.pushBuffer = [];\n      this.stateChangeRefs = [];\n      this.rejoinTimer = new Timer(() => {\n        if (this.socket.isConnected()) {\n          this.rejoin();\n        }\n      }, this.socket.rejoinAfterMs);\n      this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));\n      this.stateChangeRefs.push(\n        this.socket.onOpen(() => {\n          this.rejoinTimer.reset();\n          if (this.isErrored()) {\n            this.rejoin();\n          }\n        })\n      );\n      this.joinPush.receive(\"ok\", () => {\n        this.state = CHANNEL_STATES.joined;\n        this.rejoinTimer.reset();\n        this.pushBuffer.forEach((pushEvent) => pushEvent.send());\n        this.pushBuffer = [];\n      });\n      this.joinPush.receive(\"error\", () => {\n        this.state = CHANNEL_STATES.errored;\n        if (this.socket.isConnected()) {\n          this.rejoinTimer.scheduleTimeout();\n        }\n      });\n      this.onClose(() => {\n        this.rejoinTimer.reset();\n        if (this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`);\n        this.state = CHANNEL_STATES.closed;\n        this.socket.remove(this);\n      });\n      this.onError((reason) => {\n        if (this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason);\n        if (this.isJoining()) {\n          this.joinPush.reset();\n        }\n        this.state = CHANNEL_STATES.errored;\n        if (this.socket.isConnected()) {\n          this.rejoinTimer.scheduleTimeout();\n        }\n      });\n      this.joinPush.receive(\"timeout\", () => {\n        if (this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);\n        let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);\n        leavePush.send();\n        this.state = CHANNEL_STATES.errored;\n        this.joinPush.reset();\n        if (this.socket.isConnected()) {\n          this.rejoinTimer.scheduleTimeout();\n        }\n      });\n      this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n        this.trigger(this.replyEventName(ref), payload);\n      });\n    }\n    /**\n     * Join the channel\n     * @param {integer} timeout\n     * @returns {Push}\n     */\n    join(timeout = this.timeout) {\n      if (this.joinedOnce) {\n        throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\");\n      } else {\n        this.timeout = timeout;\n        this.joinedOnce = true;\n        this.rejoin();\n        return this.joinPush;\n      }\n    }\n    /**\n     * Hook into channel close\n     * @param {Function} callback\n     */\n    onClose(callback) {\n      this.on(CHANNEL_EVENTS.close, callback);\n    }\n    /**\n     * Hook into channel errors\n     * @param {Function} callback\n     */\n    onError(callback) {\n      return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason));\n    }\n    /**\n     * Subscribes on channel events\n     *\n     * Subscription returns a ref counter, which can be used later to\n     * unsubscribe the exact event listener\n     *\n     * @example\n     * const ref1 = channel.on(\"event\", do_stuff)\n     * const ref2 = channel.on(\"event\", do_other_stuff)\n     * channel.off(\"event\", ref1)\n     * // Since unsubscription, do_stuff won't fire,\n     * // while do_other_stuff will keep firing on the \"event\"\n     *\n     * @param {string} event\n     * @param {Function} callback\n     * @returns {integer} ref\n     */\n    on(event, callback) {\n      let ref = this.bindingRef++;\n      this.bindings.push({ event, ref, callback });\n      return ref;\n    }\n    /**\n     * Unsubscribes off of channel events\n     *\n     * Use the ref returned from a channel.on() to unsubscribe one\n     * handler, or pass nothing for the ref to unsubscribe all\n     * handlers for the given event.\n     *\n     * @example\n     * // Unsubscribe the do_stuff handler\n     * const ref1 = channel.on(\"event\", do_stuff)\n     * channel.off(\"event\", ref1)\n     *\n     * // Unsubscribe all handlers from event\n     * channel.off(\"event\")\n     *\n     * @param {string} event\n     * @param {integer} ref\n     */\n    off(event, ref) {\n      this.bindings = this.bindings.filter((bind) => {\n        return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref));\n      });\n    }\n    /**\n     * @private\n     */\n    canPush() {\n      return this.socket.isConnected() && this.isJoined();\n    }\n    /**\n     * Sends a message `event` to phoenix with the payload `payload`.\n     * Phoenix receives this in the `handle_in(event, payload, socket)`\n     * function. if phoenix replies or it times out (default 10000ms),\n     * then optionally the reply can be received.\n     *\n     * @example\n     * channel.push(\"event\")\n     *   .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n     *   .receive(\"error\", err => console.log(\"phoenix errored\", err))\n     *   .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n     * @param {string} event\n     * @param {Object} payload\n     * @param {number} [timeout]\n     * @returns {Push}\n     */\n    push(event, payload, timeout = this.timeout) {\n      payload = payload || {};\n      if (!this.joinedOnce) {\n        throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);\n      }\n      let pushEvent = new Push(this, event, function() {\n        return payload;\n      }, timeout);\n      if (this.canPush()) {\n        pushEvent.send();\n      } else {\n        pushEvent.startTimeout();\n        this.pushBuffer.push(pushEvent);\n      }\n      return pushEvent;\n    }\n    /** Leaves the channel\n     *\n     * Unsubscribes from server events, and\n     * instructs channel to terminate on server\n     *\n     * Triggers onClose() hooks\n     *\n     * To receive leave acknowledgements, use the `receive`\n     * hook to bind to the server ack, ie:\n     *\n     * @example\n     * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n     *\n     * @param {integer} timeout\n     * @returns {Push}\n     */\n    leave(timeout = this.timeout) {\n      this.rejoinTimer.reset();\n      this.joinPush.cancelTimeout();\n      this.state = CHANNEL_STATES.leaving;\n      let onClose = () => {\n        if (this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`);\n        this.trigger(CHANNEL_EVENTS.close, \"leave\");\n      };\n      let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);\n      leavePush.receive(\"ok\", () => onClose()).receive(\"timeout\", () => onClose());\n      leavePush.send();\n      if (!this.canPush()) {\n        leavePush.trigger(\"ok\", {});\n      }\n      return leavePush;\n    }\n    /**\n     * Overridable message hook\n     *\n     * Receives all events for specialized message handling\n     * before dispatching to the channel callbacks.\n     *\n     * Must return the payload, modified or unmodified\n     * @param {string} event\n     * @param {Object} payload\n     * @param {integer} ref\n     * @returns {Object}\n     */\n    onMessage(_event, payload, _ref) {\n      return payload;\n    }\n    /**\n     * @private\n     */\n    isMember(topic, event, payload, joinRef) {\n      if (this.topic !== topic) {\n        return false;\n      }\n      if (joinRef && joinRef !== this.joinRef()) {\n        if (this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", { topic, event, payload, joinRef });\n        return false;\n      } else {\n        return true;\n      }\n    }\n    /**\n     * @private\n     */\n    joinRef() {\n      return this.joinPush.ref;\n    }\n    /**\n     * @private\n     */\n    rejoin(timeout = this.timeout) {\n      if (this.isLeaving()) {\n        return;\n      }\n      this.socket.leaveOpenTopic(this.topic);\n      this.state = CHANNEL_STATES.joining;\n      this.joinPush.resend(timeout);\n    }\n    /**\n     * @private\n     */\n    trigger(event, payload, ref, joinRef) {\n      let handledPayload = this.onMessage(event, payload, ref, joinRef);\n      if (payload && !handledPayload) {\n        throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\");\n      }\n      let eventBindings = this.bindings.filter((bind) => bind.event === event);\n      for (let i = 0; i < eventBindings.length; i++) {\n        let bind = eventBindings[i];\n        bind.callback(handledPayload, ref, joinRef || this.joinRef());\n      }\n    }\n    /**\n     * @private\n     */\n    replyEventName(ref) {\n      return `chan_reply_${ref}`;\n    }\n    /**\n     * @private\n     */\n    isClosed() {\n      return this.state === CHANNEL_STATES.closed;\n    }\n    /**\n     * @private\n     */\n    isErrored() {\n      return this.state === CHANNEL_STATES.errored;\n    }\n    /**\n     * @private\n     */\n    isJoined() {\n      return this.state === CHANNEL_STATES.joined;\n    }\n    /**\n     * @private\n     */\n    isJoining() {\n      return this.state === CHANNEL_STATES.joining;\n    }\n    /**\n     * @private\n     */\n    isLeaving() {\n      return this.state === CHANNEL_STATES.leaving;\n    }\n  };\n\n  // js/phoenix/ajax.js\n  var Ajax = class {\n    static request(method, endPoint, headers, body, timeout, ontimeout, callback) {\n      if (global.XDomainRequest) {\n        let req = new global.XDomainRequest();\n        return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);\n      } else if (global.XMLHttpRequest) {\n        let req = new global.XMLHttpRequest();\n        return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback);\n      } else if (global.fetch && global.AbortController) {\n        return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback);\n      } else {\n        throw new Error(\"No suitable XMLHttpRequest implementation found\");\n      }\n    }\n    static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) {\n      let options = {\n        method,\n        headers,\n        body\n      };\n      let controller = null;\n      if (timeout) {\n        controller = new AbortController();\n        const _timeoutId = setTimeout(() => controller.abort(), timeout);\n        options.signal = controller.signal;\n      }\n      global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => {\n        if (err.name === \"AbortError\" && ontimeout) {\n          ontimeout();\n        } else {\n          callback && callback(null);\n        }\n      });\n      return controller;\n    }\n    static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {\n      req.timeout = timeout;\n      req.open(method, endPoint);\n      req.onload = () => {\n        let response = this.parseJSON(req.responseText);\n        callback && callback(response);\n      };\n      if (ontimeout) {\n        req.ontimeout = ontimeout;\n      }\n      req.onprogress = () => {\n      };\n      req.send(body);\n      return req;\n    }\n    static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) {\n      req.open(method, endPoint, true);\n      req.timeout = timeout;\n      for (let [key, value] of Object.entries(headers)) {\n        req.setRequestHeader(key, value);\n      }\n      req.onerror = () => callback && callback(null);\n      req.onreadystatechange = () => {\n        if (req.readyState === XHR_STATES.complete && callback) {\n          let response = this.parseJSON(req.responseText);\n          callback(response);\n        }\n      };\n      if (ontimeout) {\n        req.ontimeout = ontimeout;\n      }\n      req.send(body);\n      return req;\n    }\n    static parseJSON(resp) {\n      if (!resp || resp === \"\") {\n        return null;\n      }\n      try {\n        return JSON.parse(resp);\n      } catch (e) {\n        console && console.log(\"failed to parse JSON response\", resp);\n        return null;\n      }\n    }\n    static serialize(obj, parentKey) {\n      let queryStr = [];\n      for (var key in obj) {\n        if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n          continue;\n        }\n        let paramKey = parentKey ? `${parentKey}[${key}]` : key;\n        let paramVal = obj[key];\n        if (typeof paramVal === \"object\") {\n          queryStr.push(this.serialize(paramVal, paramKey));\n        } else {\n          queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal));\n        }\n      }\n      return queryStr.join(\"&\");\n    }\n    static appendParams(url, params) {\n      if (Object.keys(params).length === 0) {\n        return url;\n      }\n      let prefix = url.match(/\\?/) ? \"&\" : \"?\";\n      return `${url}${prefix}${this.serialize(params)}`;\n    }\n  };\n\n  // js/phoenix/longpoll.js\n  var arrayBufferToBase64 = (buffer) => {\n    let binary = \"\";\n    let bytes = new Uint8Array(buffer);\n    let len = bytes.byteLength;\n    for (let i = 0; i < len; i++) {\n      binary += String.fromCharCode(bytes[i]);\n    }\n    return btoa(binary);\n  };\n  var LongPoll = class {\n    constructor(endPoint, protocols) {\n      if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) {\n        this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length));\n      }\n      this.endPoint = null;\n      this.token = null;\n      this.skipHeartbeat = true;\n      this.reqs = /* @__PURE__ */ new Set();\n      this.awaitingBatchAck = false;\n      this.currentBatch = null;\n      this.currentBatchTimer = null;\n      this.batchBuffer = [];\n      this.onopen = function() {\n      };\n      this.onerror = function() {\n      };\n      this.onmessage = function() {\n      };\n      this.onclose = function() {\n      };\n      this.pollEndpoint = this.normalizeEndpoint(endPoint);\n      this.readyState = SOCKET_STATES.connecting;\n      setTimeout(() => this.poll(), 0);\n    }\n    normalizeEndpoint(endPoint) {\n      return endPoint.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\").replace(new RegExp(\"(.*)/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll);\n    }\n    endpointURL() {\n      return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n    }\n    closeAndRetry(code, reason, wasClean) {\n      this.close(code, reason, wasClean);\n      this.readyState = SOCKET_STATES.connecting;\n    }\n    ontimeout() {\n      this.onerror(\"timeout\");\n      this.closeAndRetry(1005, \"timeout\", false);\n    }\n    isActive() {\n      return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting;\n    }\n    poll() {\n      const headers = { \"Accept\": \"application/json\" };\n      if (this.authToken) {\n        headers[\"X-Phoenix-AuthToken\"] = this.authToken;\n      }\n      this.ajax(\"GET\", headers, null, () => this.ontimeout(), (resp) => {\n        if (resp) {\n          var { status, token, messages } = resp;\n          if (status === 410 && this.token !== null) {\n            this.onerror(410);\n            this.closeAndRetry(3410, \"session_gone\", false);\n            return;\n          }\n          this.token = token;\n        } else {\n          status = 0;\n        }\n        switch (status) {\n          case 200:\n            messages.forEach((msg) => {\n              setTimeout(() => this.onmessage({ data: msg }), 0);\n            });\n            this.poll();\n            break;\n          case 204:\n            this.poll();\n            break;\n          case 410:\n            this.readyState = SOCKET_STATES.open;\n            this.onopen({});\n            this.poll();\n            break;\n          case 403:\n            this.onerror(403);\n            this.close(1008, \"forbidden\", false);\n            break;\n          case 0:\n          case 500:\n            this.onerror(500);\n            this.closeAndRetry(1011, \"internal server error\", 500);\n            break;\n          default:\n            throw new Error(`unhandled poll status ${status}`);\n        }\n      });\n    }\n    // we collect all pushes within the current event loop by\n    // setTimeout 0, which optimizes back-to-back procedural\n    // pushes against an empty buffer\n    send(body) {\n      if (typeof body !== \"string\") {\n        body = arrayBufferToBase64(body);\n      }\n      if (this.currentBatch) {\n        this.currentBatch.push(body);\n      } else if (this.awaitingBatchAck) {\n        this.batchBuffer.push(body);\n      } else {\n        this.currentBatch = [body];\n        this.currentBatchTimer = setTimeout(() => {\n          this.batchSend(this.currentBatch);\n          this.currentBatch = null;\n        }, 0);\n      }\n    }\n    batchSend(messages) {\n      this.awaitingBatchAck = true;\n      this.ajax(\"POST\", { \"Content-Type\": \"application/x-ndjson\" }, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), (resp) => {\n        this.awaitingBatchAck = false;\n        if (!resp || resp.status !== 200) {\n          this.onerror(resp && resp.status);\n          this.closeAndRetry(1011, \"internal server error\", false);\n        } else if (this.batchBuffer.length > 0) {\n          this.batchSend(this.batchBuffer);\n          this.batchBuffer = [];\n        }\n      });\n    }\n    close(code, reason, wasClean) {\n      for (let req of this.reqs) {\n        req.abort();\n      }\n      this.readyState = SOCKET_STATES.closed;\n      let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean });\n      this.batchBuffer = [];\n      clearTimeout(this.currentBatchTimer);\n      this.currentBatchTimer = null;\n      if (typeof CloseEvent !== \"undefined\") {\n        this.onclose(new CloseEvent(\"close\", opts));\n      } else {\n        this.onclose(opts);\n      }\n    }\n    ajax(method, headers, body, onCallerTimeout, callback) {\n      let req;\n      let ontimeout = () => {\n        this.reqs.delete(req);\n        onCallerTimeout();\n      };\n      req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => {\n        this.reqs.delete(req);\n        if (this.isActive()) {\n          callback(resp);\n        }\n      });\n      this.reqs.add(req);\n    }\n  };\n\n  // js/phoenix/presence.js\n  var Presence = class _Presence {\n    constructor(channel, opts = {}) {\n      let events = opts.events || { state: \"presence_state\", diff: \"presence_diff\" };\n      this.state = {};\n      this.pendingDiffs = [];\n      this.channel = channel;\n      this.joinRef = null;\n      this.caller = {\n        onJoin: function() {\n        },\n        onLeave: function() {\n        },\n        onSync: function() {\n        }\n      };\n      this.channel.on(events.state, (newState) => {\n        let { onJoin, onLeave, onSync } = this.caller;\n        this.joinRef = this.channel.joinRef();\n        this.state = _Presence.syncState(this.state, newState, onJoin, onLeave);\n        this.pendingDiffs.forEach((diff) => {\n          this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n        });\n        this.pendingDiffs = [];\n        onSync();\n      });\n      this.channel.on(events.diff, (diff) => {\n        let { onJoin, onLeave, onSync } = this.caller;\n        if (this.inPendingSyncState()) {\n          this.pendingDiffs.push(diff);\n        } else {\n          this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n          onSync();\n        }\n      });\n    }\n    onJoin(callback) {\n      this.caller.onJoin = callback;\n    }\n    onLeave(callback) {\n      this.caller.onLeave = callback;\n    }\n    onSync(callback) {\n      this.caller.onSync = callback;\n    }\n    list(by) {\n      return _Presence.list(this.state, by);\n    }\n    inPendingSyncState() {\n      return !this.joinRef || this.joinRef !== this.channel.joinRef();\n    }\n    // lower-level public static API\n    /**\n     * Used to sync the list of presences on the server\n     * with the client's state. An optional `onJoin` and `onLeave` callback can\n     * be provided to react to changes in the client's local presences across\n     * disconnects and reconnects with the server.\n     *\n     * @returns {Presence}\n     */\n    static syncState(currentState, newState, onJoin, onLeave) {\n      let state = this.clone(currentState);\n      let joins = {};\n      let leaves = {};\n      this.map(state, (key, presence) => {\n        if (!newState[key]) {\n          leaves[key] = presence;\n        }\n      });\n      this.map(newState, (key, newPresence) => {\n        let currentPresence = state[key];\n        if (currentPresence) {\n          let newRefs = newPresence.metas.map((m) => m.phx_ref);\n          let curRefs = currentPresence.metas.map((m) => m.phx_ref);\n          let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0);\n          let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0);\n          if (joinedMetas.length > 0) {\n            joins[key] = newPresence;\n            joins[key].metas = joinedMetas;\n          }\n          if (leftMetas.length > 0) {\n            leaves[key] = this.clone(currentPresence);\n            leaves[key].metas = leftMetas;\n          }\n        } else {\n          joins[key] = newPresence;\n        }\n      });\n      return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);\n    }\n    /**\n     *\n     * Used to sync a diff of presence join and leave\n     * events from the server, as they happen. Like `syncState`, `syncDiff`\n     * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n     * joining or leaving from a device.\n     *\n     * @returns {Presence}\n     */\n    static syncDiff(state, diff, onJoin, onLeave) {\n      let { joins, leaves } = this.clone(diff);\n      if (!onJoin) {\n        onJoin = function() {\n        };\n      }\n      if (!onLeave) {\n        onLeave = function() {\n        };\n      }\n      this.map(joins, (key, newPresence) => {\n        let currentPresence = state[key];\n        state[key] = this.clone(newPresence);\n        if (currentPresence) {\n          let joinedRefs = state[key].metas.map((m) => m.phx_ref);\n          let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0);\n          state[key].metas.unshift(...curMetas);\n        }\n        onJoin(key, currentPresence, newPresence);\n      });\n      this.map(leaves, (key, leftPresence) => {\n        let currentPresence = state[key];\n        if (!currentPresence) {\n          return;\n        }\n        let refsToRemove = leftPresence.metas.map((m) => m.phx_ref);\n        currentPresence.metas = currentPresence.metas.filter((p) => {\n          return refsToRemove.indexOf(p.phx_ref) < 0;\n        });\n        onLeave(key, currentPresence, leftPresence);\n        if (currentPresence.metas.length === 0) {\n          delete state[key];\n        }\n      });\n      return state;\n    }\n    /**\n     * Returns the array of presences, with selected metadata.\n     *\n     * @param {Object} presences\n     * @param {Function} chooser\n     *\n     * @returns {Presence}\n     */\n    static list(presences, chooser) {\n      if (!chooser) {\n        chooser = function(key, pres) {\n          return pres;\n        };\n      }\n      return this.map(presences, (key, presence) => {\n        return chooser(key, presence);\n      });\n    }\n    // private\n    static map(obj, func) {\n      return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));\n    }\n    static clone(obj) {\n      return JSON.parse(JSON.stringify(obj));\n    }\n  };\n\n  // js/phoenix/serializer.js\n  var serializer_default = {\n    HEADER_LENGTH: 1,\n    META_LENGTH: 4,\n    KINDS: { push: 0, reply: 1, broadcast: 2 },\n    encode(msg, callback) {\n      if (msg.payload.constructor === ArrayBuffer) {\n        return callback(this.binaryEncode(msg));\n      } else {\n        let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];\n        return callback(JSON.stringify(payload));\n      }\n    },\n    decode(rawPayload, callback) {\n      if (rawPayload.constructor === ArrayBuffer) {\n        return callback(this.binaryDecode(rawPayload));\n      } else {\n        let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);\n        return callback({ join_ref, ref, topic, event, payload });\n      }\n    },\n    // private\n    binaryEncode(message) {\n      let { join_ref, ref, event, topic, payload } = message;\n      let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length;\n      let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);\n      let view = new DataView(header);\n      let offset = 0;\n      view.setUint8(offset++, this.KINDS.push);\n      view.setUint8(offset++, join_ref.length);\n      view.setUint8(offset++, ref.length);\n      view.setUint8(offset++, topic.length);\n      view.setUint8(offset++, event.length);\n      Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n      Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n      Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n      Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n      var combined = new Uint8Array(header.byteLength + payload.byteLength);\n      combined.set(new Uint8Array(header), 0);\n      combined.set(new Uint8Array(payload), header.byteLength);\n      return combined.buffer;\n    },\n    binaryDecode(buffer) {\n      let view = new DataView(buffer);\n      let kind = view.getUint8(0);\n      let decoder = new TextDecoder();\n      switch (kind) {\n        case this.KINDS.push:\n          return this.decodePush(buffer, view, decoder);\n        case this.KINDS.reply:\n          return this.decodeReply(buffer, view, decoder);\n        case this.KINDS.broadcast:\n          return this.decodeBroadcast(buffer, view, decoder);\n      }\n    },\n    decodePush(buffer, view, decoder) {\n      let joinRefSize = view.getUint8(1);\n      let topicSize = view.getUint8(2);\n      let eventSize = view.getUint8(3);\n      let offset = this.HEADER_LENGTH + this.META_LENGTH - 1;\n      let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n      offset = offset + joinRefSize;\n      let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n      offset = offset + topicSize;\n      let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n      offset = offset + eventSize;\n      let data = buffer.slice(offset, buffer.byteLength);\n      return { join_ref: joinRef, ref: null, topic, event, payload: data };\n    },\n    decodeReply(buffer, view, decoder) {\n      let joinRefSize = view.getUint8(1);\n      let refSize = view.getUint8(2);\n      let topicSize = view.getUint8(3);\n      let eventSize = view.getUint8(4);\n      let offset = this.HEADER_LENGTH + this.META_LENGTH;\n      let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n      offset = offset + joinRefSize;\n      let ref = decoder.decode(buffer.slice(offset, offset + refSize));\n      offset = offset + refSize;\n      let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n      offset = offset + topicSize;\n      let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n      offset = offset + eventSize;\n      let data = buffer.slice(offset, buffer.byteLength);\n      let payload = { status: event, response: data };\n      return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload };\n    },\n    decodeBroadcast(buffer, view, decoder) {\n      let topicSize = view.getUint8(1);\n      let eventSize = view.getUint8(2);\n      let offset = this.HEADER_LENGTH + 2;\n      let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n      offset = offset + topicSize;\n      let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n      offset = offset + eventSize;\n      let data = buffer.slice(offset, buffer.byteLength);\n      return { join_ref: null, ref: null, topic, event, payload: data };\n    }\n  };\n\n  // js/phoenix/socket.js\n  var Socket = class {\n    constructor(endPoint, opts = {}) {\n      this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n      this.channels = [];\n      this.sendBuffer = [];\n      this.ref = 0;\n      this.fallbackRef = null;\n      this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n      this.transport = opts.transport || global.WebSocket || LongPoll;\n      this.primaryPassedHealthCheck = false;\n      this.longPollFallbackMs = opts.longPollFallbackMs;\n      this.fallbackTimer = null;\n      this.sessionStore = opts.sessionStorage || global && global.sessionStorage;\n      this.establishedConnections = 0;\n      this.defaultEncoder = serializer_default.encode.bind(serializer_default);\n      this.defaultDecoder = serializer_default.decode.bind(serializer_default);\n      this.closeWasClean = true;\n      this.disconnecting = false;\n      this.binaryType = opts.binaryType || \"arraybuffer\";\n      this.connectClock = 1;\n      this.pageHidden = false;\n      if (this.transport !== LongPoll) {\n        this.encode = opts.encode || this.defaultEncoder;\n        this.decode = opts.decode || this.defaultDecoder;\n      } else {\n        this.encode = this.defaultEncoder;\n        this.decode = this.defaultDecoder;\n      }\n      let awaitingConnectionOnPageShow = null;\n      if (phxWindow && phxWindow.addEventListener) {\n        phxWindow.addEventListener(\"pagehide\", (_e) => {\n          if (this.conn) {\n            this.disconnect();\n            awaitingConnectionOnPageShow = this.connectClock;\n          }\n        });\n        phxWindow.addEventListener(\"pageshow\", (_e) => {\n          if (awaitingConnectionOnPageShow === this.connectClock) {\n            awaitingConnectionOnPageShow = null;\n            this.connect();\n          }\n        });\n        phxWindow.addEventListener(\"visibilitychange\", () => {\n          if (document.visibilityState === \"hidden\") {\n            this.pageHidden = true;\n          } else {\n            this.pageHidden = false;\n            if (!this.isConnected() && !this.closeWasClean) {\n              this.teardown(() => this.connect());\n            }\n          }\n        });\n      }\n      this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4;\n      this.rejoinAfterMs = (tries) => {\n        if (opts.rejoinAfterMs) {\n          return opts.rejoinAfterMs(tries);\n        } else {\n          return [1e3, 2e3, 5e3][tries - 1] || 1e4;\n        }\n      };\n      this.reconnectAfterMs = (tries) => {\n        if (opts.reconnectAfterMs) {\n          return opts.reconnectAfterMs(tries);\n        } else {\n          return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3;\n        }\n      };\n      this.logger = opts.logger || null;\n      if (!this.logger && opts.debug) {\n        this.logger = (kind, msg, data) => {\n          console.log(`${kind}: ${msg}`, data);\n        };\n      }\n      this.longpollerTimeout = opts.longpollerTimeout || 2e4;\n      this.params = closure(opts.params || {});\n      this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;\n      this.vsn = opts.vsn || DEFAULT_VSN;\n      this.heartbeatTimeoutTimer = null;\n      this.heartbeatTimer = null;\n      this.pendingHeartbeatRef = null;\n      this.reconnectTimer = new Timer(() => {\n        if (this.pageHidden) {\n          this.log(\"Not reconnecting as page is hidden!\");\n          this.teardown();\n          return;\n        }\n        this.teardown(() => this.connect());\n      }, this.reconnectAfterMs);\n      this.authToken = opts.authToken;\n    }\n    /**\n     * Returns the LongPoll transport reference\n     */\n    getLongPollTransport() {\n      return LongPoll;\n    }\n    /**\n     * Disconnects and replaces the active transport\n     *\n     * @param {Function} newTransport - The new transport class to instantiate\n     *\n     */\n    replaceTransport(newTransport) {\n      this.connectClock++;\n      this.closeWasClean = true;\n      clearTimeout(this.fallbackTimer);\n      this.reconnectTimer.reset();\n      if (this.conn) {\n        this.conn.close();\n        this.conn = null;\n      }\n      this.transport = newTransport;\n    }\n    /**\n     * Returns the socket protocol\n     *\n     * @returns {string}\n     */\n    protocol() {\n      return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n    }\n    /**\n     * The fully qualified socket url\n     *\n     * @returns {string}\n     */\n    endPointURL() {\n      let uri = Ajax.appendParams(\n        Ajax.appendParams(this.endPoint, this.params()),\n        { vsn: this.vsn }\n      );\n      if (uri.charAt(0) !== \"/\") {\n        return uri;\n      }\n      if (uri.charAt(1) === \"/\") {\n        return `${this.protocol()}:${uri}`;\n      }\n      return `${this.protocol()}://${location.host}${uri}`;\n    }\n    /**\n     * Disconnects the socket\n     *\n     * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n     *\n     * @param {Function} callback - Optional callback which is called after socket is disconnected.\n     * @param {integer} code - A status code for disconnection (Optional).\n     * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n     */\n    disconnect(callback, code, reason) {\n      this.connectClock++;\n      this.disconnecting = true;\n      this.closeWasClean = true;\n      clearTimeout(this.fallbackTimer);\n      this.reconnectTimer.reset();\n      this.teardown(() => {\n        this.disconnecting = false;\n        callback && callback();\n      }, code, reason);\n    }\n    /**\n     *\n     * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n     *\n     * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n     * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n     */\n    connect(params) {\n      if (params) {\n        console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\");\n        this.params = closure(params);\n      }\n      if (this.conn && !this.disconnecting) {\n        return;\n      }\n      if (this.longPollFallbackMs && this.transport !== LongPoll) {\n        this.connectWithFallback(LongPoll, this.longPollFallbackMs);\n      } else {\n        this.transportConnect();\n      }\n    }\n    /**\n     * Logs the message. Override `this.logger` for specialized logging. noops by default\n     * @param {string} kind\n     * @param {string} msg\n     * @param {Object} data\n     */\n    log(kind, msg, data) {\n      this.logger && this.logger(kind, msg, data);\n    }\n    /**\n     * Returns true if a logger has been set on this socket.\n     */\n    hasLogger() {\n      return this.logger !== null;\n    }\n    /**\n     * Registers callbacks for connection open events\n     *\n     * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n     *\n     * @param {Function} callback\n     */\n    onOpen(callback) {\n      let ref = this.makeRef();\n      this.stateChangeCallbacks.open.push([ref, callback]);\n      return ref;\n    }\n    /**\n     * Registers callbacks for connection close events\n     * @param {Function} callback\n     */\n    onClose(callback) {\n      let ref = this.makeRef();\n      this.stateChangeCallbacks.close.push([ref, callback]);\n      return ref;\n    }\n    /**\n     * Registers callbacks for connection error events\n     *\n     * @example socket.onError(function(error){ alert(\"An error occurred\") })\n     *\n     * @param {Function} callback\n     */\n    onError(callback) {\n      let ref = this.makeRef();\n      this.stateChangeCallbacks.error.push([ref, callback]);\n      return ref;\n    }\n    /**\n     * Registers callbacks for connection message events\n     * @param {Function} callback\n     */\n    onMessage(callback) {\n      let ref = this.makeRef();\n      this.stateChangeCallbacks.message.push([ref, callback]);\n      return ref;\n    }\n    /**\n     * Pings the server and invokes the callback with the RTT in milliseconds\n     * @param {Function} callback\n     *\n     * Returns true if the ping was pushed or false if unable to be pushed.\n     */\n    ping(callback) {\n      if (!this.isConnected()) {\n        return false;\n      }\n      let ref = this.makeRef();\n      let startTime = Date.now();\n      this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref });\n      let onMsgRef = this.onMessage((msg) => {\n        if (msg.ref === ref) {\n          this.off([onMsgRef]);\n          callback(Date.now() - startTime);\n        }\n      });\n      return true;\n    }\n    /**\n     * @private\n     *\n     * @param {Function}\n     */\n    transportName(transport) {\n      switch (transport) {\n        case LongPoll:\n          return \"LongPoll\";\n        default:\n          return transport.name;\n      }\n    }\n    /**\n     * @private\n     */\n    transportConnect() {\n      this.connectClock++;\n      this.closeWasClean = false;\n      let protocols = void 0;\n      if (this.authToken) {\n        protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`];\n      }\n      this.conn = new this.transport(this.endPointURL(), protocols);\n      this.conn.binaryType = this.binaryType;\n      this.conn.timeout = this.longpollerTimeout;\n      this.conn.onopen = () => this.onConnOpen();\n      this.conn.onerror = (error) => this.onConnError(error);\n      this.conn.onmessage = (event) => this.onConnMessage(event);\n      this.conn.onclose = (event) => this.onConnClose(event);\n    }\n    getSession(key) {\n      return this.sessionStore && this.sessionStore.getItem(key);\n    }\n    storeSession(key, val) {\n      this.sessionStore && this.sessionStore.setItem(key, val);\n    }\n    connectWithFallback(fallbackTransport, fallbackThreshold = 2500) {\n      clearTimeout(this.fallbackTimer);\n      let established = false;\n      let primaryTransport = true;\n      let openRef, errorRef;\n      let fallbackTransportName = this.transportName(fallbackTransport);\n      let fallback = (reason) => {\n        this.log(\"transport\", `falling back to ${fallbackTransportName}...`, reason);\n        this.off([openRef, errorRef]);\n        primaryTransport = false;\n        this.replaceTransport(fallbackTransport);\n        this.transportConnect();\n      };\n      if (this.getSession(`phx:fallback:${fallbackTransportName}`)) {\n        return fallback(\"memorized\");\n      }\n      this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n      errorRef = this.onError((reason) => {\n        this.log(\"transport\", \"error\", reason);\n        if (primaryTransport && !established) {\n          clearTimeout(this.fallbackTimer);\n          fallback(reason);\n        }\n      });\n      if (this.fallbackRef) {\n        this.off([this.fallbackRef]);\n      }\n      this.fallbackRef = this.onOpen(() => {\n        established = true;\n        if (!primaryTransport) {\n          let fallbackTransportName2 = this.transportName(fallbackTransport);\n          if (!this.primaryPassedHealthCheck) {\n            this.storeSession(`phx:fallback:${fallbackTransportName2}`, \"true\");\n          }\n          return this.log(\"transport\", `established ${fallbackTransportName2} fallback`);\n        }\n        clearTimeout(this.fallbackTimer);\n        this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n        this.ping((rtt) => {\n          this.log(\"transport\", \"connected to primary after\", rtt);\n          this.primaryPassedHealthCheck = true;\n          clearTimeout(this.fallbackTimer);\n        });\n      });\n      this.transportConnect();\n    }\n    clearHeartbeats() {\n      clearTimeout(this.heartbeatTimer);\n      clearTimeout(this.heartbeatTimeoutTimer);\n    }\n    onConnOpen() {\n      if (this.hasLogger()) this.log(\"transport\", `${this.transportName(this.transport)} connected to ${this.endPointURL()}`);\n      this.closeWasClean = false;\n      this.disconnecting = false;\n      this.establishedConnections++;\n      this.flushSendBuffer();\n      this.reconnectTimer.reset();\n      this.resetHeartbeat();\n      this.stateChangeCallbacks.open.forEach(([, callback]) => callback());\n    }\n    /**\n     * @private\n     */\n    heartbeatTimeout() {\n      if (this.pendingHeartbeatRef) {\n        this.pendingHeartbeatRef = null;\n        if (this.hasLogger()) {\n          this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\");\n        }\n        this.triggerChanError();\n        this.closeWasClean = false;\n        this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\");\n      }\n    }\n    resetHeartbeat() {\n      if (this.conn && this.conn.skipHeartbeat) {\n        return;\n      }\n      this.pendingHeartbeatRef = null;\n      this.clearHeartbeats();\n      this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n    }\n    teardown(callback, code, reason) {\n      if (!this.conn) {\n        return callback && callback();\n      }\n      const connToClose = this.conn;\n      this.waitForBufferDone(connToClose, () => {\n        if (code) {\n          connToClose.close(code, reason || \"\");\n        } else {\n          connToClose.close();\n        }\n        this.waitForSocketClosed(connToClose, () => {\n          if (this.conn === connToClose) {\n            this.conn.onopen = function() {\n            };\n            this.conn.onerror = function() {\n            };\n            this.conn.onmessage = function() {\n            };\n            this.conn.onclose = function() {\n            };\n            this.conn = null;\n          }\n          callback && callback();\n        });\n      });\n    }\n    waitForBufferDone(conn, callback, tries = 1) {\n      if (tries === 5 || !conn.bufferedAmount) {\n        callback();\n        return;\n      }\n      setTimeout(() => {\n        this.waitForBufferDone(conn, callback, tries + 1);\n      }, 150 * tries);\n    }\n    waitForSocketClosed(conn, callback, tries = 1) {\n      if (tries === 5 || conn.readyState === SOCKET_STATES.closed) {\n        callback();\n        return;\n      }\n      setTimeout(() => {\n        this.waitForSocketClosed(conn, callback, tries + 1);\n      }, 150 * tries);\n    }\n    onConnClose(event) {\n      if (this.conn) this.conn.onclose = () => {\n      };\n      let closeCode = event && event.code;\n      if (this.hasLogger()) this.log(\"transport\", \"close\", event);\n      this.triggerChanError();\n      this.clearHeartbeats();\n      if (!this.closeWasClean && closeCode !== 1e3) {\n        this.reconnectTimer.scheduleTimeout();\n      }\n      this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));\n    }\n    /**\n     * @private\n     */\n    onConnError(error) {\n      if (this.hasLogger()) this.log(\"transport\", error);\n      let transportBefore = this.transport;\n      let establishedBefore = this.establishedConnections;\n      this.stateChangeCallbacks.error.forEach(([, callback]) => {\n        callback(error, transportBefore, establishedBefore);\n      });\n      if (transportBefore === this.transport || establishedBefore > 0) {\n        this.triggerChanError();\n      }\n    }\n    /**\n     * @private\n     */\n    triggerChanError() {\n      this.channels.forEach((channel) => {\n        if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {\n          channel.trigger(CHANNEL_EVENTS.error);\n        }\n      });\n    }\n    /**\n     * @returns {string}\n     */\n    connectionState() {\n      switch (this.conn && this.conn.readyState) {\n        case SOCKET_STATES.connecting:\n          return \"connecting\";\n        case SOCKET_STATES.open:\n          return \"open\";\n        case SOCKET_STATES.closing:\n          return \"closing\";\n        default:\n          return \"closed\";\n      }\n    }\n    /**\n     * @returns {boolean}\n     */\n    isConnected() {\n      return this.connectionState() === \"open\";\n    }\n    /**\n     * @private\n     *\n     * @param {Channel}\n     */\n    remove(channel) {\n      this.off(channel.stateChangeRefs);\n      this.channels = this.channels.filter((c) => c !== channel);\n    }\n    /**\n     * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n     *\n     * @param {refs} - list of refs returned by calls to\n     *                 `onOpen`, `onClose`, `onError,` and `onMessage`\n     */\n    off(refs) {\n      for (let key in this.stateChangeCallbacks) {\n        this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n          return refs.indexOf(ref) === -1;\n        });\n      }\n    }\n    /**\n     * Initiates a new channel for the given topic\n     *\n     * @param {string} topic\n     * @param {Object} chanParams - Parameters for the channel\n     * @returns {Channel}\n     */\n    channel(topic, chanParams = {}) {\n      let chan = new Channel(topic, chanParams, this);\n      this.channels.push(chan);\n      return chan;\n    }\n    /**\n     * @param {Object} data\n     */\n    push(data) {\n      if (this.hasLogger()) {\n        let { topic, event, payload, ref, join_ref } = data;\n        this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload);\n      }\n      if (this.isConnected()) {\n        this.encode(data, (result) => this.conn.send(result));\n      } else {\n        this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result)));\n      }\n    }\n    /**\n     * Return the next message ref, accounting for overflows\n     * @returns {string}\n     */\n    makeRef() {\n      let newRef = this.ref + 1;\n      if (newRef === this.ref) {\n        this.ref = 0;\n      } else {\n        this.ref = newRef;\n      }\n      return this.ref.toString();\n    }\n    sendHeartbeat() {\n      if (this.pendingHeartbeatRef && !this.isConnected()) {\n        return;\n      }\n      this.pendingHeartbeatRef = this.makeRef();\n      this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef });\n      this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs);\n    }\n    flushSendBuffer() {\n      if (this.isConnected() && this.sendBuffer.length > 0) {\n        this.sendBuffer.forEach((callback) => callback());\n        this.sendBuffer = [];\n      }\n    }\n    onConnMessage(rawMessage) {\n      this.decode(rawMessage.data, (msg) => {\n        let { topic, event, payload, ref, join_ref } = msg;\n        if (ref && ref === this.pendingHeartbeatRef) {\n          this.clearHeartbeats();\n          this.pendingHeartbeatRef = null;\n          this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n        }\n        if (this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload);\n        for (let i = 0; i < this.channels.length; i++) {\n          const channel = this.channels[i];\n          if (!channel.isMember(topic, event, payload, join_ref)) {\n            continue;\n          }\n          channel.trigger(event, payload, ref, join_ref);\n        }\n        for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {\n          let [, callback] = this.stateChangeCallbacks.message[i];\n          callback(msg);\n        }\n      });\n    }\n    leaveOpenTopic(topic) {\n      let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining()));\n      if (dupChannel) {\n        if (this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`);\n        dupChannel.leave();\n      }\n    }\n  };\n  return __toCommonJS(phoenix_exports);\n})();\n"
  },
  {
    "path": "priv/static/phoenix.mjs",
    "content": "// js/phoenix/utils.js\nvar closure = (value) => {\n  if (typeof value === \"function\") {\n    return value;\n  } else {\n    let closure2 = function() {\n      return value;\n    };\n    return closure2;\n  }\n};\n\n// js/phoenix/constants.js\nvar globalSelf = typeof self !== \"undefined\" ? self : null;\nvar phxWindow = typeof window !== \"undefined\" ? window : null;\nvar global = globalSelf || phxWindow || globalThis;\nvar DEFAULT_VSN = \"2.0.0\";\nvar SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };\nvar DEFAULT_TIMEOUT = 1e4;\nvar WS_CLOSE_NORMAL = 1e3;\nvar CHANNEL_STATES = {\n  closed: \"closed\",\n  errored: \"errored\",\n  joined: \"joined\",\n  joining: \"joining\",\n  leaving: \"leaving\"\n};\nvar CHANNEL_EVENTS = {\n  close: \"phx_close\",\n  error: \"phx_error\",\n  join: \"phx_join\",\n  reply: \"phx_reply\",\n  leave: \"phx_leave\"\n};\nvar TRANSPORTS = {\n  longpoll: \"longpoll\",\n  websocket: \"websocket\"\n};\nvar XHR_STATES = {\n  complete: 4\n};\nvar AUTH_TOKEN_PREFIX = \"base64url.bearer.phx.\";\n\n// js/phoenix/push.js\nvar Push = class {\n  constructor(channel, event, payload, timeout) {\n    this.channel = channel;\n    this.event = event;\n    this.payload = payload || function() {\n      return {};\n    };\n    this.receivedResp = null;\n    this.timeout = timeout;\n    this.timeoutTimer = null;\n    this.recHooks = [];\n    this.sent = false;\n  }\n  /**\n   *\n   * @param {number} timeout\n   */\n  resend(timeout) {\n    this.timeout = timeout;\n    this.reset();\n    this.send();\n  }\n  /**\n   *\n   */\n  send() {\n    if (this.hasReceived(\"timeout\")) {\n      return;\n    }\n    this.startTimeout();\n    this.sent = true;\n    this.channel.socket.push({\n      topic: this.channel.topic,\n      event: this.event,\n      payload: this.payload(),\n      ref: this.ref,\n      join_ref: this.channel.joinRef()\n    });\n  }\n  /**\n   *\n   * @param {*} status\n   * @param {*} callback\n   */\n  receive(status, callback) {\n    if (this.hasReceived(status)) {\n      callback(this.receivedResp.response);\n    }\n    this.recHooks.push({ status, callback });\n    return this;\n  }\n  /**\n   * @private\n   */\n  reset() {\n    this.cancelRefEvent();\n    this.ref = null;\n    this.refEvent = null;\n    this.receivedResp = null;\n    this.sent = false;\n  }\n  /**\n   * @private\n   */\n  matchReceive({ status, response, _ref }) {\n    this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response));\n  }\n  /**\n   * @private\n   */\n  cancelRefEvent() {\n    if (!this.refEvent) {\n      return;\n    }\n    this.channel.off(this.refEvent);\n  }\n  /**\n   * @private\n   */\n  cancelTimeout() {\n    clearTimeout(this.timeoutTimer);\n    this.timeoutTimer = null;\n  }\n  /**\n   * @private\n   */\n  startTimeout() {\n    if (this.timeoutTimer) {\n      this.cancelTimeout();\n    }\n    this.ref = this.channel.socket.makeRef();\n    this.refEvent = this.channel.replyEventName(this.ref);\n    this.channel.on(this.refEvent, (payload) => {\n      this.cancelRefEvent();\n      this.cancelTimeout();\n      this.receivedResp = payload;\n      this.matchReceive(payload);\n    });\n    this.timeoutTimer = setTimeout(() => {\n      this.trigger(\"timeout\", {});\n    }, this.timeout);\n  }\n  /**\n   * @private\n   */\n  hasReceived(status) {\n    return this.receivedResp && this.receivedResp.status === status;\n  }\n  /**\n   * @private\n   */\n  trigger(status, response) {\n    this.channel.trigger(this.refEvent, { status, response });\n  }\n};\n\n// js/phoenix/timer.js\nvar Timer = class {\n  constructor(callback, timerCalc) {\n    this.callback = callback;\n    this.timerCalc = timerCalc;\n    this.timer = null;\n    this.tries = 0;\n  }\n  reset() {\n    this.tries = 0;\n    clearTimeout(this.timer);\n  }\n  /**\n   * Cancels any previous scheduleTimeout and schedules callback\n   */\n  scheduleTimeout() {\n    clearTimeout(this.timer);\n    this.timer = setTimeout(() => {\n      this.tries = this.tries + 1;\n      this.callback();\n    }, this.timerCalc(this.tries + 1));\n  }\n};\n\n// js/phoenix/channel.js\nvar Channel = class {\n  constructor(topic, params, socket) {\n    this.state = CHANNEL_STATES.closed;\n    this.topic = topic;\n    this.params = closure(params || {});\n    this.socket = socket;\n    this.bindings = [];\n    this.bindingRef = 0;\n    this.timeout = this.socket.timeout;\n    this.joinedOnce = false;\n    this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);\n    this.pushBuffer = [];\n    this.stateChangeRefs = [];\n    this.rejoinTimer = new Timer(() => {\n      if (this.socket.isConnected()) {\n        this.rejoin();\n      }\n    }, this.socket.rejoinAfterMs);\n    this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset()));\n    this.stateChangeRefs.push(\n      this.socket.onOpen(() => {\n        this.rejoinTimer.reset();\n        if (this.isErrored()) {\n          this.rejoin();\n        }\n      })\n    );\n    this.joinPush.receive(\"ok\", () => {\n      this.state = CHANNEL_STATES.joined;\n      this.rejoinTimer.reset();\n      this.pushBuffer.forEach((pushEvent) => pushEvent.send());\n      this.pushBuffer = [];\n    });\n    this.joinPush.receive(\"error\", () => {\n      this.state = CHANNEL_STATES.errored;\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.onClose(() => {\n      this.rejoinTimer.reset();\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `close ${this.topic} ${this.joinRef()}`);\n      this.state = CHANNEL_STATES.closed;\n      this.socket.remove(this);\n    });\n    this.onError((reason) => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `error ${this.topic}`, reason);\n      if (this.isJoining()) {\n        this.joinPush.reset();\n      }\n      this.state = CHANNEL_STATES.errored;\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.joinPush.receive(\"timeout\", () => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout);\n      let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout);\n      leavePush.send();\n      this.state = CHANNEL_STATES.errored;\n      this.joinPush.reset();\n      if (this.socket.isConnected()) {\n        this.rejoinTimer.scheduleTimeout();\n      }\n    });\n    this.on(CHANNEL_EVENTS.reply, (payload, ref) => {\n      this.trigger(this.replyEventName(ref), payload);\n    });\n  }\n  /**\n   * Join the channel\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  join(timeout = this.timeout) {\n    if (this.joinedOnce) {\n      throw new Error(\"tried to join multiple times. 'join' can only be called a single time per channel instance\");\n    } else {\n      this.timeout = timeout;\n      this.joinedOnce = true;\n      this.rejoin();\n      return this.joinPush;\n    }\n  }\n  /**\n   * Hook into channel close\n   * @param {Function} callback\n   */\n  onClose(callback) {\n    this.on(CHANNEL_EVENTS.close, callback);\n  }\n  /**\n   * Hook into channel errors\n   * @param {Function} callback\n   */\n  onError(callback) {\n    return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason));\n  }\n  /**\n   * Subscribes on channel events\n   *\n   * Subscription returns a ref counter, which can be used later to\n   * unsubscribe the exact event listener\n   *\n   * @example\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * const ref2 = channel.on(\"event\", do_other_stuff)\n   * channel.off(\"event\", ref1)\n   * // Since unsubscription, do_stuff won't fire,\n   * // while do_other_stuff will keep firing on the \"event\"\n   *\n   * @param {string} event\n   * @param {Function} callback\n   * @returns {integer} ref\n   */\n  on(event, callback) {\n    let ref = this.bindingRef++;\n    this.bindings.push({ event, ref, callback });\n    return ref;\n  }\n  /**\n   * Unsubscribes off of channel events\n   *\n   * Use the ref returned from a channel.on() to unsubscribe one\n   * handler, or pass nothing for the ref to unsubscribe all\n   * handlers for the given event.\n   *\n   * @example\n   * // Unsubscribe the do_stuff handler\n   * const ref1 = channel.on(\"event\", do_stuff)\n   * channel.off(\"event\", ref1)\n   *\n   * // Unsubscribe all handlers from event\n   * channel.off(\"event\")\n   *\n   * @param {string} event\n   * @param {integer} ref\n   */\n  off(event, ref) {\n    this.bindings = this.bindings.filter((bind) => {\n      return !(bind.event === event && (typeof ref === \"undefined\" || ref === bind.ref));\n    });\n  }\n  /**\n   * @private\n   */\n  canPush() {\n    return this.socket.isConnected() && this.isJoined();\n  }\n  /**\n   * Sends a message `event` to phoenix with the payload `payload`.\n   * Phoenix receives this in the `handle_in(event, payload, socket)`\n   * function. if phoenix replies or it times out (default 10000ms),\n   * then optionally the reply can be received.\n   *\n   * @example\n   * channel.push(\"event\")\n   *   .receive(\"ok\", payload => console.log(\"phoenix replied:\", payload))\n   *   .receive(\"error\", err => console.log(\"phoenix errored\", err))\n   *   .receive(\"timeout\", () => console.log(\"timed out pushing\"))\n   * @param {string} event\n   * @param {Object} payload\n   * @param {number} [timeout]\n   * @returns {Push}\n   */\n  push(event, payload, timeout = this.timeout) {\n    payload = payload || {};\n    if (!this.joinedOnce) {\n      throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`);\n    }\n    let pushEvent = new Push(this, event, function() {\n      return payload;\n    }, timeout);\n    if (this.canPush()) {\n      pushEvent.send();\n    } else {\n      pushEvent.startTimeout();\n      this.pushBuffer.push(pushEvent);\n    }\n    return pushEvent;\n  }\n  /** Leaves the channel\n   *\n   * Unsubscribes from server events, and\n   * instructs channel to terminate on server\n   *\n   * Triggers onClose() hooks\n   *\n   * To receive leave acknowledgements, use the `receive`\n   * hook to bind to the server ack, ie:\n   *\n   * @example\n   * channel.leave().receive(\"ok\", () => alert(\"left!\") )\n   *\n   * @param {integer} timeout\n   * @returns {Push}\n   */\n  leave(timeout = this.timeout) {\n    this.rejoinTimer.reset();\n    this.joinPush.cancelTimeout();\n    this.state = CHANNEL_STATES.leaving;\n    let onClose = () => {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", `leave ${this.topic}`);\n      this.trigger(CHANNEL_EVENTS.close, \"leave\");\n    };\n    let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout);\n    leavePush.receive(\"ok\", () => onClose()).receive(\"timeout\", () => onClose());\n    leavePush.send();\n    if (!this.canPush()) {\n      leavePush.trigger(\"ok\", {});\n    }\n    return leavePush;\n  }\n  /**\n   * Overridable message hook\n   *\n   * Receives all events for specialized message handling\n   * before dispatching to the channel callbacks.\n   *\n   * Must return the payload, modified or unmodified\n   * @param {string} event\n   * @param {Object} payload\n   * @param {integer} ref\n   * @returns {Object}\n   */\n  onMessage(_event, payload, _ref) {\n    return payload;\n  }\n  /**\n   * @private\n   */\n  isMember(topic, event, payload, joinRef) {\n    if (this.topic !== topic) {\n      return false;\n    }\n    if (joinRef && joinRef !== this.joinRef()) {\n      if (this.socket.hasLogger()) this.socket.log(\"channel\", \"dropping outdated message\", { topic, event, payload, joinRef });\n      return false;\n    } else {\n      return true;\n    }\n  }\n  /**\n   * @private\n   */\n  joinRef() {\n    return this.joinPush.ref;\n  }\n  /**\n   * @private\n   */\n  rejoin(timeout = this.timeout) {\n    if (this.isLeaving()) {\n      return;\n    }\n    this.socket.leaveOpenTopic(this.topic);\n    this.state = CHANNEL_STATES.joining;\n    this.joinPush.resend(timeout);\n  }\n  /**\n   * @private\n   */\n  trigger(event, payload, ref, joinRef) {\n    let handledPayload = this.onMessage(event, payload, ref, joinRef);\n    if (payload && !handledPayload) {\n      throw new Error(\"channel onMessage callbacks must return the payload, modified or unmodified\");\n    }\n    let eventBindings = this.bindings.filter((bind) => bind.event === event);\n    for (let i = 0; i < eventBindings.length; i++) {\n      let bind = eventBindings[i];\n      bind.callback(handledPayload, ref, joinRef || this.joinRef());\n    }\n  }\n  /**\n   * @private\n   */\n  replyEventName(ref) {\n    return `chan_reply_${ref}`;\n  }\n  /**\n   * @private\n   */\n  isClosed() {\n    return this.state === CHANNEL_STATES.closed;\n  }\n  /**\n   * @private\n   */\n  isErrored() {\n    return this.state === CHANNEL_STATES.errored;\n  }\n  /**\n   * @private\n   */\n  isJoined() {\n    return this.state === CHANNEL_STATES.joined;\n  }\n  /**\n   * @private\n   */\n  isJoining() {\n    return this.state === CHANNEL_STATES.joining;\n  }\n  /**\n   * @private\n   */\n  isLeaving() {\n    return this.state === CHANNEL_STATES.leaving;\n  }\n};\n\n// js/phoenix/ajax.js\nvar Ajax = class {\n  static request(method, endPoint, headers, body, timeout, ontimeout, callback) {\n    if (global.XDomainRequest) {\n      let req = new global.XDomainRequest();\n      return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);\n    } else if (global.XMLHttpRequest) {\n      let req = new global.XMLHttpRequest();\n      return this.xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback);\n    } else if (global.fetch && global.AbortController) {\n      return this.fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback);\n    } else {\n      throw new Error(\"No suitable XMLHttpRequest implementation found\");\n    }\n  }\n  static fetchRequest(method, endPoint, headers, body, timeout, ontimeout, callback) {\n    let options = {\n      method,\n      headers,\n      body\n    };\n    let controller = null;\n    if (timeout) {\n      controller = new AbortController();\n      const _timeoutId = setTimeout(() => controller.abort(), timeout);\n      options.signal = controller.signal;\n    }\n    global.fetch(endPoint, options).then((response) => response.text()).then((data) => this.parseJSON(data)).then((data) => callback && callback(data)).catch((err) => {\n      if (err.name === \"AbortError\" && ontimeout) {\n        ontimeout();\n      } else {\n        callback && callback(null);\n      }\n    });\n    return controller;\n  }\n  static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {\n    req.timeout = timeout;\n    req.open(method, endPoint);\n    req.onload = () => {\n      let response = this.parseJSON(req.responseText);\n      callback && callback(response);\n    };\n    if (ontimeout) {\n      req.ontimeout = ontimeout;\n    }\n    req.onprogress = () => {\n    };\n    req.send(body);\n    return req;\n  }\n  static xhrRequest(req, method, endPoint, headers, body, timeout, ontimeout, callback) {\n    req.open(method, endPoint, true);\n    req.timeout = timeout;\n    for (let [key, value] of Object.entries(headers)) {\n      req.setRequestHeader(key, value);\n    }\n    req.onerror = () => callback && callback(null);\n    req.onreadystatechange = () => {\n      if (req.readyState === XHR_STATES.complete && callback) {\n        let response = this.parseJSON(req.responseText);\n        callback(response);\n      }\n    };\n    if (ontimeout) {\n      req.ontimeout = ontimeout;\n    }\n    req.send(body);\n    return req;\n  }\n  static parseJSON(resp) {\n    if (!resp || resp === \"\") {\n      return null;\n    }\n    try {\n      return JSON.parse(resp);\n    } catch {\n      console && console.log(\"failed to parse JSON response\", resp);\n      return null;\n    }\n  }\n  static serialize(obj, parentKey) {\n    let queryStr = [];\n    for (var key in obj) {\n      if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n        continue;\n      }\n      let paramKey = parentKey ? `${parentKey}[${key}]` : key;\n      let paramVal = obj[key];\n      if (typeof paramVal === \"object\") {\n        queryStr.push(this.serialize(paramVal, paramKey));\n      } else {\n        queryStr.push(encodeURIComponent(paramKey) + \"=\" + encodeURIComponent(paramVal));\n      }\n    }\n    return queryStr.join(\"&\");\n  }\n  static appendParams(url, params) {\n    if (Object.keys(params).length === 0) {\n      return url;\n    }\n    let prefix = url.match(/\\?/) ? \"&\" : \"?\";\n    return `${url}${prefix}${this.serialize(params)}`;\n  }\n};\n\n// js/phoenix/longpoll.js\nvar arrayBufferToBase64 = (buffer) => {\n  let binary = \"\";\n  let bytes = new Uint8Array(buffer);\n  let len = bytes.byteLength;\n  for (let i = 0; i < len; i++) {\n    binary += String.fromCharCode(bytes[i]);\n  }\n  return btoa(binary);\n};\nvar LongPoll = class {\n  constructor(endPoint, protocols) {\n    if (protocols && protocols.length === 2 && protocols[1].startsWith(AUTH_TOKEN_PREFIX)) {\n      this.authToken = atob(protocols[1].slice(AUTH_TOKEN_PREFIX.length));\n    }\n    this.endPoint = null;\n    this.token = null;\n    this.skipHeartbeat = true;\n    this.reqs = /* @__PURE__ */ new Set();\n    this.awaitingBatchAck = false;\n    this.currentBatch = null;\n    this.currentBatchTimer = null;\n    this.batchBuffer = [];\n    this.onopen = function() {\n    };\n    this.onerror = function() {\n    };\n    this.onmessage = function() {\n    };\n    this.onclose = function() {\n    };\n    this.pollEndpoint = this.normalizeEndpoint(endPoint);\n    this.readyState = SOCKET_STATES.connecting;\n    setTimeout(() => this.poll(), 0);\n  }\n  normalizeEndpoint(endPoint) {\n    return endPoint.replace(\"ws://\", \"http://\").replace(\"wss://\", \"https://\").replace(new RegExp(\"(.*)/\" + TRANSPORTS.websocket), \"$1/\" + TRANSPORTS.longpoll);\n  }\n  endpointURL() {\n    return Ajax.appendParams(this.pollEndpoint, { token: this.token });\n  }\n  closeAndRetry(code, reason, wasClean) {\n    this.close(code, reason, wasClean);\n    this.readyState = SOCKET_STATES.connecting;\n  }\n  ontimeout() {\n    this.onerror(\"timeout\");\n    this.closeAndRetry(1005, \"timeout\", false);\n  }\n  isActive() {\n    return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting;\n  }\n  poll() {\n    const headers = { \"Accept\": \"application/json\" };\n    if (this.authToken) {\n      headers[\"X-Phoenix-AuthToken\"] = this.authToken;\n    }\n    this.ajax(\"GET\", headers, null, () => this.ontimeout(), (resp) => {\n      if (resp) {\n        var { status, token, messages } = resp;\n        if (status === 410 && this.token !== null) {\n          this.onerror(410);\n          this.closeAndRetry(3410, \"session_gone\", false);\n          return;\n        }\n        this.token = token;\n      } else {\n        status = 0;\n      }\n      switch (status) {\n        case 200:\n          messages.forEach((msg) => {\n            setTimeout(() => this.onmessage({ data: msg }), 0);\n          });\n          this.poll();\n          break;\n        case 204:\n          this.poll();\n          break;\n        case 410:\n          this.readyState = SOCKET_STATES.open;\n          this.onopen({});\n          this.poll();\n          break;\n        case 403:\n          this.onerror(403);\n          this.close(1008, \"forbidden\", false);\n          break;\n        case 0:\n        case 500:\n          this.onerror(500);\n          this.closeAndRetry(1011, \"internal server error\", 500);\n          break;\n        default:\n          throw new Error(`unhandled poll status ${status}`);\n      }\n    });\n  }\n  // we collect all pushes within the current event loop by\n  // setTimeout 0, which optimizes back-to-back procedural\n  // pushes against an empty buffer\n  send(body) {\n    if (typeof body !== \"string\") {\n      body = arrayBufferToBase64(body);\n    }\n    if (this.currentBatch) {\n      this.currentBatch.push(body);\n    } else if (this.awaitingBatchAck) {\n      this.batchBuffer.push(body);\n    } else {\n      this.currentBatch = [body];\n      this.currentBatchTimer = setTimeout(() => {\n        this.batchSend(this.currentBatch);\n        this.currentBatch = null;\n      }, 0);\n    }\n  }\n  batchSend(messages) {\n    this.awaitingBatchAck = true;\n    this.ajax(\"POST\", { \"Content-Type\": \"application/x-ndjson\" }, messages.join(\"\\n\"), () => this.onerror(\"timeout\"), (resp) => {\n      this.awaitingBatchAck = false;\n      if (!resp || resp.status !== 200) {\n        this.onerror(resp && resp.status);\n        this.closeAndRetry(1011, \"internal server error\", false);\n      } else if (this.batchBuffer.length > 0) {\n        this.batchSend(this.batchBuffer);\n        this.batchBuffer = [];\n      }\n    });\n  }\n  close(code, reason, wasClean) {\n    for (let req of this.reqs) {\n      req.abort();\n    }\n    this.readyState = SOCKET_STATES.closed;\n    let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean });\n    this.batchBuffer = [];\n    clearTimeout(this.currentBatchTimer);\n    this.currentBatchTimer = null;\n    if (typeof CloseEvent !== \"undefined\") {\n      this.onclose(new CloseEvent(\"close\", opts));\n    } else {\n      this.onclose(opts);\n    }\n  }\n  ajax(method, headers, body, onCallerTimeout, callback) {\n    let req;\n    let ontimeout = () => {\n      this.reqs.delete(req);\n      onCallerTimeout();\n    };\n    req = Ajax.request(method, this.endpointURL(), headers, body, this.timeout, ontimeout, (resp) => {\n      this.reqs.delete(req);\n      if (this.isActive()) {\n        callback(resp);\n      }\n    });\n    this.reqs.add(req);\n  }\n};\n\n// js/phoenix/presence.js\nvar Presence = class _Presence {\n  constructor(channel, opts = {}) {\n    let events = opts.events || { state: \"presence_state\", diff: \"presence_diff\" };\n    this.state = {};\n    this.pendingDiffs = [];\n    this.channel = channel;\n    this.joinRef = null;\n    this.caller = {\n      onJoin: function() {\n      },\n      onLeave: function() {\n      },\n      onSync: function() {\n      }\n    };\n    this.channel.on(events.state, (newState) => {\n      let { onJoin, onLeave, onSync } = this.caller;\n      this.joinRef = this.channel.joinRef();\n      this.state = _Presence.syncState(this.state, newState, onJoin, onLeave);\n      this.pendingDiffs.forEach((diff) => {\n        this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n      });\n      this.pendingDiffs = [];\n      onSync();\n    });\n    this.channel.on(events.diff, (diff) => {\n      let { onJoin, onLeave, onSync } = this.caller;\n      if (this.inPendingSyncState()) {\n        this.pendingDiffs.push(diff);\n      } else {\n        this.state = _Presence.syncDiff(this.state, diff, onJoin, onLeave);\n        onSync();\n      }\n    });\n  }\n  onJoin(callback) {\n    this.caller.onJoin = callback;\n  }\n  onLeave(callback) {\n    this.caller.onLeave = callback;\n  }\n  onSync(callback) {\n    this.caller.onSync = callback;\n  }\n  list(by) {\n    return _Presence.list(this.state, by);\n  }\n  inPendingSyncState() {\n    return !this.joinRef || this.joinRef !== this.channel.joinRef();\n  }\n  // lower-level public static API\n  /**\n   * Used to sync the list of presences on the server\n   * with the client's state. An optional `onJoin` and `onLeave` callback can\n   * be provided to react to changes in the client's local presences across\n   * disconnects and reconnects with the server.\n   *\n   * @returns {Presence}\n   */\n  static syncState(currentState, newState, onJoin, onLeave) {\n    let state = this.clone(currentState);\n    let joins = {};\n    let leaves = {};\n    this.map(state, (key, presence) => {\n      if (!newState[key]) {\n        leaves[key] = presence;\n      }\n    });\n    this.map(newState, (key, newPresence) => {\n      let currentPresence = state[key];\n      if (currentPresence) {\n        let newRefs = newPresence.metas.map((m) => m.phx_ref);\n        let curRefs = currentPresence.metas.map((m) => m.phx_ref);\n        let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0);\n        let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0);\n        if (joinedMetas.length > 0) {\n          joins[key] = newPresence;\n          joins[key].metas = joinedMetas;\n        }\n        if (leftMetas.length > 0) {\n          leaves[key] = this.clone(currentPresence);\n          leaves[key].metas = leftMetas;\n        }\n      } else {\n        joins[key] = newPresence;\n      }\n    });\n    return this.syncDiff(state, { joins, leaves }, onJoin, onLeave);\n  }\n  /**\n   *\n   * Used to sync a diff of presence join and leave\n   * events from the server, as they happen. Like `syncState`, `syncDiff`\n   * accepts optional `onJoin` and `onLeave` callbacks to react to a user\n   * joining or leaving from a device.\n   *\n   * @returns {Presence}\n   */\n  static syncDiff(state, diff, onJoin, onLeave) {\n    let { joins, leaves } = this.clone(diff);\n    if (!onJoin) {\n      onJoin = function() {\n      };\n    }\n    if (!onLeave) {\n      onLeave = function() {\n      };\n    }\n    this.map(joins, (key, newPresence) => {\n      let currentPresence = state[key];\n      state[key] = this.clone(newPresence);\n      if (currentPresence) {\n        let joinedRefs = state[key].metas.map((m) => m.phx_ref);\n        let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0);\n        state[key].metas.unshift(...curMetas);\n      }\n      onJoin(key, currentPresence, newPresence);\n    });\n    this.map(leaves, (key, leftPresence) => {\n      let currentPresence = state[key];\n      if (!currentPresence) {\n        return;\n      }\n      let refsToRemove = leftPresence.metas.map((m) => m.phx_ref);\n      currentPresence.metas = currentPresence.metas.filter((p) => {\n        return refsToRemove.indexOf(p.phx_ref) < 0;\n      });\n      onLeave(key, currentPresence, leftPresence);\n      if (currentPresence.metas.length === 0) {\n        delete state[key];\n      }\n    });\n    return state;\n  }\n  /**\n   * Returns the array of presences, with selected metadata.\n   *\n   * @param {Object} presences\n   * @param {Function} chooser\n   *\n   * @returns {Presence}\n   */\n  static list(presences, chooser) {\n    if (!chooser) {\n      chooser = function(key, pres) {\n        return pres;\n      };\n    }\n    return this.map(presences, (key, presence) => {\n      return chooser(key, presence);\n    });\n  }\n  // private\n  static map(obj, func) {\n    return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key]));\n  }\n  static clone(obj) {\n    return JSON.parse(JSON.stringify(obj));\n  }\n};\n\n// js/phoenix/serializer.js\nvar serializer_default = {\n  HEADER_LENGTH: 1,\n  META_LENGTH: 4,\n  KINDS: { push: 0, reply: 1, broadcast: 2 },\n  encode(msg, callback) {\n    if (msg.payload.constructor === ArrayBuffer) {\n      return callback(this.binaryEncode(msg));\n    } else {\n      let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];\n      return callback(JSON.stringify(payload));\n    }\n  },\n  decode(rawPayload, callback) {\n    if (rawPayload.constructor === ArrayBuffer) {\n      return callback(this.binaryDecode(rawPayload));\n    } else {\n      let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload);\n      return callback({ join_ref, ref, topic, event, payload });\n    }\n  },\n  // private\n  binaryEncode(message) {\n    let { join_ref, ref, event, topic, payload } = message;\n    let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length;\n    let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength);\n    let view = new DataView(header);\n    let offset = 0;\n    view.setUint8(offset++, this.KINDS.push);\n    view.setUint8(offset++, join_ref.length);\n    view.setUint8(offset++, ref.length);\n    view.setUint8(offset++, topic.length);\n    view.setUint8(offset++, event.length);\n    Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0)));\n    var combined = new Uint8Array(header.byteLength + payload.byteLength);\n    combined.set(new Uint8Array(header), 0);\n    combined.set(new Uint8Array(payload), header.byteLength);\n    return combined.buffer;\n  },\n  binaryDecode(buffer) {\n    let view = new DataView(buffer);\n    let kind = view.getUint8(0);\n    let decoder = new TextDecoder();\n    switch (kind) {\n      case this.KINDS.push:\n        return this.decodePush(buffer, view, decoder);\n      case this.KINDS.reply:\n        return this.decodeReply(buffer, view, decoder);\n      case this.KINDS.broadcast:\n        return this.decodeBroadcast(buffer, view, decoder);\n    }\n  },\n  decodePush(buffer, view, decoder) {\n    let joinRefSize = view.getUint8(1);\n    let topicSize = view.getUint8(2);\n    let eventSize = view.getUint8(3);\n    let offset = this.HEADER_LENGTH + this.META_LENGTH - 1;\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n    offset = offset + joinRefSize;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    return { join_ref: joinRef, ref: null, topic, event, payload: data };\n  },\n  decodeReply(buffer, view, decoder) {\n    let joinRefSize = view.getUint8(1);\n    let refSize = view.getUint8(2);\n    let topicSize = view.getUint8(3);\n    let eventSize = view.getUint8(4);\n    let offset = this.HEADER_LENGTH + this.META_LENGTH;\n    let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize));\n    offset = offset + joinRefSize;\n    let ref = decoder.decode(buffer.slice(offset, offset + refSize));\n    offset = offset + refSize;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    let payload = { status: event, response: data };\n    return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload };\n  },\n  decodeBroadcast(buffer, view, decoder) {\n    let topicSize = view.getUint8(1);\n    let eventSize = view.getUint8(2);\n    let offset = this.HEADER_LENGTH + 2;\n    let topic = decoder.decode(buffer.slice(offset, offset + topicSize));\n    offset = offset + topicSize;\n    let event = decoder.decode(buffer.slice(offset, offset + eventSize));\n    offset = offset + eventSize;\n    let data = buffer.slice(offset, buffer.byteLength);\n    return { join_ref: null, ref: null, topic, event, payload: data };\n  }\n};\n\n// js/phoenix/socket.js\nvar Socket = class {\n  constructor(endPoint, opts = {}) {\n    this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };\n    this.channels = [];\n    this.sendBuffer = [];\n    this.ref = 0;\n    this.fallbackRef = null;\n    this.timeout = opts.timeout || DEFAULT_TIMEOUT;\n    this.transport = opts.transport || global.WebSocket || LongPoll;\n    this.primaryPassedHealthCheck = false;\n    this.longPollFallbackMs = opts.longPollFallbackMs;\n    this.fallbackTimer = null;\n    this.sessionStore = opts.sessionStorage || global && global.sessionStorage;\n    this.establishedConnections = 0;\n    this.defaultEncoder = serializer_default.encode.bind(serializer_default);\n    this.defaultDecoder = serializer_default.decode.bind(serializer_default);\n    this.closeWasClean = true;\n    this.disconnecting = false;\n    this.binaryType = opts.binaryType || \"arraybuffer\";\n    this.connectClock = 1;\n    this.pageHidden = false;\n    if (this.transport !== LongPoll) {\n      this.encode = opts.encode || this.defaultEncoder;\n      this.decode = opts.decode || this.defaultDecoder;\n    } else {\n      this.encode = this.defaultEncoder;\n      this.decode = this.defaultDecoder;\n    }\n    let awaitingConnectionOnPageShow = null;\n    if (phxWindow && phxWindow.addEventListener) {\n      phxWindow.addEventListener(\"pagehide\", (_e) => {\n        if (this.conn) {\n          this.disconnect();\n          awaitingConnectionOnPageShow = this.connectClock;\n        }\n      });\n      phxWindow.addEventListener(\"pageshow\", (_e) => {\n        if (awaitingConnectionOnPageShow === this.connectClock) {\n          awaitingConnectionOnPageShow = null;\n          this.connect();\n        }\n      });\n      phxWindow.addEventListener(\"visibilitychange\", () => {\n        if (document.visibilityState === \"hidden\") {\n          this.pageHidden = true;\n        } else {\n          this.pageHidden = false;\n          if (!this.isConnected() && !this.closeWasClean) {\n            this.teardown(() => this.connect());\n          }\n        }\n      });\n    }\n    this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4;\n    this.rejoinAfterMs = (tries) => {\n      if (opts.rejoinAfterMs) {\n        return opts.rejoinAfterMs(tries);\n      } else {\n        return [1e3, 2e3, 5e3][tries - 1] || 1e4;\n      }\n    };\n    this.reconnectAfterMs = (tries) => {\n      if (opts.reconnectAfterMs) {\n        return opts.reconnectAfterMs(tries);\n      } else {\n        return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3;\n      }\n    };\n    this.logger = opts.logger || null;\n    if (!this.logger && opts.debug) {\n      this.logger = (kind, msg, data) => {\n        console.log(`${kind}: ${msg}`, data);\n      };\n    }\n    this.longpollerTimeout = opts.longpollerTimeout || 2e4;\n    this.params = closure(opts.params || {});\n    this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`;\n    this.vsn = opts.vsn || DEFAULT_VSN;\n    this.heartbeatTimeoutTimer = null;\n    this.heartbeatTimer = null;\n    this.pendingHeartbeatRef = null;\n    this.reconnectTimer = new Timer(() => {\n      if (this.pageHidden) {\n        this.log(\"Not reconnecting as page is hidden!\");\n        this.teardown();\n        return;\n      }\n      this.teardown(() => this.connect());\n    }, this.reconnectAfterMs);\n    this.authToken = opts.authToken;\n  }\n  /**\n   * Returns the LongPoll transport reference\n   */\n  getLongPollTransport() {\n    return LongPoll;\n  }\n  /**\n   * Disconnects and replaces the active transport\n   *\n   * @param {Function} newTransport - The new transport class to instantiate\n   *\n   */\n  replaceTransport(newTransport) {\n    this.connectClock++;\n    this.closeWasClean = true;\n    clearTimeout(this.fallbackTimer);\n    this.reconnectTimer.reset();\n    if (this.conn) {\n      this.conn.close();\n      this.conn = null;\n    }\n    this.transport = newTransport;\n  }\n  /**\n   * Returns the socket protocol\n   *\n   * @returns {string}\n   */\n  protocol() {\n    return location.protocol.match(/^https/) ? \"wss\" : \"ws\";\n  }\n  /**\n   * The fully qualified socket url\n   *\n   * @returns {string}\n   */\n  endPointURL() {\n    let uri = Ajax.appendParams(\n      Ajax.appendParams(this.endPoint, this.params()),\n      { vsn: this.vsn }\n    );\n    if (uri.charAt(0) !== \"/\") {\n      return uri;\n    }\n    if (uri.charAt(1) === \"/\") {\n      return `${this.protocol()}:${uri}`;\n    }\n    return `${this.protocol()}://${location.host}${uri}`;\n  }\n  /**\n   * Disconnects the socket\n   *\n   * See https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes for valid status codes.\n   *\n   * @param {Function} callback - Optional callback which is called after socket is disconnected.\n   * @param {integer} code - A status code for disconnection (Optional).\n   * @param {string} reason - A textual description of the reason to disconnect. (Optional)\n   */\n  disconnect(callback, code, reason) {\n    this.connectClock++;\n    this.disconnecting = true;\n    this.closeWasClean = true;\n    clearTimeout(this.fallbackTimer);\n    this.reconnectTimer.reset();\n    this.teardown(() => {\n      this.disconnecting = false;\n      callback && callback();\n    }, code, reason);\n  }\n  /**\n   *\n   * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`\n   *\n   * Passing params to connect is deprecated; pass them in the Socket constructor instead:\n   * `new Socket(\"/socket\", {params: {user_id: userToken}})`.\n   */\n  connect(params) {\n    if (params) {\n      console && console.log(\"passing params to connect is deprecated. Instead pass :params to the Socket constructor\");\n      this.params = closure(params);\n    }\n    if (this.conn && !this.disconnecting) {\n      return;\n    }\n    if (this.longPollFallbackMs && this.transport !== LongPoll) {\n      this.connectWithFallback(LongPoll, this.longPollFallbackMs);\n    } else {\n      this.transportConnect();\n    }\n  }\n  /**\n   * Logs the message. Override `this.logger` for specialized logging. noops by default\n   * @param {string} kind\n   * @param {string} msg\n   * @param {Object} data\n   */\n  log(kind, msg, data) {\n    this.logger && this.logger(kind, msg, data);\n  }\n  /**\n   * Returns true if a logger has been set on this socket.\n   */\n  hasLogger() {\n    return this.logger !== null;\n  }\n  /**\n   * Registers callbacks for connection open events\n   *\n   * @example socket.onOpen(function(){ console.info(\"the socket was opened\") })\n   *\n   * @param {Function} callback\n   */\n  onOpen(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.open.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection close events\n   * @param {Function} callback\n   */\n  onClose(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.close.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection error events\n   *\n   * @example socket.onError(function(error){ alert(\"An error occurred\") })\n   *\n   * @param {Function} callback\n   */\n  onError(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.error.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Registers callbacks for connection message events\n   * @param {Function} callback\n   */\n  onMessage(callback) {\n    let ref = this.makeRef();\n    this.stateChangeCallbacks.message.push([ref, callback]);\n    return ref;\n  }\n  /**\n   * Pings the server and invokes the callback with the RTT in milliseconds\n   * @param {Function} callback\n   *\n   * Returns true if the ping was pushed or false if unable to be pushed.\n   */\n  ping(callback) {\n    if (!this.isConnected()) {\n      return false;\n    }\n    let ref = this.makeRef();\n    let startTime = Date.now();\n    this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref });\n    let onMsgRef = this.onMessage((msg) => {\n      if (msg.ref === ref) {\n        this.off([onMsgRef]);\n        callback(Date.now() - startTime);\n      }\n    });\n    return true;\n  }\n  /**\n   * @private\n   *\n   * @param {Function}\n   */\n  transportName(transport) {\n    switch (transport) {\n      case LongPoll:\n        return \"LongPoll\";\n      default:\n        return transport.name;\n    }\n  }\n  /**\n   * @private\n   */\n  transportConnect() {\n    this.connectClock++;\n    this.closeWasClean = false;\n    let protocols = void 0;\n    if (this.authToken) {\n      protocols = [\"phoenix\", `${AUTH_TOKEN_PREFIX}${btoa(this.authToken).replace(/=/g, \"\")}`];\n    }\n    this.conn = new this.transport(this.endPointURL(), protocols);\n    this.conn.binaryType = this.binaryType;\n    this.conn.timeout = this.longpollerTimeout;\n    this.conn.onopen = () => this.onConnOpen();\n    this.conn.onerror = (error) => this.onConnError(error);\n    this.conn.onmessage = (event) => this.onConnMessage(event);\n    this.conn.onclose = (event) => this.onConnClose(event);\n  }\n  getSession(key) {\n    return this.sessionStore && this.sessionStore.getItem(key);\n  }\n  storeSession(key, val) {\n    this.sessionStore && this.sessionStore.setItem(key, val);\n  }\n  connectWithFallback(fallbackTransport, fallbackThreshold = 2500) {\n    clearTimeout(this.fallbackTimer);\n    let established = false;\n    let primaryTransport = true;\n    let openRef, errorRef;\n    let fallbackTransportName = this.transportName(fallbackTransport);\n    let fallback = (reason) => {\n      this.log(\"transport\", `falling back to ${fallbackTransportName}...`, reason);\n      this.off([openRef, errorRef]);\n      primaryTransport = false;\n      this.replaceTransport(fallbackTransport);\n      this.transportConnect();\n    };\n    if (this.getSession(`phx:fallback:${fallbackTransportName}`)) {\n      return fallback(\"memorized\");\n    }\n    this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n    errorRef = this.onError((reason) => {\n      this.log(\"transport\", \"error\", reason);\n      if (primaryTransport && !established) {\n        clearTimeout(this.fallbackTimer);\n        fallback(reason);\n      }\n    });\n    if (this.fallbackRef) {\n      this.off([this.fallbackRef]);\n    }\n    this.fallbackRef = this.onOpen(() => {\n      established = true;\n      if (!primaryTransport) {\n        let fallbackTransportName2 = this.transportName(fallbackTransport);\n        if (!this.primaryPassedHealthCheck) {\n          this.storeSession(`phx:fallback:${fallbackTransportName2}`, \"true\");\n        }\n        return this.log(\"transport\", `established ${fallbackTransportName2} fallback`);\n      }\n      clearTimeout(this.fallbackTimer);\n      this.fallbackTimer = setTimeout(fallback, fallbackThreshold);\n      this.ping((rtt) => {\n        this.log(\"transport\", \"connected to primary after\", rtt);\n        this.primaryPassedHealthCheck = true;\n        clearTimeout(this.fallbackTimer);\n      });\n    });\n    this.transportConnect();\n  }\n  clearHeartbeats() {\n    clearTimeout(this.heartbeatTimer);\n    clearTimeout(this.heartbeatTimeoutTimer);\n  }\n  onConnOpen() {\n    if (this.hasLogger()) this.log(\"transport\", `${this.transportName(this.transport)} connected to ${this.endPointURL()}`);\n    this.closeWasClean = false;\n    this.disconnecting = false;\n    this.establishedConnections++;\n    this.flushSendBuffer();\n    this.reconnectTimer.reset();\n    this.resetHeartbeat();\n    this.stateChangeCallbacks.open.forEach(([, callback]) => callback());\n  }\n  /**\n   * @private\n   */\n  heartbeatTimeout() {\n    if (this.pendingHeartbeatRef) {\n      this.pendingHeartbeatRef = null;\n      if (this.hasLogger()) {\n        this.log(\"transport\", \"heartbeat timeout. Attempting to re-establish connection\");\n      }\n      this.triggerChanError();\n      this.closeWasClean = false;\n      this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, \"heartbeat timeout\");\n    }\n  }\n  resetHeartbeat() {\n    if (this.conn && this.conn.skipHeartbeat) {\n      return;\n    }\n    this.pendingHeartbeatRef = null;\n    this.clearHeartbeats();\n    this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n  }\n  teardown(callback, code, reason) {\n    if (!this.conn) {\n      return callback && callback();\n    }\n    const connToClose = this.conn;\n    this.waitForBufferDone(connToClose, () => {\n      if (code) {\n        connToClose.close(code, reason || \"\");\n      } else {\n        connToClose.close();\n      }\n      this.waitForSocketClosed(connToClose, () => {\n        if (this.conn === connToClose) {\n          this.conn.onopen = function() {\n          };\n          this.conn.onerror = function() {\n          };\n          this.conn.onmessage = function() {\n          };\n          this.conn.onclose = function() {\n          };\n          this.conn = null;\n        }\n        callback && callback();\n      });\n    });\n  }\n  waitForBufferDone(conn, callback, tries = 1) {\n    if (tries === 5 || !conn.bufferedAmount) {\n      callback();\n      return;\n    }\n    setTimeout(() => {\n      this.waitForBufferDone(conn, callback, tries + 1);\n    }, 150 * tries);\n  }\n  waitForSocketClosed(conn, callback, tries = 1) {\n    if (tries === 5 || conn.readyState === SOCKET_STATES.closed) {\n      callback();\n      return;\n    }\n    setTimeout(() => {\n      this.waitForSocketClosed(conn, callback, tries + 1);\n    }, 150 * tries);\n  }\n  onConnClose(event) {\n    if (this.conn) this.conn.onclose = () => {\n    };\n    let closeCode = event && event.code;\n    if (this.hasLogger()) this.log(\"transport\", \"close\", event);\n    this.triggerChanError();\n    this.clearHeartbeats();\n    if (!this.closeWasClean && closeCode !== 1e3) {\n      this.reconnectTimer.scheduleTimeout();\n    }\n    this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event));\n  }\n  /**\n   * @private\n   */\n  onConnError(error) {\n    if (this.hasLogger()) this.log(\"transport\", error);\n    let transportBefore = this.transport;\n    let establishedBefore = this.establishedConnections;\n    this.stateChangeCallbacks.error.forEach(([, callback]) => {\n      callback(error, transportBefore, establishedBefore);\n    });\n    if (transportBefore === this.transport || establishedBefore > 0) {\n      this.triggerChanError();\n    }\n  }\n  /**\n   * @private\n   */\n  triggerChanError() {\n    this.channels.forEach((channel) => {\n      if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) {\n        channel.trigger(CHANNEL_EVENTS.error);\n      }\n    });\n  }\n  /**\n   * @returns {string}\n   */\n  connectionState() {\n    switch (this.conn && this.conn.readyState) {\n      case SOCKET_STATES.connecting:\n        return \"connecting\";\n      case SOCKET_STATES.open:\n        return \"open\";\n      case SOCKET_STATES.closing:\n        return \"closing\";\n      default:\n        return \"closed\";\n    }\n  }\n  /**\n   * @returns {boolean}\n   */\n  isConnected() {\n    return this.connectionState() === \"open\";\n  }\n  /**\n   * @private\n   *\n   * @param {Channel}\n   */\n  remove(channel) {\n    this.off(channel.stateChangeRefs);\n    this.channels = this.channels.filter((c) => c !== channel);\n  }\n  /**\n   * Removes `onOpen`, `onClose`, `onError,` and `onMessage` registrations.\n   *\n   * @param {refs} - list of refs returned by calls to\n   *                 `onOpen`, `onClose`, `onError,` and `onMessage`\n   */\n  off(refs) {\n    for (let key in this.stateChangeCallbacks) {\n      this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => {\n        return refs.indexOf(ref) === -1;\n      });\n    }\n  }\n  /**\n   * Initiates a new channel for the given topic\n   *\n   * @param {string} topic\n   * @param {Object} chanParams - Parameters for the channel\n   * @returns {Channel}\n   */\n  channel(topic, chanParams = {}) {\n    let chan = new Channel(topic, chanParams, this);\n    this.channels.push(chan);\n    return chan;\n  }\n  /**\n   * @param {Object} data\n   */\n  push(data) {\n    if (this.hasLogger()) {\n      let { topic, event, payload, ref, join_ref } = data;\n      this.log(\"push\", `${topic} ${event} (${join_ref}, ${ref})`, payload);\n    }\n    if (this.isConnected()) {\n      this.encode(data, (result) => this.conn.send(result));\n    } else {\n      this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result)));\n    }\n  }\n  /**\n   * Return the next message ref, accounting for overflows\n   * @returns {string}\n   */\n  makeRef() {\n    let newRef = this.ref + 1;\n    if (newRef === this.ref) {\n      this.ref = 0;\n    } else {\n      this.ref = newRef;\n    }\n    return this.ref.toString();\n  }\n  sendHeartbeat() {\n    if (this.pendingHeartbeatRef && !this.isConnected()) {\n      return;\n    }\n    this.pendingHeartbeatRef = this.makeRef();\n    this.push({ topic: \"phoenix\", event: \"heartbeat\", payload: {}, ref: this.pendingHeartbeatRef });\n    this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs);\n  }\n  flushSendBuffer() {\n    if (this.isConnected() && this.sendBuffer.length > 0) {\n      this.sendBuffer.forEach((callback) => callback());\n      this.sendBuffer = [];\n    }\n  }\n  onConnMessage(rawMessage) {\n    this.decode(rawMessage.data, (msg) => {\n      let { topic, event, payload, ref, join_ref } = msg;\n      if (ref && ref === this.pendingHeartbeatRef) {\n        this.clearHeartbeats();\n        this.pendingHeartbeatRef = null;\n        this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs);\n      }\n      if (this.hasLogger()) this.log(\"receive\", `${payload.status || \"\"} ${topic} ${event} ${ref && \"(\" + ref + \")\" || \"\"}`, payload);\n      for (let i = 0; i < this.channels.length; i++) {\n        const channel = this.channels[i];\n        if (!channel.isMember(topic, event, payload, join_ref)) {\n          continue;\n        }\n        channel.trigger(event, payload, ref, join_ref);\n      }\n      for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) {\n        let [, callback] = this.stateChangeCallbacks.message[i];\n        callback(msg);\n      }\n    });\n  }\n  leaveOpenTopic(topic) {\n    let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining()));\n    if (dupChannel) {\n      if (this.hasLogger()) this.log(\"transport\", `leaving duplicate topic \"${topic}\"`);\n      dupChannel.leave();\n    }\n  }\n};\nexport {\n  Channel,\n  LongPoll,\n  Presence,\n  serializer_default as Serializer,\n  Socket\n};\n//# sourceMappingURL=phoenix.mjs.map\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/AGENTS.md.eex",
    "content": "## Authentication\n\n- **Always** handle authentication flow at the router level with proper redirects\n- **Always** be mindful of where to place routes. `phx.gen.auth` creates multiple router plugs<%= if live? do %> and `live_session` scopes<% end %>:\n  - A plug `:fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>` that is included in the default browser pipeline\n  - A plug `:require_authenticated_<%= schema.singular %>` that redirects to the log in page when the <%= schema.singular %> is not authenticated<%= if live? do %>\n  - A `live_session :current_<%= schema.singular %>` scope - for routes that need the current <%= schema.singular %> but don't require authentication, similar to `:fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>`\n  - A `live_session :require_authenticated_<%= schema.singular %>` scope - for routes that require authentication, similar to the plug with the same name<% end %>\n  - In both cases, a `@<%= scope_config.scope.assign_key %>` is assigned to the Plug connection<%= if live? do %> and LiveView socket<% end %>\n  - A plug `redirect_if_<%= schema.singular %>_is_authenticated` that redirects to a default path in case the <%= schema.singular %> is authenticated - useful for a registration page that should only be shown to unauthenticated <%= schema.plural %>\n- **Always let the user know in which router scopes<%= if live? do%>, `live_session`,<% end %> and pipeline you are placing the route, AND SAY WHY**\n- `phx.gen.auth` assigns the `<%= scope_config.scope.assign_key %>` assign - it **does not assign a `current_<%= schema.singular %>` assign**\n- Always pass the assign `<%= scope_config.scope.assign_key %>` to context modules as first argument. When performing queries, use `<%= scope_config.scope.assign_key %>.<%= schema.singular %>` to filter the query results\n- To derive/access `current_<%= schema.singular %>` in templates, **always use the `@<%= scope_config.scope.assign_key %>.<%= schema.singular %>`**, never use **`@current_<%= schema.singular %>`** in templates<%= if live? do %> or LiveViews\n- **Never** duplicate `live_session` names. A `live_session :current_<%= schema.singular %>` can only be defined __once__ in the router, so all routes for the `live_session :current_<%= schema.singular %>`  must be grouped in a single block<% end %>\n- Anytime you hit `<%= scope_config.scope.assign_key %>` errors or the logged in session isn't displaying the right content, **always double check the router and ensure you are using the correct plug<%= if live? do %> and `live_session`<% end %> as described below**\n\n### Routes that require authentication\n\n<%= if live? do %>LiveViews that require login should **always be placed inside the __existing__ `live_session :require_authenticated_<%= schema.singular %>` block**:\n\n    scope \"/\", AppWeb do\n      pipe_through [:browser, :require_authenticated_<%= schema.singular %>]\n\n      live_session :require_authenticated_<%= schema.singular %>,\n        on_mount: [{<%= inspect auth_module %>, :require_authenticated}] do\n        # phx.gen.auth generated routes\n        live \"/<%= schema.plural %>/settings\", <%= inspect schema.alias %>Live.Settings, :edit\n        live \"/<%= schema.plural %>/settings/confirm-email/:token\", <%= inspect schema.alias %>Live.Settings, :confirm_email\n        # our own routes that require logged in <%= schema.singular %>\n        live \"/\", MyLiveThatRequiresAuth, :index\n      end\n    end\n\n<% end %>Controller routes must be placed in a scope that sets the `:require_authenticated_<%= schema.singular %>` plug:\n\n    scope \"/\", AppWeb do\n      pipe_through [:browser, :require_authenticated_<%= schema.singular %>]\n\n      get \"/\", MyControllerThatRequiresAuth, :index\n    end\n\n### Routes that work with or without authentication\n\n<%= if live? do %>LiveViews that can work with or without authentication, **always use the __existing__ `:current_<%= schema.singular %>` scope**, ie:\n\n    scope \"/\", MyAppWeb do\n      pipe_through [:browser]\n\n      live_session :current_<%= schema.singular %>,\n        on_mount: [{<%= inspect auth_module %>, :mount_<%= scope_config.scope.assign_key %>}] do\n        # our own routes that work with or without authentication\n        live \"/\", PublicLive\n      end\n    end\n\n<% end %>Controllers automatically have the `<%= scope_config.scope.assign_key %>` available if they use the `:browser` pipeline.\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/auth.ex.eex",
    "content": "defmodule <%= inspect auth_module %> do\n  use <%= inspect context.web_module %>, :verified_routes\n\n  import Plug.Conn\n  import Phoenix.Controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect scope_config.scope.module %>\n\n  # Make the remember me cookie valid for 14 days. This should match\n  # the session validity setting in <%= inspect schema.alias %>Token.\n  @max_cookie_age_in_days 14\n  @remember_me_cookie \"_<%= web_app_name %>_<%= schema.singular %>_remember_me\"\n  @remember_me_options [\n    sign: true,\n    max_age: @max_cookie_age_in_days * 24 * 60 * 60,\n    same_site: \"Lax\"\n  ]\n\n  # How old the session token should be before a new one is issued. When a request is made\n  # with a session token older than this value, then a new session token will be created\n  # and the session and remember-me cookies (if set) will be updated with the new token.\n  # Lowering this value will result in more tokens being created by active users. Increasing\n  # it will result in less time before a session token expires for a user to get issued a new\n  # token. This can be set to a value greater than `@max_cookie_age_in_days` to disable\n  # the reissuing of tokens completely.\n  @session_reissue_age_in_days 7\n\n  @doc \"\"\"\n  Logs the <%= schema.singular %> in.\n\n  Redirects to the session's `:<%= schema.singular %>_return_to` path\n  or falls back to the `signed_in_path/1`.\n  \"\"\"\n  def log_in_<%= schema.singular %>(conn, <%= schema.singular %>, params \\\\ %{}) do\n    <%= schema.singular %>_return_to = get_session(conn, :<%= schema.singular %>_return_to)\n\n    conn\n    |> create_or_extend_session(<%= schema.singular %>, params)\n    |> redirect(to: <%= schema.singular %>_return_to || signed_in_path(conn))\n  end\n\n  @doc \"\"\"\n  Logs the <%= schema.singular %> out.\n\n  It clears all session data for safety. See renew_session.\n  \"\"\"\n  def log_out_<%= schema.singular %>(conn) do\n    <%= schema.singular %>_token = get_session(conn, :<%= schema.singular %>_token)\n    <%= schema.singular %>_token && <%= inspect context.alias %>.delete_<%= schema.singular %>_session_token(<%= schema.singular %>_token)\n\n    if live_socket_id = get_session(conn, :live_socket_id) do\n      <%= inspect(endpoint_module) %>.broadcast(live_socket_id, \"disconnect\", %{})\n    end\n\n    conn\n    |> renew_session(nil)\n    |> delete_resp_cookie(@remember_me_cookie, @remember_me_options)\n    |> redirect(to: ~p\"/\")\n  end\n\n  @doc \"\"\"\n  Authenticates the <%= schema.singular %> by looking into the session and remember me token.\n\n  Will reissue the session token if it is older than the configured age.\n  \"\"\"\n  def fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>(conn, _opts) do\n    with {token, conn} <- ensure_<%= schema.singular %>_token(conn),\n         {<%= schema.singular %>, token_inserted_at} <- <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token) do\n      conn\n      |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n      |> maybe_reissue_<%= schema.singular %>_session_token(<%= schema.singular %>, token_inserted_at)\n    else\n      nil -> assign(conn, :<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(nil))\n    end\n  end\n\n  defp ensure_<%= schema.singular %>_token(conn) do\n    if token = get_session(conn, :<%= schema.singular %>_token) do\n      {token, conn}\n    else\n      conn = fetch_cookies(conn, signed: [@remember_me_cookie])\n\n      if token = conn.cookies[@remember_me_cookie] do\n        {token, conn |> put_token_in_session(token) |> put_session(:<%= schema.singular %>_remember_me, true)}\n      else\n        nil\n      end\n    end\n  end\n\n  # Reissue the session token if it is older than the configured reissue age.\n  defp maybe_reissue_<%= schema.singular %>_session_token(conn, <%= schema.singular %>, token_inserted_at) do\n    token_age = <%= inspect datetime_module %>.diff(<%= datetime_now %>, token_inserted_at, :day)\n\n    if token_age >= @session_reissue_age_in_days do\n      create_or_extend_session(conn, <%= schema.singular %>, %{})\n    else\n      conn\n    end\n  end\n\n  # This function is the one responsible for creating session tokens\n  # and storing them safely in the session and cookies. It may be called\n  # either when logging in, during sudo mode, or to renew a session which\n  # will soon expire.\n  #\n  # When the session is created, rather than extended, the renew_session\n  # function will clear the session to avoid fixation attacks. See the\n  # renew_session function to customize this behaviour.\n  defp create_or_extend_session(conn, <%= schema.singular %>, params) do\n    token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n    remember_me = get_session(conn, :<%= schema.singular %>_remember_me)\n\n    conn\n    |> renew_session(<%= schema.singular %>)\n    |> put_token_in_session(token)\n    |> maybe_write_remember_me_cookie(token, params, remember_me)\n  end\n\n  # Do not renew session if the <%= schema.singular %> is already logged in\n  # to prevent CSRF errors or data being lost in tabs that are still open\n  defp renew_session(conn, <%= schema.singular %>) when conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id do\n    conn\n  end\n\n  # This function renews the session ID and erases the whole\n  # session to avoid fixation attacks. If there is any data\n  # in the session you may want to preserve after log in/log out,\n  # you must explicitly fetch the session data before clearing\n  # and then immediately set it after clearing, for example:\n  #\n  #     defp renew_session(conn, _<%= schema.singular %>) do\n  #       delete_csrf_token()\n  #       preferred_locale = get_session(conn, :preferred_locale)\n  #\n  #       conn\n  #       |> configure_session(renew: true)\n  #       |> clear_session()\n  #       |> put_session(:preferred_locale, preferred_locale)\n  #     end\n  #\n  defp renew_session(conn, _<%= schema.singular %>) do\n    delete_csrf_token()\n\n    conn\n    |> configure_session(renew: true)\n    |> clear_session()\n  end\n\n  defp maybe_write_remember_me_cookie(conn, token, %{\"remember_me\" => \"true\"}, _),\n    do: write_remember_me_cookie(conn, token)\n\n  defp maybe_write_remember_me_cookie(conn, token, _params, true),\n    do: write_remember_me_cookie(conn, token)\n\n  defp maybe_write_remember_me_cookie(conn, _token, _params, _), do: conn\n\n  defp write_remember_me_cookie(conn, token) do\n    conn\n    |> put_session(:<%= schema.singular %>_remember_me, true)\n    |> put_resp_cookie(@remember_me_cookie, token, @remember_me_options)\n  end\n\n  <%= if live? do %>defp put_token_in_session(conn, token) do\n    conn\n    |> put_session(:<%= schema.singular %>_token, token)\n    |> put_session(:live_socket_id, <%= schema.singular %>_session_topic(token))\n  end\n\n  @doc \"\"\"\n  Disconnects existing sockets for the given tokens.\n  \"\"\"\n  def disconnect_sessions(tokens) do\n    Enum.each(tokens, fn %{token: token} ->\n      <%= inspect endpoint_module %>.broadcast(<%= schema.singular %>_session_topic(token), \"disconnect\", %{})\n    end)\n  end\n\n  defp <%= schema.singular %>_session_topic(token), do: \"<%= schema.plural %>_sessions:#{Base.url_encode64(token)}\"\n\n  @doc \"\"\"\n  Handles mounting and authenticating the <%= scope_config.scope.assign_key %> in LiveViews.\n\n  ## `on_mount` arguments\n\n    * `:mount_<%= scope_config.scope.assign_key %>` - Assigns <%= scope_config.scope.assign_key %>\n      to socket assigns based on <%= schema.singular %>_token, or nil if\n      there's no <%= schema.singular %>_token or no matching <%= schema.singular %>.\n\n    * `:require_authenticated` - Authenticates the <%= schema.singular %> from the session,\n      and assigns the <%= scope_config.scope.assign_key %> to socket assigns based\n      on <%= schema.singular %>_token.\n      Redirects to login page if there's no logged <%= schema.singular %>.\n\n  ## Examples\n\n  Use the `on_mount` lifecycle macro in LiveViews to mount or authenticate\n  the `<%= scope_config.scope.assign_key %>`:\n\n      defmodule <%= inspect context.web_module %>.PageLive do\n        use <%= inspect context.web_module %>, :live_view\n\n        on_mount {<%= inspect auth_module %>, :mount_<%= scope_config.scope.assign_key %>}\n        ...\n      end\n\n  Or use the `live_session` of your router to invoke the on_mount callback:\n\n      live_session :authenticated, on_mount: [{<%= inspect auth_module %>, :require_authenticated}] do\n        live \"/profile\", ProfileLive, :index\n      end\n  \"\"\"\n  def on_mount(:mount_<%= scope_config.scope.assign_key %>, _params, session, socket) do\n    {:cont, mount_<%= scope_config.scope.assign_key %>(socket, session)}\n  end\n\n  def on_mount(:require_authenticated, _params, session, socket) do\n    socket = mount_<%= scope_config.scope.assign_key %>(socket, session)\n\n    if socket.assigns.<%= scope_config.scope.assign_key %> && socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %> do\n      {:cont, socket}\n    else\n      socket =\n        socket\n        |> Phoenix.LiveView.put_flash(:error, \"You must log in to access this page.\")\n        |> Phoenix.LiveView.redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:halt, socket}\n    end\n  end\n\n  def on_mount(:require_sudo_mode, _params, session, socket) do\n    socket = mount_<%= scope_config.scope.assign_key %>(socket, session)\n\n    if <%= inspect context.alias %>.sudo_mode?(socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>, -10) do\n      {:cont, socket}\n    else\n      socket =\n        socket\n        |> Phoenix.LiveView.put_flash(:error, \"You must re-authenticate to access this page.\")\n        |> Phoenix.LiveView.redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:halt, socket}\n    end\n  end\n\n  defp mount_<%= scope_config.scope.assign_key %>(socket, session) do\n    Phoenix.Component.assign_new(socket, :<%= scope_config.scope.assign_key %>, fn ->\n      {<%= schema.singular %>, _} =\n        if <%= schema.singular %>_token = session[\"<%= schema.singular %>_token\"] do\n          <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)\n        end || {nil, nil}\n\n      <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>)\n    end)\n  end\n\n  @doc \"Returns the path to redirect to after log in.\"\n  # the <%= schema.singular %> was already logged in, redirect to settings\n  def signed_in_path(%Plug.Conn{assigns: %{<%= scope_config.scope.assign_key %>: %<%= inspect scope_config.scope.alias %>{<%= schema.singular %>: %<%= inspect context.alias %>.<%= inspect schema.alias %>{}}}}) do\n    ~p\"<%= schema.route_prefix %>/settings\"\n  end\n\n  def signed_in_path(_), do: ~p\"/\"\n\n  <% else %>defp put_token_in_session(conn, token) do\n    put_session(conn, :<%= schema.singular %>_token, token)\n  end\n\n  @doc \"\"\"\n  Plug for routes that require sudo mode.\n  \"\"\"\n  def require_sudo_mode(conn, _opts) do\n    if <%= inspect context.alias %>.sudo_mode?(conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>, -10) do\n      conn\n    else\n      conn\n      |> put_flash(:error, \"You must re-authenticate to access this page.\")\n      |> maybe_store_return_to()\n      |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n      |> halt()\n    end\n  end\n\n  @doc \"\"\"\n  Plug for routes that require the <%= schema.singular %> to not be authenticated.\n  \"\"\"\n  def redirect_if_<%= schema.singular %>_is_authenticated(conn, _opts) do\n    if conn.assigns.<%= scope_config.scope.assign_key %> do\n      conn\n      |> redirect(to: signed_in_path(conn))\n      |> halt()\n    else\n      conn\n    end\n  end\n\n  defp signed_in_path(_conn), do: ~p\"/\"\n\n  <% end %>@doc \"\"\"\n  Plug for routes that require the <%= schema.singular %> to be authenticated.\n  \"\"\"\n  def require_authenticated_<%= schema.singular %>(conn, _opts) do\n    if conn.assigns.<%= scope_config.scope.assign_key %> && conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %> do\n      conn\n    else\n      conn\n      |> put_flash(:error, \"You must log in to access this page.\")\n      |> maybe_store_return_to()\n      |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n      |> halt()\n    end\n  end\n\n  defp maybe_store_return_to(%{method: \"GET\"} = conn) do\n    put_session(conn, :<%= schema.singular %>_return_to, current_path(conn))\n  end\n\n  defp maybe_store_return_to(conn), do: conn\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/auth_test.exs.eex",
    "content": "defmodule <%= inspect auth_module %>Test do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  <%= if live? do %>alias Phoenix.LiveView\n  <% end %>alias <%= inspect context.module %>\n  alias <%= inspect context.module %>.<%= inspect scope_config.scope.alias %>\n  alias <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Auth\n\n  import <%= inspect context.module %>Fixtures\n\n  @remember_me_cookie \"_<%= web_app_name %>_<%= schema.singular %>_remember_me\"\n  @remember_me_cookie_max_age 60 * 60 * 24 * 14\n\n  setup %{conn: conn} do\n    conn =\n      conn\n      |> Map.replace!(:secret_key_base, <%= inspect endpoint_module %>.config(:secret_key_base))\n      |> init_test_session(%{})\n\n    %{<%= schema.singular %>: %{<%= schema.singular %>_fixture() | authenticated_at: <%= datetime_now %>}, conn: conn}\n  end\n\n  describe \"log_in_<%= schema.singular %>/3\" do\n    test \"stores the <%= schema.singular %> token in the session\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(conn, <%= schema.singular %>)\n      assert token = get_session(conn, :<%= schema.singular %>_token)<%= if live? do %>\n      assert get_session(conn, :live_socket_id) == \"<%= schema.plural %>_sessions:#{Base.url_encode64(token)}\"<% end %>\n      assert redirected_to(conn) == ~p\"/\"\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token)\n    end\n\n    test \"clears everything previously stored in the session\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = conn |> put_session(:to_be_removed, \"value\") |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n      refute get_session(conn, :to_be_removed)\n    end\n\n    test \"keeps session when re-authenticating\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        conn\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> put_session(:to_be_removed, \"value\")\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n\n      assert get_session(conn, :to_be_removed)\n    end\n\n    test \"clears session when <%= schema.singular %> does not match when re-authenticating\", %{\n      conn: conn,\n      <%= schema.singular %>: <%= schema.singular %>\n    } do\n      other_<%= schema.singular %> = <%= schema.singular %>_fixture()\n\n      conn =\n        conn\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(other_<%= schema.singular %>))\n        |> put_session(:to_be_removed, \"value\")\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n\n      refute get_session(conn, :to_be_removed)\n    end\n\n    test \"redirects to the configured path\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = conn |> put_session(:<%= schema.singular %>_return_to, \"/hello\") |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n      assert redirected_to(conn) == \"/hello\"\n    end\n\n    test \"writes a cookie if remember_me is configured\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = conn |> fetch_cookies() |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, %{\"remember_me\" => \"true\"})\n      assert get_session(conn, :<%= schema.singular %>_token) == conn.cookies[@remember_me_cookie]\n      assert get_session(conn, :<%= schema.singular %>_remember_me) == true\n\n      assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]\n      assert signed_token != get_session(conn, :<%= schema.singular %>_token)\n      assert max_age == @remember_me_cookie_max_age\n    end<%= if live? do %>\n\n    test \"redirects to settings when <%= schema.singular %> is already logged in\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        conn\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/settings\"\n    end<% end %>\n\n    test \"writes a cookie if remember_me was set in previous session\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = conn |> fetch_cookies() |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, %{\"remember_me\" => \"true\"})\n      assert get_session(conn, :<%= schema.singular %>_token) == conn.cookies[@remember_me_cookie]\n      assert get_session(conn, :<%= schema.singular %>_remember_me) == true\n\n      conn =\n        conn\n        |> recycle()\n        |> Map.replace!(:secret_key_base, <%= inspect endpoint_module %>.config(:secret_key_base))\n        |> fetch_cookies()\n        |> init_test_session(%{<%= schema.singular %>_remember_me: true})\n\n      # the conn is already logged in and has the remember_me cookie set,\n      # now we log in again and even without explicitly setting remember_me,\n      # the cookie should be set again\n      conn = conn |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, %{})\n      assert %{value: signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]\n      assert signed_token != get_session(conn, :<%= schema.singular %>_token)\n      assert max_age == @remember_me_cookie_max_age\n      assert get_session(conn, :<%= schema.singular %>_remember_me) == true\n    end\n  end\n\n  describe \"logout_<%= schema.singular %>/1\" do\n    test \"erases session and cookies\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n\n      conn =\n        conn\n        |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token)\n        |> put_req_cookie(@remember_me_cookie, <%= schema.singular %>_token)\n        |> fetch_cookies()\n        |> <%= inspect schema.alias %>Auth.log_out_<%= schema.singular %>()\n\n      refute get_session(conn, :<%= schema.singular %>_token)\n      refute conn.cookies[@remember_me_cookie]\n      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]\n      assert redirected_to(conn) == ~p\"/\"\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)\n    end\n\n    <%= if live? do %>test \"broadcasts to the given live_socket_id\", %{conn: conn} do\n      live_socket_id = \"<%= schema.plural %>_sessions:abcdef-token\"\n      <%= inspect(endpoint_module) %>.subscribe(live_socket_id)\n\n      conn\n      |> put_session(:live_socket_id, live_socket_id)\n      |> <%= inspect(schema.alias) %>Auth.log_out_<%= schema.singular %>()\n\n      assert_receive %Phoenix.Socket.Broadcast{event: \"disconnect\", topic: ^live_socket_id}\n    end\n\n    <% end %>test \"works even if <%= schema.singular %> is already logged out\", %{conn: conn} do\n      conn = conn |> fetch_cookies() |> <%= inspect schema.alias %>Auth.log_out_<%= schema.singular %>()\n      refute get_session(conn, :<%= schema.singular %>_token)\n      assert %{max_age: 0} = conn.resp_cookies[@remember_me_cookie]\n      assert redirected_to(conn) == ~p\"/\"\n    end\n  end\n\n  describe \"fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>/2\" do\n    test \"authenticates <%= schema.singular %> from session\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n\n      conn =\n        conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>([])\n\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.authenticated_at == <%= schema.singular %>.authenticated_at\n      assert get_session(conn, :<%= schema.singular %>_token) == <%= schema.singular %>_token\n    end\n\n    test \"authenticates <%= schema.singular %> from cookies\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      logged_in_conn =\n        conn |> fetch_cookies() |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, %{\"remember_me\" => \"true\"})\n\n      <%= schema.singular %>_token = logged_in_conn.cookies[@remember_me_cookie]\n      %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]\n\n      conn =\n        conn\n        |> put_req_cookie(@remember_me_cookie, signed_token)\n        |> <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>([])\n\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.authenticated_at == <%= schema.singular %>.authenticated_at\n      assert get_session(conn, :<%= schema.singular %>_token) == <%= schema.singular %>_token\n      assert get_session(conn, :<%= schema.singular %>_remember_me)<%= if live? do %>\n\n      assert get_session(conn, :live_socket_id) ==\n               \"<%= schema.plural %>_sessions:#{Base.url_encode64(<%= schema.singular %>_token)}\"<% end %>\n    end\n\n    test \"does not authenticate if data is missing\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      _ = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      conn = <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>(conn, [])\n      refute get_session(conn, :<%= schema.singular %>_token)\n      refute conn.assigns.<%= scope_config.scope.assign_key %>\n    end\n\n    test \"reissues a new token after a few days and refreshes cookie\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      logged_in_conn =\n        conn |> fetch_cookies() |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, %{\"remember_me\" => \"true\"})\n\n      token = logged_in_conn.cookies[@remember_me_cookie]\n      %{value: signed_token} = logged_in_conn.resp_cookies[@remember_me_cookie]\n\n      offset_<%= schema.singular %>_token(token, -10, :day)\n      {<%= schema.singular %>, _} = <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token)\n\n      conn =\n        conn\n        |> put_session(:<%= schema.singular %>_token, token)\n        |> put_session(:<%= schema.singular %>_remember_me, true)\n        |> put_req_cookie(@remember_me_cookie, signed_token)\n        |> <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>([])\n\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id\n      assert conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.authenticated_at == <%= schema.singular %>.authenticated_at\n      assert new_token = get_session(conn, :<%= schema.singular %>_token)\n      assert new_token != token\n      assert %{value: new_signed_token, max_age: max_age} = conn.resp_cookies[@remember_me_cookie]\n      assert new_signed_token != signed_token\n      assert max_age == @remember_me_cookie_max_age\n    end\n  end\n\n  <%= if live? do %>describe \"on_mount :mount_<%= scope_config.scope.assign_key %>\" do\n    setup %{conn: conn} do\n      %{conn: <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>(conn, [])}\n    end\n\n    test \"assigns <%= scope_config.scope.assign_key %> based on a valid <%= schema.singular %>_token\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      {:cont, updated_socket} =\n        <%= inspect schema.alias %>Auth.on_mount(:mount_<%= scope_config.scope.assign_key %>, %{}, session, %LiveView.Socket{})\n\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id\n    end\n\n    test \"assigns nil to <%= scope_config.scope.assign_key %> assign if there isn't a valid <%= schema.singular %>_token\", %{conn: conn} do\n      <%= schema.singular %>_token = \"invalid_token\"\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      {:cont, updated_socket} =\n        <%= inspect schema.alias %>Auth.on_mount(:mount_<%= scope_config.scope.assign_key %>, %{}, session, %LiveView.Socket{})\n\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %> == nil\n    end\n\n    test \"assigns nil to <%= scope_config.scope.assign_key %> assign if there isn't a <%= schema.singular %>_token\", %{conn: conn} do\n      session = conn |> get_session()\n\n      {:cont, updated_socket} =\n        <%= inspect schema.alias %>Auth.on_mount(:mount_<%= scope_config.scope.assign_key %>, %{}, session, %LiveView.Socket{})\n\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %> == nil\n    end\n  end\n\n  describe \"on_mount :require_authenticated\" do\n    test \"authenticates <%= scope_config.scope.assign_key %> based on a valid <%= schema.singular %>_token\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      {:cont, updated_socket} =\n        <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, %LiveView.Socket{})\n\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>.id == <%= schema.singular %>.id\n    end\n\n    test \"redirects to login page if there isn't a valid <%= schema.singular %>_token\", %{conn: conn} do\n      <%= schema.singular %>_token = \"invalid_token\"\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      socket = %LiveView.Socket{\n        endpoint: <%= inspect context.web_module %>.Endpoint,\n        assigns: %{__changed__: %{}, flash: %{}}\n      }\n\n      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, socket)\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %> == nil\n    end\n\n    test \"redirects to login page if there isn't a <%= schema.singular %>_token\", %{conn: conn} do\n      session = conn |> get_session()\n\n      socket = %LiveView.Socket{\n        endpoint: <%= inspect context.web_module %>.Endpoint,\n        assigns: %{__changed__: %{}, flash: %{}}\n      }\n\n      {:halt, updated_socket} = <%= inspect schema.alias %>Auth.on_mount(:require_authenticated, %{}, session, socket)\n      assert updated_socket.assigns.<%= scope_config.scope.assign_key %> == nil\n    end\n  end\n\n  describe \"on_mount :require_sudo_mode\" do\n    test \"allows <%= schema.plural %> that have authenticated in the last 10 minutes\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      socket = %LiveView.Socket{\n        endpoint: <%= inspect(endpoint_module) %>,\n        assigns: %{__changed__: %{}, flash: %{}}\n      }\n\n      assert {:cont, _updated_socket} =\n               <%= inspect schema.alias %>Auth.on_mount(:require_sudo_mode, %{}, session, socket)\n    end\n\n    test \"redirects when authentication is too old\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      eleven_minutes_ago = <%= datetime_now %> |> <%= inspect datetime_module %>.add(-11, :minute)\n      <%= schema.singular %> = %{<%= schema.singular %> | authenticated_at: eleven_minutes_ago}\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      {<%= schema.singular %>, token_inserted_at} = <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)\n      assert <%= inspect datetime_module %>.compare(token_inserted_at, <%= schema.singular %>.authenticated_at) == :gt\n      session = conn |> put_session(:<%= schema.singular %>_token, <%= schema.singular %>_token) |> get_session()\n\n      socket = %LiveView.Socket{\n        endpoint: <%= inspect context.web_module %>.Endpoint,\n        assigns: %{__changed__: %{}, flash: %{}}\n      }\n\n      assert {:halt, _updated_socket} =\n               <%= inspect schema.alias %>Auth.on_mount(:require_sudo_mode, %{}, session, socket)\n    end\n  end<% else %>describe \"require_sudo_mode/2\" do\n    test \"allows <%= schema.plural %> that have authenticated in the last 10 minutes\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        conn\n        |> fetch_flash()\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> <%= inspect schema.alias %>Auth.require_sudo_mode([])\n\n      refute conn.halted\n      refute conn.status\n    end\n\n    test \"redirects when authentication is too old\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      eleven_minutes_ago = <%= datetime_now %> |> <%= inspect datetime_module %>.add(-11, :minute)\n      <%= schema.singular %> = %{<%= schema.singular %> | authenticated_at: eleven_minutes_ago}\n      <%= schema.singular %>_token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      {<%= schema.singular %>, token_inserted_at} = <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(<%= schema.singular %>_token)\n      assert <%= inspect datetime_module %>.compare(token_inserted_at, <%= schema.singular %>.authenticated_at) == :gt\n\n      conn =\n        conn\n        |> fetch_flash()\n        |> assign(:<%= scope_config.scope.assign_key %>, Scope.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> <%= inspect schema.alias %>Auth.require_sudo_mode([])\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==\n               \"You must re-authenticate to access this page.\"\n    end\n  end\n\n  describe \"redirect_if_<%= schema.singular %>_is_authenticated/2\" do\n    setup %{conn: conn} do\n      %{conn: <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>(conn, [])}\n    end\n\n    test \"redirects if <%= schema.singular %> is authenticated\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        conn\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> <%= inspect schema.alias %>Auth.redirect_if_<%= schema.singular %>_is_authenticated([])\n\n      assert conn.halted\n      assert redirected_to(conn) == ~p\"/\"\n    end\n\n    test \"does not redirect if <%= schema.singular %> is not authenticated\", %{conn: conn} do\n      conn = <%= inspect schema.alias %>Auth.redirect_if_<%= schema.singular %>_is_authenticated(conn, [])\n      refute conn.halted\n      refute conn.status\n    end\n  end<% end %>\n\n  describe \"require_authenticated_<%= schema.singular %>/2\" do\n    setup %{conn: conn} do\n      %{conn: <%= inspect schema.alias %>Auth.fetch_<%= scope_config.scope.assign_key %>_for_<%= schema.singular %>(conn, [])}\n    end\n\n    test \"redirects if <%= schema.singular %> is not authenticated\", %{conn: conn} do\n      conn = conn |> fetch_flash() |> <%= inspect schema.alias %>Auth.require_authenticated_<%= schema.singular %>([])\n      assert conn.halted\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==\n               \"You must log in to access this page.\"\n    end\n\n    test \"stores the path to redirect to on GET\", %{conn: conn} do\n      halted_conn =\n        %{conn | path_info: [\"foo\"], query_string: \"\"}\n        |> fetch_flash()\n        |> <%= inspect schema.alias %>Auth.require_authenticated_<%= schema.singular %>([])\n\n      assert halted_conn.halted\n      assert get_session(halted_conn, :<%= schema.singular %>_return_to) == \"/foo\"\n\n      halted_conn =\n        %{conn | path_info: [\"foo\"], query_string: \"bar=baz\"}\n        |> fetch_flash()\n        |> <%= inspect schema.alias %>Auth.require_authenticated_<%= schema.singular %>([])\n\n      assert halted_conn.halted\n      assert get_session(halted_conn, :<%= schema.singular %>_return_to) == \"/foo?bar=baz\"\n\n      halted_conn =\n        %{conn | path_info: [\"foo\"], query_string: \"bar\", method: \"POST\"}\n        |> fetch_flash()\n        |> <%= inspect schema.alias %>Auth.require_authenticated_<%= schema.singular %>([])\n\n      assert halted_conn.halted\n      refute get_session(halted_conn, :<%= schema.singular %>_return_to)\n    end\n\n    test \"does not redirect if <%= schema.singular %> is authenticated\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        conn\n        |> assign(:<%= scope_config.scope.assign_key %>, <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>))\n        |> <%= inspect schema.alias %>Auth.require_authenticated_<%= schema.singular %>([])\n\n      refute conn.halted\n      refute conn.status\n    end\n  end<%= if live? do %>\n\n  describe \"disconnect_sessions/1\" do\n    test \"broadcasts disconnect messages for each token\" do\n      tokens = [%{token: \"token1\"}, %{token: \"token2\"}]\n\n      for %{token: token} <- tokens do\n        <%= inspect context.web_module %>.Endpoint.subscribe(\"<%= schema.plural %>_sessions:#{Base.url_encode64(token)}\")\n      end\n\n      <%= inspect schema.alias %>Auth.disconnect_sessions(tokens)\n\n      assert_receive %Phoenix.Socket.Broadcast{\n        event: \"disconnect\",\n        topic: \"<%= schema.plural %>_sessions:dG9rZW4x\"\n      }\n\n      assert_receive %Phoenix.Socket.Broadcast{\n        event: \"disconnect\",\n        topic: \"<%= schema.plural %>_sessions:dG9rZW4y\"\n      }\n    end\n  end<% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/confirmation_live.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Confirmation do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n      <div class=\"mx-auto max-w-sm\">\n        <div class=\"text-center\">\n          <.header>Welcome {@<%= schema.singular %>.email}</.header>\n        </div>\n\n        <.form\n          :if={!@<%= schema.singular %>.confirmed_at}\n          for={@form}\n          id=\"confirmation_form\"\n          phx-mounted={JS.focus_first()}\n          phx-submit=\"submit\"\n          action={~p\"<%= schema.route_prefix %>/log-in?_action=confirmed\"}\n          phx-trigger-action={@trigger_submit}\n        >\n          <input type=\"hidden\" name={@form[:token].name} value={@form[:token].value} />\n          <.button\n            name={@form[:remember_me].name}\n            value=\"true\"\n            phx-disable-with=\"Confirming...\"\n            class=\"btn btn-primary w-full\"\n          >\n            Confirm and stay logged in\n          </.button>\n          <.button phx-disable-with=\"Confirming...\" class=\"btn btn-primary btn-soft w-full mt-2\">\n            Confirm and log in only this time\n          </.button>\n        </.form>\n\n        <.form\n          :if={@<%= schema.singular %>.confirmed_at}\n          for={@form}\n          id=\"login_form\"\n          phx-submit=\"submit\"\n          phx-mounted={JS.focus_first()}\n          action={~p\"<%= schema.route_prefix %>/log-in\"}\n          phx-trigger-action={@trigger_submit}\n        >\n          <input type=\"hidden\" name={@form[:token].name} value={@form[:token].value} />\n          <%%= if @<%= scope_config.scope.assign_key %> do %>\n            <.button phx-disable-with=\"Logging in...\" class=\"btn btn-primary w-full\">\n              Log in\n            </.button>\n          <%% else %>\n            <.button\n              name={@form[:remember_me].name}\n              value=\"true\"\n              phx-disable-with=\"Logging in...\"\n              class=\"btn btn-primary w-full\"\n            >\n              Keep me logged in on this device\n            </.button>\n            <.button phx-disable-with=\"Logging in...\" class=\"btn btn-primary btn-soft w-full mt-2\">\n              Log me in only this time\n            </.button>\n          <%% end %>\n        </.form>\n\n        <p :if={!@<%= schema.singular %>.confirmed_at} class=\"alert alert-outline mt-8\">\n          Tip: If you prefer passwords, you can enable them in the <%= schema.singular %> settings.\n        </p>\n      </div>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(%{\"token\" => token}, _session, socket) do\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_magic_link_token(token) do\n      form = to_form(%{\"token\" => token}, as: \"<%= schema.singular %>\")\n\n      {:ok, assign(socket, <%= schema.singular %>: <%= schema.singular %>, form: form, trigger_submit: false),\n       temporary_assigns: [form: nil]}\n    else\n      {:ok,\n       socket\n       |> put_flash(:error, \"Magic link is invalid or it has expired.\")\n       |> push_navigate(to: ~p\"<%= schema.route_prefix %>/log-in\")}\n    end\n  end\n\n  @impl true\n  def handle_event(\"submit\", %{\"<%= schema.singular %>\" => params}, socket) do\n    {:noreply, assign(socket, form: to_form(params, as: \"<%= schema.singular %>\"), trigger_submit: true)}\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/confirmation_live_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.ConfirmationTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  import Phoenix.LiveViewTest\n  import <%= inspect context.module %>Fixtures\n\n  alias <%= inspect context.module %>\n\n  setup do\n    %{unconfirmed_<%= schema.singular %>: unconfirmed_<%= schema.singular %>_fixture(), confirmed_<%= schema.singular %>: <%= schema.singular %>_fixture()}\n  end\n\n  describe \"Confirm <%= schema.singular %>\" do\n    test \"renders confirmation page for unconfirmed <%= schema.singular %>\", %{conn: conn, unconfirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n      assert html =~ \"Confirm and stay logged in\"\n    end\n\n    test \"renders login page for confirmed <%= schema.singular %>\", %{conn: conn, confirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n      refute html =~ \"Confirm my account\"\n      assert html =~ \"Keep me logged in on this device\"\n    end\n\n    test \"renders login page for already logged in <%= schema.singular %>\", %{conn: conn, confirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      conn = log_in_<%= schema.singular %>(conn, <%= schema.singular %>)\n\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n      refute html =~ \"Confirm my account\"\n      assert html =~ \"Log in\"\n    end\n\n    test \"confirms the given token once\", %{conn: conn, unconfirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n\n      form = form(lv, \"#confirmation_form\", %{\"<%= schema.singular %>\" => %{\"token\" => token}})\n      render_submit(form)\n\n      conn = follow_trigger_action(form, conn)\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~\n               \"<%= inspect schema.alias %> confirmed successfully\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id).confirmed_at\n      # we are logged in now\n      assert get_session(conn, :<%= schema.singular %>_token)\n      assert redirected_to(conn) == ~p\"/\"\n\n      # log out, new conn\n      conn = build_conn()\n\n      {:ok, _lv, html} =\n        live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"Magic link is invalid or it has expired\"\n    end\n\n    test \"logs confirmed <%= schema.singular %> in without changing confirmed_at\", %{\n      conn: conn,\n      confirmed_<%= schema.singular %>: <%= schema.singular %>\n    } do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n\n      form = form(lv, \"#login_form\", %{\"<%= schema.singular %>\" => %{\"token\" => token}})\n      render_submit(form)\n\n      conn = follow_trigger_action(form, conn)\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~\n               \"Welcome back!\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id).confirmed_at == <%= schema.singular %>.confirmed_at\n\n      # log out, new conn\n      conn = build_conn()\n\n      {:ok, _lv, html} =\n        live(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"Magic link is invalid or it has expired\"\n    end\n\n    test \"raises error for invalid token\", %{conn: conn} do\n      {:ok, _lv, html} =\n        live(conn, ~p\"<%= schema.route_prefix %>/log-in/invalid-token\")\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"Magic link is invalid or it has expired\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/conn_case.exs.eex",
    "content": "\n  @doc \"\"\"\n  Setup helper that registers and logs in <%= schema.plural %>.\n\n      setup :register_and_log_in_<%= schema.singular %>\n\n  It stores an updated connection and a registered <%= schema.singular %> in the\n  test context.\n  \"\"\"\n  def register_and_log_in_<%= schema.singular %>(%{conn: conn} = context) do\n    <%= schema.singular %> = <%= inspect context.module %>Fixtures.<%= schema.singular %>_fixture()\n    scope = <%= inspect scope_config.scope.module %>.for_<%= schema.singular %>(<%= schema.singular %>)\n\n    opts =\n      context\n      |> Map.take([:token_authenticated_at])\n      |> Enum.into([])\n\n    %{conn: log_in_<%= schema.singular %>(conn, <%= schema.singular %>, opts), <%= schema.singular %>: <%= schema.singular %>, scope: scope}\n  end\n\n  @doc \"\"\"\n  Logs the given `<%= schema.singular %>` into the `conn`.\n\n  It returns an updated `conn`.\n  \"\"\"\n  def log_in_<%= schema.singular %>(conn, <%= schema.singular %>, opts \\\\ []) do\n    token = <%= inspect context.module %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n\n    maybe_set_token_authenticated_at(token, opts[:token_authenticated_at])\n\n    conn\n    |> Phoenix.ConnTest.init_test_session(%{})\n    |> Plug.Conn.put_session(:<%= schema.singular %>_token, token)\n  end\n\n  defp maybe_set_token_authenticated_at(_token, nil), do: nil\n\n  defp maybe_set_token_authenticated_at(token, authenticated_at) do\n    <%= inspect context.module %>Fixtures.override_token_authenticated_at(token, authenticated_at)\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/context_fixtures_functions.ex.eex",
    "content": "  import Ecto.Query\n\n  alias <%= inspect context.module %>\n  alias <%= inspect scope_config.scope.module %>\n\n  def unique_<%= schema.singular %>_email, do: \"<%= schema.singular %>#{System.unique_integer()}@example.com\"\n  def valid_<%= schema.singular %>_password, do: \"hello world!\"\n\n  def valid_<%= schema.singular %>_attributes(attrs \\\\ %{}) do\n    Enum.into(attrs, %{\n      email: unique_<%= schema.singular %>_email()\n    })\n  end\n\n  def unconfirmed_<%= schema.singular %>_fixture(attrs \\\\ %{}) do\n    {:ok, <%= schema.singular %>} =\n      attrs\n      |> valid_<%= schema.singular %>_attributes()\n      |> <%= inspect context.alias %>.register_<%= schema.singular %>()\n\n    <%= schema.singular %>\n  end\n\n  def <%= schema.singular %>_fixture(attrs \\\\ %{}) do\n    <%= schema.singular %> = unconfirmed_<%= schema.singular %>_fixture(attrs)\n\n    token =\n      extract_<%= schema.singular %>_token(fn url ->\n        <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n      end)\n\n    {:ok, {<%= schema.singular %>, _expired_tokens}} =\n      <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(token)\n\n    <%= schema.singular %>\n  end\n\n  def <%= schema.singular %>_scope_fixture do\n    <%= schema.singular %> = <%= schema.singular %>_fixture()\n    <%= schema.singular %>_scope_fixture(<%= schema.singular %>)\n  end\n\n  def <%= schema.singular %>_scope_fixture(<%= schema.singular %>) do\n    <%= inspect scope_config.scope.alias %>.for_<%= schema.singular %>(<%= schema.singular %>)\n  end\n\n  def set_password(<%= schema.singular %>) do\n    {:ok, {<%= schema.singular %>, _expired_tokens}} =\n      <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: valid_<%= schema.singular %>_password()})\n\n    <%= schema.singular %>\n  end\n\n  def extract_<%= schema.singular %>_token(fun) do\n    {:ok, captured_email} = fun.(&\"[TOKEN]#{&1}[TOKEN]\")\n    [_, token | _] = String.split(captured_email.text_body, \"[TOKEN]\")\n    token\n  end\n\n  def override_token_authenticated_at(token, authenticated_at) when is_binary(token) do\n    <%= inspect schema.repo %>.update_all(\n      from(t in <%= inspect context.alias %>.<%= inspect schema.alias %>Token,\n        where: t.token == ^token\n      ),\n      set: [authenticated_at: authenticated_at]\n    )\n  end\n\n  def generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>) do\n    {encoded_token, <%= schema.singular %>_token} = <%= inspect context.alias %>.<%= inspect schema.alias %>Token.build_email_token(<%= schema.singular %>, \"login\")\n    <%= inspect schema.repo %>.insert!(<%= schema.singular %>_token)\n    {encoded_token, <%= schema.singular %>_token.token}\n  end\n\n  def offset_<%= schema.singular %>_token(token, amount_to_add, unit) do\n    dt = <%= inspect datetime_module %>.add(<%= datetime_now %>, amount_to_add, unit)\n\n    <%= inspect schema.repo %>.update_all(\n      from(ut in <%= inspect context.alias %>.<%= inspect schema.alias %>Token, where: ut.token == ^token),\n      set: [inserted_at: dt, authenticated_at: dt]\n    )\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/context_functions.ex.eex",
    "content": "  alias <%= inspect context.module %>.{<%= inspect schema.alias %>, <%= inspect schema.alias %>Token, <%= inspect schema.alias %>Notifier}\n\n  ## Database getters\n\n  @doc \"\"\"\n  Gets a <%= schema.singular %> by email.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>_by_email(\"foo@example.com\")\n      %<%= inspect schema.alias %>{}\n\n      iex> get_<%= schema.singular %>_by_email(\"unknown@example.com\")\n      nil\n\n  \"\"\"\n  def get_<%= schema.singular %>_by_email(email) when is_binary(email) do\n    Repo.get_by(<%= inspect schema.alias %>, email: email)\n  end\n\n  @doc \"\"\"\n  Gets a <%= schema.singular %> by email and password.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>_by_email_and_password(\"foo@example.com\", \"correct_password\")\n      %<%= inspect schema.alias %>{}\n\n      iex> get_<%= schema.singular %>_by_email_and_password(\"foo@example.com\", \"invalid_password\")\n      nil\n\n  \"\"\"\n  def get_<%= schema.singular %>_by_email_and_password(email, password)\n      when is_binary(email) and is_binary(password) do\n    <%= schema.singular %> = Repo.get_by(<%= inspect schema.alias %>, email: email)\n    if <%= inspect schema.alias %>.valid_password?(<%= schema.singular %>, password), do: <%= schema.singular %>\n  end\n\n  @doc \"\"\"\n  Gets a single <%= schema.singular %>.\n\n  Raises `Ecto.NoResultsError` if the <%= inspect schema.alias %> does not exist.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>!(123)\n      %<%= inspect schema.alias %>{}\n\n      iex> get_<%= schema.singular %>!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_<%= schema.singular %>!(id), do: Repo.get!(<%= inspect schema.alias %>, id)\n\n  ## <%= schema.human_singular %> registration\n\n  @doc \"\"\"\n  Registers a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> register_<%= schema.singular %>(%{field: value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> register_<%= schema.singular %>(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def register_<%= schema.singular %>(attrs) do\n    %<%= inspect schema.alias %>{}\n    |> <%= inspect schema.alias %>.email_changeset(attrs)\n    |> Repo.insert()\n  end\n\n  ## Settings\n\n  @doc \"\"\"\n  Checks whether the <%= schema.singular %> is in sudo mode.\n\n  The <%= schema.singular %> is in sudo mode when the last authentication was done no further\n  than 20 minutes ago. The limit can be given as second argument in minutes.\n  \"\"\"\n  def sudo_mode?(<%= schema.singular %>, minutes \\\\ -20)\n\n  def sudo_mode?(%<%= inspect schema.alias %>{authenticated_at: ts}, minutes) when is_struct(ts, <%= inspect datetime_module %>) do\n    <%= inspect datetime_module %>.after?(ts, <%= inspect datetime_module %>.utc_now() |> <%= inspect datetime_module %>.add(minutes, :minute))\n  end\n\n  def sudo_mode?(_<%= schema.singular %>, _minutes), do: false\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for changing the <%= schema.singular %> email.\n\n  See `<%= inspect context.module %>.<%= inspect schema.alias %>.email_changeset/3` for a list of supported options.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>_email(<%= schema.singular %>)\n      %Ecto.Changeset{data: %<%= inspect schema.alias %>{}}\n\n  \"\"\"\n  def change_<%= schema.singular %>_email(<%= schema.singular %>, attrs \\\\ %{}, opts \\\\ []) do\n    <%= inspect schema.alias %>.email_changeset(<%= schema.singular %>, attrs, opts)\n  end\n\n  @doc \"\"\"\n  Updates the <%= schema.singular %> email using the given token.\n\n  If the token matches, the <%= schema.singular %> email is updated and the token is deleted.\n  \"\"\"\n  def update_<%= schema.singular %>_email(<%= schema.singular %>, token) do\n    context = \"change:#{<%= schema.singular %>.email}\"\n\n    Repo.transact(fn ->\n      with {:ok, query} <- <%= inspect schema.alias %>Token.verify_change_email_token_query(token, context),\n           %<%= inspect schema.alias %>Token{sent_to: email} <- Repo.one(query),\n           {:ok, <%= schema.singular %>} <- Repo.update(<%= inspect schema.alias %>.email_changeset(<%= schema.singular %>, %{email: email})),\n           {_count, _result} <-\n             Repo.delete_all(from(<%= inspect schema.alias %>Token, where: [<%= schema.singular %>_id: ^<%= schema.singular %>.id, context: ^context])) do\n        {:ok, <%= schema.singular %>}\n      else\n        _ -> {:error, :transaction_aborted}\n      end\n    end)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for changing the <%= schema.singular %> password.\n\n  See `<%= inspect context.module %>.<%= inspect schema.alias %>.password_changeset/3` for a list of supported options.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>_password(<%= schema.singular %>)\n      %Ecto.Changeset{data: %<%= inspect schema.alias %>{}}\n\n  \"\"\"\n  def change_<%= schema.singular %>_password(<%= schema.singular %>, attrs \\\\ %{}, opts \\\\ []) do\n    <%= inspect schema.alias %>.password_changeset(<%= schema.singular %>, attrs, opts)\n  end\n\n  @doc \"\"\"\n  Updates the <%= schema.singular %> password.\n\n  Returns a tuple with the updated <%= schema.singular %>, as well as a list of expired tokens.\n\n  ## Examples\n\n      iex> update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: ...})\n      {:ok, {%<%= inspect schema.alias %>{}, [...]}}\n\n      iex> update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: \"too short\"})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_<%= schema.singular %>_password(<%= schema.singular %>, attrs) do\n    <%= schema.singular %>\n    |> <%= inspect schema.alias %>.password_changeset(attrs)\n    |> update_<%= schema.singular %>_and_delete_all_tokens()\n  end\n\n  ## Session\n\n  @doc \"\"\"\n  Generates a session token.\n  \"\"\"\n  def generate_<%= schema.singular %>_session_token(<%= schema.singular %>) do\n    {token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_session_token(<%= schema.singular %>)\n    Repo.insert!(<%= schema.singular %>_token)\n    token\n  end\n\n  @doc \"\"\"\n  Gets the <%= schema.singular %> with the given signed token.\n\n  If the token is valid `{<%= schema.singular %>, token_inserted_at}` is returned, otherwise `nil` is returned.\n  \"\"\"\n  def get_<%= schema.singular %>_by_session_token(token) do\n    {:ok, query} = <%= inspect schema.alias %>Token.verify_session_token_query(token)\n    Repo.one(query)\n  end\n\n  @doc \"\"\"\n  Gets the <%= schema.singular %> with the given magic link token.\n  \"\"\"\n  def get_<%= schema.singular %>_by_magic_link_token(token) do\n    with {:ok, query} <- <%= inspect schema.alias %>Token.verify_magic_link_token_query(token),\n         {<%= schema.singular %>, _token} <- Repo.one(query) do\n      <%= schema.singular %>\n    else\n      _ -> nil\n    end\n  end\n\n  @doc \"\"\"\n  Logs the <%= schema.singular %> in by magic link.\n\n  There are three cases to consider:\n\n  1. The <%= schema.singular %> has already confirmed their email. They are logged in\n     and the magic link is expired.\n\n  2. The <%= schema.singular %> has not confirmed their email and no password is set.\n     In this case, the <%= schema.singular %> gets confirmed, logged in, and all tokens -\n     including session ones - are expired. In theory, no other tokens\n     exist but we delete all of them for best security practices.\n\n  3. The <%= schema.singular %> has not confirmed their email but a password is set.\n     This cannot happen in the default implementation but may be the\n     source of security pitfalls. See the \"Mixing magic link and password registration\" section of\n     `mix help phx.gen.auth`.\n  \"\"\"\n  def login_<%= schema.singular %>_by_magic_link(token) do\n    {:ok, query} = <%= inspect schema.alias %>Token.verify_magic_link_token_query(token)\n\n    case Repo.one(query) do\n      # Prevent session fixation attacks by disallowing magic links for unconfirmed users with password\n      {%<%= inspect schema.alias %>{confirmed_at: nil, hashed_password: hash}, _token} when not is_nil(hash) ->\n        raise \"\"\"\n        magic link log in is not allowed for unconfirmed users with a password set!\n\n        This cannot happen with the default implementation, which indicates that you\n        might have adapted the code to a different use case. Please make sure to read the\n        \"Mixing magic link and password registration\" section of `mix help phx.gen.auth`.\n        \"\"\"\n\n      {%<%= inspect schema.alias %>{confirmed_at: nil} = <%= schema.singular %>, _token} ->\n        <%= schema.singular %>\n        |> <%= inspect schema.alias %>.confirm_changeset()\n        |> update_<%= schema.singular %>_and_delete_all_tokens()\n\n      {<%= schema.singular %>, token} ->\n        Repo.delete!(token)\n        {:ok, {<%= schema.singular %>, []}}\n\n      nil ->\n        {:error, :not_found}\n    end\n  end\n\n  @doc ~S\"\"\"\n  Delivers the update email instructions to the given <%= schema.singular %>.\n\n  ## Examples\n\n      iex> deliver_<%= schema.singular %>_update_email_instructions(<%= schema.singular %>, current_email, &url(~p\"<%= schema.route_prefix %>/settings/confirm-email/#{&1}\"))\n      {:ok, %{to: ..., body: ...}}\n\n  \"\"\"\n  def deliver_<%= schema.singular %>_update_email_instructions(%<%= inspect schema.alias %>{} = <%= schema.singular %>, current_email, update_email_url_fun)\n      when is_function(update_email_url_fun, 1) do\n    {encoded_token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_email_token(<%= schema.singular %>, \"change:#{current_email}\")\n\n    Repo.insert!(<%= schema.singular %>_token)\n    <%= inspect schema.alias %>Notifier.deliver_update_email_instructions(<%= schema.singular %>, update_email_url_fun.(encoded_token))\n  end\n\n  @doc \"\"\"\n  Delivers the magic link login instructions to the given <%= schema.singular %>.\n  \"\"\"\n  def deliver_login_instructions(%<%= inspect schema.alias %>{} = <%= schema.singular %>, magic_link_url_fun)\n      when is_function(magic_link_url_fun, 1) do\n    {encoded_token, <%= schema.singular %>_token} = <%= inspect schema.alias %>Token.build_email_token(<%= schema.singular %>, \"login\")\n    Repo.insert!(<%= schema.singular %>_token)\n    <%= inspect schema.alias %>Notifier.deliver_login_instructions(<%= schema.singular %>, magic_link_url_fun.(encoded_token))\n  end\n\n  @doc \"\"\"\n  Deletes the signed token with the given context.\n  \"\"\"\n  def delete_<%= schema.singular %>_session_token(token) do\n    Repo.delete_all(from(<%= inspect schema.alias %>Token, where: [token: ^token, context: \"session\"]))\n    :ok\n  end\n\n  ## Token helper\n\n  defp update_<%= schema.singular %>_and_delete_all_tokens(changeset) do\n    Repo.transact(fn ->\n      with {:ok, <%= schema.singular %>} <- Repo.update(changeset) do\n        tokens_to_expire = Repo.all_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n\n        Repo.delete_all(from(t in <%= inspect schema.alias %>Token, where: t.id in ^Enum.map(tokens_to_expire, & &1.id)))\n\n        {:ok, {<%= schema.singular %>, tokens_to_expire}}\n      end\n    end)\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/login_live.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Login do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n      <div class=\"mx-auto max-w-sm space-y-4\">\n        <div class=\"text-center\">\n          <.header>\n            <p>Log in</p>\n            <:subtitle>\n              <%%= if @<%= scope_config.scope.assign_key %> do %>\n                You need to reauthenticate to perform sensitive actions on your account.\n              <%% else %>\n                Don't have an account? <.link\n                  navigate={~p\"<%= schema.route_prefix %>/register\"}\n                  class=\"font-semibold text-brand hover:underline\"\n                  phx-no-format\n                >Sign up</.link> for an account now.\n              <%% end %>\n            </:subtitle>\n          </.header>\n        </div>\n\n        <div :if={local_mail_adapter?()} class=\"alert alert-info\">\n          <.icon name=\"hero-information-circle\" class=\"size-6 shrink-0\" />\n          <div>\n            <p>You are running the local mail adapter.</p>\n            <p>\n              To see sent emails, visit <.link href=\"/dev/mailbox\" class=\"underline\">the mailbox page</.link>.\n            </p>\n          </div>\n        </div>\n\n        <.form\n          :let={f}\n          for={@form}\n          id=\"login_form_magic\"\n          action={~p\"<%= schema.route_prefix %>/log-in\"}\n          phx-submit=\"submit_magic\"\n        >\n          <.input\n            readonly={!!@<%= scope_config.scope.assign_key %>}\n            field={f[:email]}\n            type=\"email\"\n            label=\"Email\"\n            autocomplete=\"username\"\n            spellcheck=\"false\"\n            required\n            phx-mounted={JS.focus()}\n          />\n          <.button class=\"btn btn-primary w-full\">\n            Log in with email <span aria-hidden=\"true\">→</span>\n          </.button>\n        </.form>\n\n        <div class=\"divider\">or</div>\n\n        <.form\n          :let={f}\n          for={@form}\n          id=\"login_form_password\"\n          action={~p\"<%= schema.route_prefix %>/log-in\"}\n          phx-submit=\"submit_password\"\n          phx-trigger-action={@trigger_submit}\n        >\n          <.input\n            readonly={!!@<%= scope_config.scope.assign_key %>}\n            field={f[:email]}\n            type=\"email\"\n            label=\"Email\"\n            autocomplete=\"username\"\n            spellcheck=\"false\"\n            required\n          />\n          <.input\n            field={@form[:password]}\n            type=\"password\"\n            label=\"Password\"\n            autocomplete=\"current-password\"\n            spellcheck=\"false\"\n          />\n          <.button class=\"btn btn-primary w-full\" name={@form[:remember_me].name} value=\"true\">\n            Log in and stay logged in <span aria-hidden=\"true\">→</span>\n          </.button>\n          <.button class=\"btn btn-primary btn-soft w-full mt-2\">\n            Log in only this time\n          </.button>\n        </.form>\n      </div>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(_params, _session, socket) do\n    email =\n      Phoenix.Flash.get(socket.assigns.flash, :email) ||\n        get_in(socket.assigns, [:<%= scope_config.scope.assign_key %>, Access.key(:<%= schema.singular %>), Access.key(:email)])\n\n    form = to_form(%{\"email\" => email}, as: \"<%= schema.singular %>\")\n\n    {:ok, assign(socket, form: form, trigger_submit: false)}\n  end\n\n  @impl true\n  def handle_event(\"submit_password\", _params, socket) do\n    {:noreply, assign(socket, :trigger_submit, true)}\n  end\n\n  def handle_event(\"submit_magic\", %{\"<%= schema.singular %>\" => %{\"email\" => email}}, socket) do\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(email) do\n      <%= inspect context.alias %>.deliver_login_instructions(\n        <%= schema.singular %>,\n        &url(~p\"<%= schema.route_prefix %>/log-in/#{&1}\")\n      )\n    end\n\n    info =\n      \"If your email is in our system, you will receive instructions for logging in shortly.\"\n\n    {:noreply,\n     socket\n     |> put_flash(:info, info)\n     |> push_navigate(to: ~p\"<%= schema.route_prefix %>/log-in\")}\n  end\n\n  defp local_mail_adapter? do\n    Application.get_env(:<%= Mix.Phoenix.otp_app() %>, <%= inspect context.base_module %>.Mailer)[:adapter] == Swoosh.Adapters.Local\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/login_live_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.LoginTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  import Phoenix.LiveViewTest\n  import <%= inspect context.module %>Fixtures\n\n  describe \"login page\" do\n    test \"renders login page\", %{conn: conn} do\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"Log in\"\n      assert html =~ \"Register\"\n      assert html =~ \"Log in with email\"\n    end\n  end\n\n  describe \"<%= schema.singular %> login - magic link\" do\n    test \"sends magic link email when <%= schema.singular %> exists\", %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:ok, _lv, html} =\n        form(lv, \"#login_form_magic\", <%= schema.singular %>: %{email: <%= schema.singular %>.email})\n        |> render_submit()\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"If your email is in our system\"\n\n      assert <%= inspect schema.repo %>.get_by!(<%= inspect context.module %>.<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id).context ==\n               \"login\"\n    end\n\n    test \"does not disclose if <%= schema.singular %> is registered\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:ok, _lv, html} =\n        form(lv, \"#login_form_magic\", <%= schema.singular %>: %{email: \"idonotexist@example.com\"})\n        |> render_submit()\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"If your email is in our system\"\n    end\n  end\n\n  describe \"<%= schema.singular %> login - password\" do\n    test \"redirects if <%= schema.singular %> logs in with valid credentials\", %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture() |> set_password()\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      form =\n        form(lv, \"#login_form_password\",\n          <%= schema.singular %>: %{email: <%= schema.singular %>.email, password: valid_<%= schema.singular %>_password(), remember_me: true}\n        )\n\n      conn = submit_form(form, conn)\n\n      assert redirected_to(conn) == ~p\"/\"\n    end\n\n    test \"redirects to login page with a flash error if credentials are invalid\", %{\n      conn: conn\n    } do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      form =\n        form(lv, \"#login_form_password\", <%= schema.singular %>: %{email: \"test@email.com\", password: \"123456\"})\n\n      render_submit(form, %{user: %{remember_me: true}})\n\n      conn = follow_trigger_action(form, conn)\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) == \"Invalid email or password\"\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n    end\n  end\n\n  describe \"login navigation\" do\n    test \"redirects to registration page when the Register button is clicked\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:ok, _login_live, login_html} =\n        lv\n        |> element(\"main a\", \"Sign up\")\n        |> render_click()\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      assert login_html =~ \"Register\"\n    end\n  end\n\n  describe \"re-authentication (sudo mode)\" do\n    setup %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      %{<%= schema.singular %>: <%= schema.singular %>, conn: log_in_<%= schema.singular %>(conn, <%= schema.singular %>)}\n    end\n\n    test \"shows login page with email filled in\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~ \"You need to reauthenticate\"\n      refute html =~ \"Register\"\n      assert html =~ \"Log in with email\"\n\n      assert html =~\n               ~s(<input type=\"email\" name=\"<%= schema.singular %>[email]\" id=\"login_form_magic_email\" value=\"#{<%= schema.singular %>.email}\")\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/migration.ex.eex",
    "content": "defmodule <%= inspect schema.repo %>.Migrations.Create<%= Macro.camelize(schema.table) %>AuthTables do\n  use <%= inspect schema.migration_module %>\n\n  def change do<%= if Enum.any?(migration.extensions) do %><%= for extension <- migration.extensions do %>\n    <%= extension %><% end %>\n<% end %>\n    create table(:<%= schema.table %><%= if schema.binary_id do %>, primary_key: false<% end %>) do\n<%= if schema.binary_id do %>      add :id, :binary_id, primary_key: true\n<% end %>      <%= migration.column_definitions[:email] %>\n      add :hashed_password, :string\n      add :confirmed_at, <%= inspect schema.timestamp_type %>\n\n      timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}\" %>)\n    end\n\n    create unique_index(:<%= schema.table %>, [:email])\n\n    create table(:<%= schema.table %>_tokens<%= if schema.binary_id do %>, primary_key: false<% end %>) do\n<%= if schema.binary_id do %>      add :id, :binary_id, primary_key: true\n<% end %>      add :<%= schema.singular %>_id, references(:<%= schema.table %>, <%= if schema.binary_id do %>type: :binary_id, <% end %>on_delete: :delete_all), null: false\n      <%= migration.column_definitions[:token] %>\n      add :context, :string, null: false\n      add :sent_to, :string\n      add :authenticated_at, <%= inspect schema.timestamp_type %>\n\n      timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}, \" %>updated_at: false)\n    end\n\n    create index(:<%= schema.table %>_tokens, [:<%= schema.singular %>_id])\n    create unique_index(:<%= schema.table %>_tokens, [:context, :token])\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/notifier.ex.eex",
    "content": "defmodule <%= inspect context.module %>.<%= inspect schema.alias %>Notifier do\n  import Swoosh.Email\n\n  alias <%= inspect context.base_module %>.Mailer\n  alias <%= inspect context.module %>.<%= inspect schema.alias %>\n\n  # Delivers the email using the application mailer.\n  defp deliver(recipient, subject, body) do\n    email =\n      new()\n      |> to(recipient)\n      |> from({\"<%= inspect context.base_module %>\", \"contact@example.com\"})\n      |> subject(subject)\n      |> text_body(body)\n\n    with {:ok, _metadata} <- Mailer.deliver(email) do\n      {:ok, email}\n    end\n  end\n\n  @doc \"\"\"\n  Deliver instructions to update a <%= schema.singular %> email.\n  \"\"\"\n  def deliver_update_email_instructions(<%= schema.singular %>, url) do\n    deliver(<%= schema.singular %>.email, \"Update email instructions\", \"\"\"\n\n    ==============================\n\n    Hi #{<%= schema.singular %>.email},\n\n    You can change your email by visiting the URL below:\n\n    #{url}\n\n    If you didn't request this change, please ignore this.\n\n    ==============================\n    \"\"\")\n  end\n\n  @doc \"\"\"\n  Deliver instructions to log in with a magic link.\n  \"\"\"\n  def deliver_login_instructions(<%= schema.singular %>, url) do\n    case <%= schema.singular %> do\n      %<%= inspect schema.alias %>{confirmed_at: nil} -> deliver_confirmation_instructions(<%= schema.singular %>, url)\n      _ -> deliver_magic_link_instructions(<%= schema.singular %>, url)\n    end\n  end\n\n  defp deliver_magic_link_instructions(<%= schema.singular %>, url) do\n    deliver(<%= schema.singular %>.email, \"Log in instructions\", \"\"\"\n\n    ==============================\n\n    Hi #{<%= schema.singular %>.email},\n\n    You can log into your account by visiting the URL below:\n\n    #{url}\n\n    If you didn't request this email, please ignore this.\n\n    ==============================\n    \"\"\")\n  end\n\n  defp deliver_confirmation_instructions(<%= schema.singular %>, url) do\n    deliver(<%= schema.singular %>.email, \"Confirmation instructions\", \"\"\"\n\n    ==============================\n\n    Hi #{<%= schema.singular %>.email},\n\n    You can confirm your account by visiting the URL below:\n\n    #{url}\n\n    If you didn't create an account with us, please ignore this.\n\n    ==============================\n    \"\"\")\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>RegistrationController do\n  use <%= inspect context.web_module %>, :controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect schema.module %>\n\n  def new(conn, _params) do\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_email(%<%= inspect schema.alias %>{})\n    render(conn, :new, changeset: changeset)\n  end\n\n  def create(conn, %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}) do\n    case <%= inspect context.alias %>.register_<%= schema.singular %>(<%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        {:ok, _} =\n          <%= inspect context.alias %>.deliver_login_instructions(\n            <%= schema.singular %>,\n            &url(~p\"<%= schema.route_prefix %>/log-in/#{&1}\")\n          )\n\n        conn\n        |> put_flash(\n          :info,\n          \"An email was sent to #{<%= schema.singular %>.email}, please access it to confirm your account.\"\n        )\n        |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, :new, changeset: changeset)\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_controller_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>RegistrationControllerTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  import <%= inspect context.module %>Fixtures\n\n  describe \"GET <%= schema.route_prefix %>/register\" do\n    test \"renders registration page\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/register\")\n      response = html_response(conn, 200)\n      assert response =~ \"Register\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/log-in\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/register\"\n    end\n\n    test \"redirects if already logged in\", %{conn: conn} do\n      conn = conn |> log_in_<%= schema.singular %>(<%= schema.singular %>_fixture()) |> get(~p\"<%= schema.route_prefix %>/register\")\n\n      assert redirected_to(conn) == ~p\"/\"\n    end\n  end\n\n  describe \"POST <%= schema.route_prefix %>/register\" do\n    @tag :capture_log\n    test \"creates account but does not log in\", %{conn: conn} do\n      email = unique_<%= schema.singular %>_email()\n\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/register\", %{\n          \"<%= schema.singular %>\" => valid_<%= schema.singular %>_attributes(email: email)\n        })\n\n      refute get_session(conn, :<%= schema.singular %>_token)\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n\n      assert conn.assigns.flash[\"info\"] =~\n               ~r/An email was sent to .*, please access it to confirm your account/\n    end\n\n    test \"render errors for invalid data\", %{conn: conn} do\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/register\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => \"with spaces\"}\n        })\n\n      response = html_response(conn, 200)\n      assert response =~ \"Register\"\n      assert response =~ \"must have the @ sign and no spaces\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_html.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>RegistrationHTML do\n  use <%= inspect context.web_module %>, :html\n\n  embed_templates \"<%= schema.singular %>_registration_html/*\"\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_live.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Registration do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n  alias <%= inspect schema.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n      <div class=\"mx-auto max-w-sm\">\n        <div class=\"text-center\">\n          <.header>\n            Register for an account\n            <:subtitle>\n              Already registered?\n              <.link navigate={~p\"<%= schema.route_prefix %>/log-in\"} class=\"font-semibold text-brand hover:underline\">\n                Log in\n              </.link>\n              to your account now.\n            </:subtitle>\n          </.header>\n        </div>\n\n        <.form for={@form} id=\"registration_form\" phx-submit=\"save\" phx-change=\"validate\">\n          <.input\n            field={@form[:email]}\n            type=\"email\"\n            label=\"Email\"\n            autocomplete=\"username\"\n            spellcheck=\"false\"\n            required\n            phx-mounted={JS.focus()}\n          />\n\n          <.button phx-disable-with=\"Creating account...\" class=\"btn btn-primary w-full\">\n            Create an account\n          </.button>\n        </.form>\n      </div>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(_params, _session, %{assigns: %{<%= scope_config.scope.assign_key %>: %{<%= schema.singular %>: <%= schema.singular %>}}} = socket)\n      when not is_nil(<%= schema.singular %>) do\n    {:ok, redirect(socket, to: <%= inspect auth_module %>.signed_in_path(socket))}\n  end\n\n  def mount(_params, _session, socket) do\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_email(%<%= inspect schema.alias %>{}, %{}, validate_unique: false)\n\n    {:ok, assign_form(socket, changeset), temporary_assigns: [form: nil]}\n  end\n\n  @impl true\n  def handle_event(\"save\", %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}, socket) do\n    case <%= inspect context.alias %>.register_<%= schema.singular %>(<%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        {:ok, _} =\n          <%= inspect context.alias %>.deliver_login_instructions(\n            <%= schema.singular %>,\n            &url(~p\"<%= schema.route_prefix %>/log-in/#{&1}\")\n          )\n\n        {:noreply,\n         socket\n         |> put_flash(\n           :info,\n           \"An email was sent to #{<%= schema.singular %>.email}, please access it to confirm your account.\"\n         )\n         |> push_navigate(to: ~p\"<%= schema.route_prefix %>/log-in\")}\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        {:noreply, assign_form(socket, changeset)}\n    end\n  end\n\n  def handle_event(\"validate\", %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}, socket) do\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_email(%<%= inspect schema.alias %>{}, <%= schema.singular %>_params, validate_unique: false)\n    {:noreply, assign_form(socket, Map.put(changeset, :action, :validate))}\n  end\n\n  defp assign_form(socket, %Ecto.Changeset{} = changeset) do\n    form = to_form(changeset, as: \"<%= schema.singular %>\")\n    assign(socket, form: form)\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_live_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.RegistrationTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  import Phoenix.LiveViewTest\n  import <%= inspect context.module %>Fixtures\n\n  describe \"Registration page\" do\n    test \"renders registration page\", %{conn: conn} do\n      {:ok, _lv, html} = live(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      assert html =~ \"Register\"\n      assert html =~ \"Log in\"\n    end\n\n    test \"redirects if already logged in\", %{conn: conn} do\n      result =\n        conn\n        |> log_in_<%= schema.singular %>(<%= schema.singular %>_fixture())\n        |> live(~p\"<%= schema.route_prefix %>/register\")\n        |> follow_redirect(conn, ~p\"/\")\n\n      assert {:ok, _conn} = result\n    end\n\n    test \"renders errors for invalid data\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      result =\n        lv\n        |> element(\"#registration_form\")\n        |> render_change(<%= schema.singular %>: %{\"email\" => \"with spaces\"})\n\n      assert result =~ \"Register\"\n      assert result =~ \"must have the @ sign and no spaces\"\n    end\n  end\n\n  describe \"register <%= schema.singular %>\" do\n    test \"creates account but does not log in\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      email = unique_<%= schema.singular %>_email()\n      form = form(lv, \"#registration_form\", <%= schema.singular %>: valid_<%= schema.singular %>_attributes(email: email))\n\n      {:ok, _lv, html} =\n        render_submit(form)\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert html =~\n               ~r/An email was sent to .*, please access it to confirm your account/\n    end\n\n    test \"renders errors for duplicated email\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      <%= schema.singular %> = <%= schema.singular %>_fixture(%{email: \"test@email.com\"})\n\n      result =\n        lv\n        |> form(\"#registration_form\",\n          <%= schema.singular %>: %{\"email\" => <%= schema.singular %>.email}\n        )\n        |> render_submit()\n\n      assert result =~ \"has already been taken\"\n    end\n  end\n\n  describe \"registration navigation\" do\n    test \"redirects to login page when the Log in button is clicked\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/register\")\n\n      {:ok, _login_live, login_html} =\n        lv\n        |> element(\"main a\", \"Log in\")\n        |> render_click()\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert login_html =~ \"Log in\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/registration_new.html.heex.eex",
    "content": "<Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n  <div class=\"mx-auto max-w-sm\">\n    <div class=\"text-center\">\n      <.header>\n        Register for an account\n        <:subtitle>\n          Already registered?\n          <.link navigate={~p\"<%= schema.route_prefix %>/log-in\"} class=\"font-semibold text-brand hover:underline\">\n            Log in\n          </.link>\n          to your account now.\n        </:subtitle>\n      </.header>\n    </div>\n\n    <.form :let={f} for={@changeset} action={~p\"<%= schema.route_prefix %>/register\"}>\n      <.input\n        field={f[:email]}\n        type=\"email\"\n        label=\"Email\"\n        autocomplete=\"username\"\n        spellcheck=\"false\"\n        required\n        phx-mounted={JS.focus()}\n      />\n\n      <.button phx-disable-with=\"Creating account...\" class=\"btn btn-primary w-full\">\n        Create an account\n      </.button>\n    </.form>\n  </div>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/routes.ex.eex",
    "content": "\n  ## Authentication routes\n\n  <%= if not live? do %>scope <%= router_scope %> do\n    pipe_through [:browser, :redirect_if_<%= schema.singular %>_is_authenticated]\n\n    get \"/<%= schema.plural %>/register\", <%= inspect schema.alias %>RegistrationController, :new\n    post \"/<%= schema.plural %>/register\", <%= inspect schema.alias %>RegistrationController, :create\n  end\n\n  <% end %>scope <%= router_scope %> do\n    pipe_through [:browser, :require_authenticated_<%= schema.singular %>]<%= if live? do %>\n\n    live_session :require_authenticated_<%= schema.singular %>,\n      on_mount: [{<%= inspect auth_module %>, :require_authenticated}] do\n      live \"/<%= schema.plural %>/settings\", <%= inspect schema.alias %>Live.Settings, :edit\n      live \"/<%= schema.plural %>/settings/confirm-email/:token\", <%= inspect schema.alias %>Live.Settings, :confirm_email\n    end\n\n    post \"/<%= schema.plural %>/update-password\", <%= inspect schema.alias %>SessionController, :update_password<% else %>\n\n    get \"/<%= schema.plural %>/settings\", <%= inspect schema.alias %>SettingsController, :edit\n    put \"/<%= schema.plural %>/settings\", <%= inspect schema.alias %>SettingsController, :update\n    get \"/<%= schema.plural %>/settings/confirm-email/:token\", <%= inspect schema.alias %>SettingsController, :confirm_email<% end %>\n  end\n\n  scope <%= router_scope %> do\n    pipe_through [:browser]\n\n    <%= if live? do %>live_session :current_<%= schema.singular %>,\n      on_mount: [{<%= inspect auth_module %>, :mount_<%= scope_config.scope.assign_key %>}] do\n      live \"/<%= schema.plural %>/register\", <%= inspect schema.alias %>Live.Registration, :new\n      live \"/<%= schema.plural %>/log-in\", <%= inspect schema.alias %>Live.Login, :new\n      live \"/<%= schema.plural %>/log-in/:token\", <%= inspect schema.alias %>Live.Confirmation, :new\n    end\n\n    post \"/<%= schema.plural %>/log-in\", <%= inspect schema.alias %>SessionController, :create\n    delete \"/<%= schema.plural %>/log-out\", <%= inspect schema.alias %>SessionController, :delete<% else %>get \"/<%= schema.plural %>/log-in\", <%= inspect schema.alias %>SessionController, :new\n    get \"/<%= schema.plural %>/log-in/:token\", <%= inspect schema.alias %>SessionController, :confirm\n    post \"/<%= schema.plural %>/log-in\", <%= inspect schema.alias %>SessionController, :create\n    delete \"/<%= schema.plural %>/log-out\", <%= inspect schema.alias %>SessionController, :delete<% end %>\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/schema.ex.eex",
    "content": "defmodule <%= inspect schema.module %> do\n  use Ecto.Schema\n  import Ecto.Changeset\n<%= if schema.binary_id do %>\n  @primary_key {:id, :binary_id, autogenerate: true}\n  @foreign_key_type :binary_id<% end %>\n  schema <%= inspect schema.table %> do\n    field :email, :string\n    field :password, :string, virtual: true, redact: true\n    field :hashed_password, :string, redact: true\n    field :confirmed_at, <%= inspect schema.timestamp_type %>\n    field :authenticated_at, <%= inspect schema.timestamp_type %>, virtual: true\n\n    timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}\" %>)\n  end\n\n  @doc \"\"\"\n  A <%= schema.singular %> changeset for registering or changing the email.\n\n  It requires the email to change otherwise an error is added.\n\n  ## Options\n\n    * `:validate_unique` - Set to false if you don't want to validate the\n      uniqueness of the email, useful when displaying live validations.\n      Defaults to `true`.\n  \"\"\"\n  def email_changeset(<%= schema.singular %>, attrs, opts \\\\ []) do\n    <%= schema.singular %>\n    |> cast(attrs, [:email])\n    |> validate_email(opts)\n  end\n\n  defp validate_email(changeset, opts) do\n    changeset =\n      changeset\n      |> validate_required([:email])\n      |> validate_format(:email, ~r/^[^@,;\\s]+@[^@,;\\s]+$/,\n        message: \"must have the @ sign and no spaces\"\n      )\n      |> validate_length(:email, max: 160)\n\n    if Keyword.get(opts, :validate_unique, true) do\n      changeset\n      |> unsafe_validate_unique(:email, <%= inspect schema.repo %>)\n      |> unique_constraint(:email)\n      |> validate_email_changed()\n    else\n      changeset\n    end\n  end\n\n  defp validate_email_changed(changeset) do\n    if get_field(changeset, :email) && get_change(changeset, :email) == nil do\n      add_error(changeset, :email, \"did not change\")\n    else\n      changeset\n    end\n  end\n\n  @doc \"\"\"\n  A <%= schema.singular %> changeset for changing the password.\n\n  It is important to validate the length of the password, as long passwords may\n  be very expensive to hash for certain algorithms.\n\n  ## Options\n\n    * `:hash_password` - Hashes the password so it can be stored securely\n      in the database and ensures the password field is cleared to prevent\n      leaks in the logs. If password hashing is not needed and clearing the\n      password field is not desired (like when using this changeset for\n      validations on a LiveView form), this option can be set to `false`.\n      Defaults to `true`.\n  \"\"\"\n  def password_changeset(<%= schema.singular %>, attrs, opts \\\\ []) do\n    <%= schema.singular %>\n    |> cast(attrs, [:password])\n    |> validate_confirmation(:password, message: \"does not match password\")\n    |> validate_password(opts)\n  end\n\n  defp validate_password(changeset, opts) do\n    changeset\n    |> validate_required([:password])\n    |> validate_length(:password, min: 12, max: 72)\n    # Examples of additional password validation:\n    # |> validate_format(:password, ~r/[a-z]/, message: \"at least one lower case character\")\n    # |> validate_format(:password, ~r/[A-Z]/, message: \"at least one upper case character\")\n    # |> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: \"at least one digit or punctuation character\")\n    |> maybe_hash_password(opts)\n  end\n\n  defp maybe_hash_password(changeset, opts) do\n    hash_password? = Keyword.get(opts, :hash_password, true)\n    password = get_change(changeset, :password)\n\n    if hash_password? && password && changeset.valid? do\n      changeset<%= if hashing_library.name == :bcrypt do %>\n      # If using Bcrypt, then further validate it is at most 72 bytes long\n      |> validate_length(:password, max: 72, count: :bytes)<% end %>\n      # Hashing could be done with `Ecto.Changeset.prepare_changes/2`, but that\n      # would keep the database transaction open longer and hurt performance.\n      |> put_change(:hashed_password, <%= inspect hashing_library.module %>.hash_pwd_salt(password))\n      |> delete_change(:password)\n    else\n      changeset\n    end\n  end\n\n  @doc \"\"\"\n  Confirms the account by setting `confirmed_at`.\n  \"\"\"\n  def confirm_changeset(<%= schema.singular %>) do\n    <%= case schema.timestamp_type do %>\n    <% :naive_datetime -> %>now = NaiveDateTime.utc_now(:second)\n    <% :utc_datetime -> %>now = DateTime.utc_now(:second)\n    <% :utc_datetime_usec -> %>now = DateTime.utc_now(:microsecond)\n    <% end %>change(<%= schema.singular %>, confirmed_at: now)\n  end\n\n  @doc \"\"\"\n  Verifies the password.\n\n  If there is no <%= schema.singular %> or the <%= schema.singular %> doesn't have a password, we call\n  `<%= inspect hashing_library.module %>.no_user_verify/0` to avoid timing attacks.\n  \"\"\"\n  def valid_password?(%<%= inspect schema.module %>{hashed_password: hashed_password}, password)\n      when is_binary(hashed_password) and byte_size(password) > 0 do\n    <%= inspect hashing_library.module %>.verify_pass(password, hashed_password)\n  end\n\n  def valid_password?(_, _) do\n    <%= inspect hashing_library.module %>.no_user_verify()\n    false\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/schema_token.ex.eex",
    "content": "defmodule <%= inspect schema.module %>Token do\n  use Ecto.Schema\n  import Ecto.Query\n  alias <%= inspect schema.module %>Token\n\n  @hash_algorithm :sha256\n  @rand_size 32\n\n  # It is very important to keep the magic link token expiry short,\n  # since someone with access to the email may take over the account.\n  @magic_link_validity_in_minutes 15\n  @change_email_validity_in_days 7\n  @session_validity_in_days 14\n<%= if schema.binary_id do %>\n  @primary_key {:id, :binary_id, autogenerate: true}\n  @foreign_key_type :binary_id<% end %>\n  schema \"<%= schema.table %>_tokens\" do\n    field :token, :binary\n    field :context, :string\n    field :sent_to, :string\n    field :authenticated_at, <%= inspect schema.timestamp_type %>\n    belongs_to :<%= schema.singular %>, <%= inspect schema.module %>\n\n    timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}, \" %>updated_at: false)\n  end\n\n  @doc \"\"\"\n  Generates a token that will be stored in a signed place,\n  such as session or cookie. As they are signed, those\n  tokens do not need to be hashed.\n\n  The reason why we store session tokens in the database, even\n  though Phoenix already provides a session cookie, is because\n  Phoenix's default session cookies are not persisted, they are\n  simply signed and potentially encrypted. This means they are\n  valid indefinitely, unless you change the signing/encryption\n  salt.\n\n  Therefore, storing them allows individual <%= schema.singular %>\n  sessions to be expired. The token system can also be extended\n  to store additional data, such as the device used for logging in.\n  You could then use this information to display all valid sessions\n  and devices in the UI and allow users to explicitly expire any\n  session they deem invalid.\n  \"\"\"\n  def build_session_token(<%= schema.singular %>) do\n    token = :crypto.strong_rand_bytes(@rand_size)\n    dt = <%= schema.singular %>.authenticated_at || <%= datetime_now %>\n    {token, %<%= inspect schema.alias %>Token{token: token, context: \"session\", <%= schema.singular %>_id: <%= schema.singular %>.id, authenticated_at: dt}}\n  end\n\n  @doc \"\"\"\n  Checks if the token is valid and returns its underlying lookup query.\n\n  The query returns the <%= schema.singular %> found by the token, if any, along with the token's creation time.\n\n  The token is valid if it matches the value in the database and it has\n  not expired (after @session_validity_in_days).\n  \"\"\"\n  def verify_session_token_query(token) do\n    query =\n      from token in by_token_and_context_query(token, \"session\"),\n        join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),\n        where: token.inserted_at > ago(@session_validity_in_days, \"day\"),\n        select: {%{<%= schema.singular %> | authenticated_at: token.authenticated_at}, token.inserted_at}\n\n    {:ok, query}\n  end\n\n  @doc \"\"\"\n  Builds a token and its hash to be delivered to the <%= schema.singular %>'s email.\n\n  The non-hashed token is sent to the <%= schema.singular %> email while the\n  hashed part is stored in the database. The original token cannot be reconstructed,\n  which means anyone with read-only access to the database cannot directly use\n  the token in the application to gain access. Furthermore, if the <%= schema.singular %> changes\n  their email in the system, the tokens sent to the previous email are no longer\n  valid.\n\n  Users can easily adapt the existing code to provide other types of delivery methods,\n  for example, by phone numbers.\n  \"\"\"\n  def build_email_token(<%= schema.singular %>, context) do\n    build_hashed_token(<%= schema.singular %>, context, <%= schema.singular %>.email)\n  end\n\n  defp build_hashed_token(<%= schema.singular %>, context, sent_to) do\n    token = :crypto.strong_rand_bytes(@rand_size)\n    hashed_token = :crypto.hash(@hash_algorithm, token)\n\n    {Base.url_encode64(token, padding: false),\n     %<%= inspect schema.alias %>Token{\n       token: hashed_token,\n       context: context,\n       sent_to: sent_to,\n       <%= schema.singular %>_id: <%= schema.singular %>.id\n     }}\n  end\n\n  @doc \"\"\"\n  Checks if the token is valid and returns its underlying lookup query.\n\n  If found, the query returns a tuple of the form `{<%= schema.singular %>, token}`.\n\n  The given token is valid if it matches its hashed counterpart in the\n  database. This function also checks whether the token has expired. The context\n  of a magic link token is always \"login\".\n  \"\"\"\n  def verify_magic_link_token_query(token) do\n    case Base.url_decode64(token, padding: false) do\n      {:ok, decoded_token} ->\n        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)\n\n        query =\n          from token in by_token_and_context_query(hashed_token, \"login\"),\n            join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),\n            where: token.inserted_at > ago(^@magic_link_validity_in_minutes, \"minute\"),\n            where: token.sent_to == <%= schema.singular %>.email,\n            select: {<%= schema.singular %>, token}\n\n        {:ok, query}\n\n      :error ->\n        :error\n    end\n  end\n\n  @doc \"\"\"\n  Checks if the token is valid and returns its underlying lookup query.\n\n  The query returns the <%= schema.singular %>_token found by the token, if any.\n\n  This is used to validate requests to change the <%= schema.singular %>\n  email.\n  The given token is valid if it matches its hashed counterpart in the\n  database and if it has not expired (after @change_email_validity_in_days).\n  The context must always start with \"change:\".\n  \"\"\"\n  def verify_change_email_token_query(token, \"change:\" <> _ = context) do\n    case Base.url_decode64(token, padding: false) do\n      {:ok, decoded_token} ->\n        hashed_token = :crypto.hash(@hash_algorithm, decoded_token)\n\n        query =\n          from token in by_token_and_context_query(hashed_token, context),\n            where: token.inserted_at > ago(@change_email_validity_in_days, \"day\")\n\n        {:ok, query}\n\n      :error ->\n        :error\n    end\n  end\n\n  defp by_token_and_context_query(token, context) do\n    from <%= inspect schema.alias %>Token, where: [token: ^token, context: ^context]\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/scope.ex.eex",
    "content": "defmodule <%= inspect scope_config.scope.module %> do\n  @moduledoc \"\"\"\n  Defines the scope of the caller to be used throughout the app.\n\n  The `<%= inspect scope_config.scope.module %>` allows public interfaces to receive\n  information about the caller, such as if the call is initiated from an\n  end-user, and if so, which user. Additionally, such a scope can carry fields\n  such as \"super user\" or other privileges for use as authorization, or to\n  ensure specific code paths can only be access for a given scope.\n\n  It is useful for logging as well as for scoping pubsub subscriptions and\n  broadcasts when a caller subscribes to an interface or performs a particular\n  action.\n\n  Feel free to extend the fields on this struct to fit the needs of\n  growing application requirements.\n  \"\"\"\n\n  alias <%= inspect schema.module %>\n\n  defstruct <%= schema.singular %>: nil\n\n  @doc \"\"\"\n  Creates a scope for the given <%= schema.singular %>.\n\n  Returns nil if no <%= schema.singular %> is given.\n  \"\"\"\n  def for_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    %__MODULE__{<%= schema.singular %>: <%= schema.singular %>}\n  end\n\n  def for_<%= schema.singular %>(nil), do: nil\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/session_confirm.html.heex.eex",
    "content": "<Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n  <div class=\"mx-auto max-w-sm\">\n    <div class=\"text-center\">\n      <.header>Welcome {@<%= schema.singular %>.email}</.header>\n    </div>\n\n    <.form\n      :if={!@<%= schema.singular %>.confirmed_at}\n      for={@form}\n      id=\"confirmation_form\"\n      action={~p\"<%= schema.route_prefix %>/log-in?_action=confirmed\"}\n      phx-mounted={JS.focus_first()}\n    >\n      <input type=\"hidden\" name={@form[:token].name} value={@form[:token].value} />\n      <.button\n        name={@form[:remember_me].name}\n        value=\"true\"\n        phx-disable-with=\"Confirming...\"\n        class=\"btn btn-primary w-full\"\n      >\n        Confirm and stay logged in\n      </.button>\n      <.button phx-disable-with=\"Confirming...\" class=\"btn btn-primary btn-soft w-full mt-2\">\n        Confirm and log in only this time\n      </.button>\n    </.form>\n\n    <.form\n      :if={@<%= schema.singular %>.confirmed_at}\n      for={@form}\n      id=\"login_form\"\n      action={~p\"<%= schema.route_prefix %>/log-in\"}\n      phx-mounted={JS.focus_first()}\n    >\n      <input type=\"hidden\" name={@form[:token].name} value={@form[:token].value} />\n      <%%= if @<%= scope_config.scope.assign_key %> do %>\n        <.button variant=\"primary\" phx-disable-with=\"Logging in...\" class=\"btn btn-primary w-full\">\n          Log in\n        </.button>\n      <%% else %>\n        <.button\n          name={@form[:remember_me].name}\n          value=\"true\"\n          phx-disable-with=\"Logging in...\"\n          class=\"btn btn-primary w-full\"\n        >\n          Keep me logged in on this device\n        </.button>\n        <.button phx-disable-with=\"Logging in...\" class=\"btn btn-primary btn-soft w-full mt-2\">\n          Log me in only this time\n        </.button>\n      <%% end %>\n    </.form>\n\n    <p :if={!@<%= schema.singular %>.confirmed_at} class=\"alert alert-outline mt-8\">\n      Tip: If you prefer passwords, you can enable them in the <%= schema.singular %> settings.\n    </p>\n  </div>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/session_controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SessionController do\n  use <%= inspect context.web_module %>, :controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect auth_module %><%= if live? do %>\n\n  def create(conn, %{\"_action\" => \"confirmed\"} = params) do\n    create(conn, params, \"<%= schema.human_singular %> confirmed successfully.\")\n  end\n\n  def create(conn, params) do\n    create(conn, params, \"Welcome back!\")\n  end\n\n  # magic link login\n  defp create(conn, %{\"<%= schema.singular %>\" => %{\"token\" => token} = <%= schema.singular %>_params}, info) do\n    case <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(token) do\n      {:ok, {<%= schema.singular %>, tokens_to_disconnect}} ->\n        <%= inspect schema.alias %>Auth.disconnect_sessions(tokens_to_disconnect)\n\n        conn\n        |> put_flash(:info, info)\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params)\n\n      _ ->\n        conn\n        |> put_flash(:error, \"The link is invalid or it has expired.\")\n        |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n    end\n  end\n\n  # email + password login\n  defp create(conn, %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}, info) do\n    %{\"email\" => email, \"password\" => password} = <%= schema.singular %>_params\n\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(email, password) do\n      conn\n      |> put_flash(:info, info)\n      |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params)\n    else\n      # In order to prevent user enumeration attacks, don't disclose whether the email is registered.\n      conn\n      |> put_flash(:error, \"Invalid email or password\")\n      |> put_flash(:email, String.slice(email, 0, 160))\n      |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n    end\n  end\n\n  def update_password(conn, %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params) do\n    <%= schema.singular %> = conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n    true = <%= inspect context.alias %>.sudo_mode?(<%= schema.singular %>)\n    {:ok, {_<%= schema.singular %>, expired_tokens}} = <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, <%= schema.singular %>_params)\n\n    # disconnect all existing LiveViews with old sessions\n    <%= inspect schema.alias %>Auth.disconnect_sessions(expired_tokens)\n\n    conn\n    |> put_session(:<%= schema.singular %>_return_to, ~p\"<%= schema.route_prefix %>/settings\")\n    |> create(params, \"Password updated successfully!\")\n  end<% else %>\n\n  def new(conn, _params) do\n    email = get_in(conn.assigns, [:<%= scope_config.scope.assign_key %>, Access.key(:<%= schema.singular %>), Access.key(:email)])\n    form = Phoenix.Component.to_form(%{\"email\" => email}, as: \"<%= schema.singular %>\")\n\n    render(conn, :new, form: form)\n  end\n\n  # magic link login\n  def create(conn, %{\"<%= schema.singular %>\" => %{\"token\" => token} = <%= schema.singular %>_params} = params) do\n    info =\n      case params do\n        %{\"_action\" => \"confirmed\"} -> \"<%= schema.human_singular %> confirmed successfully.\"\n        _ -> \"Welcome back!\"\n      end\n\n    case <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(token) do\n      {:ok, {<%= schema.singular %>, _expired_tokens}} ->\n        conn\n        |> put_flash(:info, info)\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params)\n\n      {:error, :not_found} ->\n        conn\n        |> put_flash(:error, \"The link is invalid or it has expired.\")\n        |> render(:new, form: Phoenix.Component.to_form(%{}, as: \"<%= schema.singular %>\"))\n    end\n  end\n\n  # email + password login\n  def create(conn, %{\"<%= schema.singular %>\" => %{\"email\" => email, \"password\" => password} = <%= schema.singular %>_params}) do\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(email, password) do\n      conn\n      |> put_flash(:info, \"Welcome back!\")\n      |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>, <%= schema.singular %>_params)\n    else\n      form = Phoenix.Component.to_form(<%= schema.singular %>_params, as: \"<%= schema.singular %>\")\n\n      # In order to prevent user enumeration attacks, don't disclose whether the email is registered.\n      conn\n      |> put_flash(:error, \"Invalid email or password\")\n      |> render(:new, form: form)\n    end\n  end\n\n  # magic link request\n  def create(conn, %{\"<%= schema.singular %>\" => %{\"email\" => email}}) do\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(email) do\n      <%= inspect context.alias %>.deliver_login_instructions(\n        <%= schema.singular %>,\n        &url(~p\"<%= schema.route_prefix %>/log-in/#{&1}\")\n      )\n    end\n\n    info =\n      \"If your email is in our system, you will receive instructions for logging in shortly.\"\n\n    conn\n    |> put_flash(:info, info)\n    |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n  end\n\n  def confirm(conn, %{\"token\" => token}) do\n    if <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_magic_link_token(token) do\n      form = Phoenix.Component.to_form(%{\"token\" => token}, as: \"<%= schema.singular %>\")\n\n      conn\n      |> assign(:<%= schema.singular %>, <%= schema.singular %>)\n      |> assign(:form, form)\n      |> render(:confirm)\n    else\n      conn\n      |> put_flash(:error, \"Magic link is invalid or it has expired.\")\n      |> redirect(to: ~p\"<%= schema.route_prefix %>/log-in\")\n    end\n  end<% end %>\n\n  def delete(conn, _params) do\n    conn\n    |> put_flash(:info, \"Logged out successfully.\")\n    |> <%= inspect schema.alias %>Auth.log_out_<%= schema.singular %>()\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/session_controller_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SessionControllerTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  import <%= inspect context.module %>Fixtures\n  alias <%= inspect context.module %>\n\n  setup do\n    %{unconfirmed_<%= schema.singular %>: unconfirmed_<%= schema.singular %>_fixture(), <%= schema.singular %>: <%= schema.singular %>_fixture()}\n  end<%= if not live? do %>\n\n  describe \"GET <%= schema.route_prefix %>/log-in\" do\n    test \"renders login page\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n      response = html_response(conn, 200)\n      assert response =~ \"Log in\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/register\"\n      assert response =~ \"Log in with email\"\n    end\n\n    test \"renders login page with email filled in (sudo mode)\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      html =\n        conn\n        |> log_in_<%= schema.singular %>(<%= schema.singular %>)\n        |> get(~p\"<%= schema.route_prefix %>/log-in\")\n        |> html_response(200)\n\n      assert html =~ \"You need to reauthenticate\"\n      refute html =~ \"Register\"\n      assert html =~ \"Log in with email\"\n\n      assert html =~\n               ~s(<input type=\"email\" name=\"<%= schema.singular %>[email]\" id=\"login_form_magic_email\" value=\"#{<%= schema.singular %>.email}\")\n    end\n\n    test \"renders login page (email + password)\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/log-in?mode=password\")\n      response = html_response(conn, 200)\n      assert response =~ \"Log in\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/register\"\n      assert response =~ \"Log in with email\"\n    end\n  end\n\n  describe \"GET <%= schema.route_prefix %>/log-in/:token\" do\n    test \"renders confirmation page for unconfirmed <%= schema.singular %>\", %{conn: conn, unconfirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n      assert html_response(conn, 200) =~ \"Confirm and stay logged in\"\n    end\n\n    test \"renders login page for confirmed <%= schema.singular %>\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/log-in/#{token}\")\n      html = html_response(conn, 200)\n      refute html =~ \"Confirm my account\"\n      assert html =~ \"Log in\"\n    end\n\n    test \"raises error for invalid token\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/log-in/invalid-token\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==\n               \"Magic link is invalid or it has expired.\"\n    end\n  end<% end %>\n\n  describe \"POST <%= schema.route_prefix %>/log-in - email and password\" do\n    test \"logs the <%= schema.singular %> in\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %> = set_password(<%= schema.singular %>)\n\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => <%= schema.singular %>.email, \"password\" => valid_<%= schema.singular %>_password()}\n        })\n\n      assert get_session(conn, :<%= schema.singular %>_token)\n      assert redirected_to(conn) == ~p\"/\"\n\n      # Now do a logged in request and assert on the menu\n      conn = get(conn, ~p\"/\")\n      response = html_response(conn, 200)\n      assert response =~ <%= schema.singular %>.email\n      assert response =~ ~p\"<%= schema.route_prefix %>/settings\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/log-out\"\n    end\n\n    test \"logs the <%= schema.singular %> in with remember me\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %> = set_password(<%= schema.singular %>)\n\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\n            \"email\" => <%= schema.singular %>.email,\n            \"password\" => valid_<%= schema.singular %>_password(),\n            \"remember_me\" => \"true\"\n          }\n        })\n\n      assert conn.resp_cookies[\"_<%= web_app_name %>_<%= schema.singular %>_remember_me\"]\n      assert redirected_to(conn) == ~p\"/\"\n    end\n\n    test \"logs the <%= schema.singular %> in with return to\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %> = set_password(<%= schema.singular %>)\n\n      conn =\n        conn\n        |> init_test_session(<%= schema.singular %>_return_to: \"/foo/bar\")\n        |> post(~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\n            \"email\" => <%= schema.singular %>.email,\n            \"password\" => valid_<%= schema.singular %>_password()\n          }\n        })\n\n      assert redirected_to(conn) == \"/foo/bar\"\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ \"Welcome back!\"\n    end\n\n    test \"<%= if live?, do: \"redirects to login page\", else: \"emits error message\" %> with invalid credentials\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in?mode=password\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => <%= schema.singular %>.email, \"password\" => \"invalid_password\"}\n        })\n\n      <%= if live? do %>assert Phoenix.Flash.get(conn.assigns.flash, :error) == \"Invalid email or password\"\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"<% else %>response = html_response(conn, 200)\n      assert response =~ \"Log in\"\n      assert response =~ \"Invalid email or password\"<% end %>\n    end\n  end\n\n  describe \"POST <%= schema.route_prefix %>/log-in - magic link\" do\n    <%= if not live? do %>test \"sends magic link email when <%= schema.singular %> exists\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => <%= schema.singular %>.email}\n        })\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ \"If your email is in our system\"\n      assert <%= inspect schema.repo %>.get_by!(<%= inspect context.alias %>.<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id).context == \"login\"\n    end\n\n    <% end %>test \"logs the <%= schema.singular %> in\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      {token, _hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\"token\" => token}\n        })\n\n      assert get_session(conn, :<%= schema.singular %>_token)\n      assert redirected_to(conn) == ~p\"/\"\n\n      # Now do a logged in request and assert on the menu\n      conn = get(conn, ~p\"/\")\n      response = html_response(conn, 200)\n      assert response =~ <%= schema.singular %>.email\n      assert response =~ ~p\"<%= schema.route_prefix %>/settings\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/log-out\"\n    end\n\n    test \"confirms unconfirmed <%= schema.singular %>\", %{conn: conn, unconfirmed_<%= schema.singular %>: <%= schema.singular %>} do\n      {token, _hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n      refute <%= schema.singular %>.confirmed_at\n\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\"token\" => token},\n          \"_action\" => \"confirmed\"\n        })\n\n      assert get_session(conn, :<%= schema.singular %>_token)\n      assert redirected_to(conn) == ~p\"/\"\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ \"<%= schema.human_singular %> confirmed successfully.\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.id).confirmed_at\n\n      # Now do a logged in request and assert on the menu\n      conn = get(conn, ~p\"/\")\n      response = html_response(conn, 200)\n      assert response =~ <%= schema.singular %>.email\n      assert response =~ ~p\"<%= schema.route_prefix %>/settings\"\n      assert response =~ ~p\"<%= schema.route_prefix %>/log-out\"\n    end\n\n    test \"<%= if live?, do: \"redirects to login page\", else: \"emits error message\" %> when magic link is invalid\", %{conn: conn} do\n      conn =\n        post(conn, ~p\"<%= schema.route_prefix %>/log-in\", %{\n          \"<%= schema.singular %>\" => %{\"token\" => \"invalid\"}\n        })\n\n      <%= if live? do %>assert Phoenix.Flash.get(conn.assigns.flash, :error) ==\n               \"The link is invalid or it has expired.\"\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"<% else %>assert html_response(conn, 200) =~ \"The link is invalid or it has expired.\"<% end %>\n    end\n  end\n\n  describe \"DELETE <%= schema.route_prefix %>/log-out\" do\n    test \"logs the <%= schema.singular %> out\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = conn |> log_in_<%= schema.singular %>(<%= schema.singular %>) |> delete(~p\"<%= schema.route_prefix %>/log-out\")\n      assert redirected_to(conn) == ~p\"/\"\n      refute get_session(conn, :<%= schema.singular %>_token)\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ \"Logged out successfully\"\n    end\n\n    test \"succeeds even if the <%= schema.singular %> is not logged in\", %{conn: conn} do\n      conn = delete(conn, ~p\"<%= schema.route_prefix %>/log-out\")\n      assert redirected_to(conn) == ~p\"/\"\n      refute get_session(conn, :<%= schema.singular %>_token)\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ \"Logged out successfully\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/session_html.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SessionHTML do\n  use <%= inspect context.web_module %>, :html\n\n  embed_templates \"<%= schema.singular %>_session_html/*\"\n\n  defp local_mail_adapter? do\n    Application.get_env(:<%= Mix.Phoenix.otp_app() %>, <%= inspect context.base_module %>.Mailer)[:adapter] == Swoosh.Adapters.Local\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/session_new.html.heex.eex",
    "content": "<Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n  <div class=\"mx-auto max-w-sm space-y-4\">\n    <div class=\"text-center\">\n      <.header>\n        <p>Log in</p>\n        <:subtitle>\n          <%%= if @<%= scope_config.scope.assign_key %> do %>\n            You need to reauthenticate to perform sensitive actions on your account.\n          <%% else %>\n            Don't have an account? <.link\n              navigate={~p\"<%= schema.route_prefix %>/register\"}\n              class=\"font-semibold text-brand hover:underline\"\n              phx-no-format\n            >Sign up</.link> for an account now.\n          <%% end %>\n        </:subtitle>\n      </.header>\n    </div>\n\n    <div :if={local_mail_adapter?()} class=\"alert alert-info\">\n      <.icon name=\"hero-information-circle\" class=\"size-6 shrink-0\" />\n      <div>\n        <p>You are running the local mail adapter.</p>\n        <p>\n          To see sent emails, visit <.link href=\"/dev/mailbox\" class=\"underline\">the mailbox page</.link>.\n        </p>\n      </div>\n    </div>\n\n    <.form :let={f} for={@form} as={:<%= schema.singular %>} id=\"login_form_magic\" action={~p\"<%= schema.route_prefix %>/log-in\"}>\n      <.input\n        readonly={!!@<%= scope_config.scope.assign_key %>}\n        field={f[:email]}\n        type=\"email\"\n        label=\"Email\"\n        autocomplete=\"username\"\n        spellcheck=\"false\"\n        required\n        phx-mounted={JS.focus()}\n      />\n      <.button class=\"btn btn-primary w-full\">\n        Log in with email <span aria-hidden=\"true\">→</span>\n      </.button>\n    </.form>\n\n    <div class=\"divider\">or</div>\n\n    <.form :let={f} for={@form} as={:<%= schema.singular %>} id=\"login_form_password\" action={~p\"<%= schema.route_prefix %>/log-in\"}>\n      <.input\n        readonly={!!@<%= scope_config.scope.assign_key %>}\n        field={f[:email]}\n        type=\"email\"\n        label=\"Email\"\n        autocomplete=\"username\"\n        spellcheck=\"false\"\n        required\n      />\n      <.input\n        field={f[:password]}\n        type=\"password\"\n        label=\"Password\"\n        autocomplete=\"current-password\"\n        spellcheck=\"false\"\n      />\n      <.button class=\"btn btn-primary w-full\" name={@form[:remember_me].name} value=\"true\">\n        Log in and stay logged in <span aria-hidden=\"true\">→</span>\n      </.button>\n      <.button class=\"btn btn-primary btn-soft w-full mt-2\">\n        Log in only this time\n      </.button>\n    </.form>\n  </div>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SettingsController do\n  use <%= inspect context.web_module %>, :controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect auth_module %>\n\n  import <%= inspect auth_module %>, only: [require_sudo_mode: 2]\n\n  plug :require_sudo_mode\n  plug :assign_email_and_password_changesets\n\n  def edit(conn, _params) do\n    render(conn, :edit)\n  end\n\n  def update(conn, %{\"action\" => \"update_email\"} = params) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n    <%= schema.singular %> = conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n\n    case <%= inspect context.alias %>.change_<%= schema.singular %>_email(<%= schema.singular %>, <%= schema.singular %>_params) do\n      %{valid?: true} = changeset ->\n        <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(\n          Ecto.Changeset.apply_action!(changeset, :insert),\n          <%= schema.singular %>.email,\n          &url(~p\"<%= schema.route_prefix %>/settings/confirm-email/#{&1}\")\n        )\n\n        conn\n        |> put_flash(\n          :info,\n          \"A link to confirm your email change has been sent to the new address.\"\n        )\n        |> redirect(to: ~p\"<%= schema.route_prefix %>/settings\")\n\n      changeset ->\n        render(conn, :edit, email_changeset: %{changeset | action: :insert})\n    end\n  end\n\n  def update(conn, %{\"action\" => \"update_password\"} = params) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n    <%= schema.singular %> = conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n\n    case <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, <%= schema.singular %>_params) do\n      {:ok, {<%= schema.singular %>, _}} ->\n        conn\n        |> put_flash(:info, \"Password updated successfully.\")\n        |> put_session(:<%= schema.singular %>_return_to, ~p\"<%= schema.route_prefix %>/settings\")\n        |> <%= inspect schema.alias %>Auth.log_in_<%= schema.singular %>(<%= schema.singular %>)\n\n      {:error, changeset} ->\n        render(conn, :edit, password_changeset: changeset)\n    end\n  end\n\n  def confirm_email(conn, %{\"token\" => token}) do\n    case <%= inspect context.alias %>.update_<%= schema.singular %>_email(conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>, token) do\n      {:ok, _<%= schema.singular %>} ->\n        conn\n        |> put_flash(:info, \"Email changed successfully.\")\n        |> redirect(to: ~p\"<%= schema.route_prefix %>/settings\")\n\n      {:error, _} ->\n        conn\n        |> put_flash(:error, \"Email change link is invalid or it has expired.\")\n        |> redirect(to: ~p\"<%= schema.route_prefix %>/settings\")\n    end\n  end\n\n  defp assign_email_and_password_changesets(conn, _opts) do\n    <%= schema.singular %> = conn.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n\n    conn\n    |> assign(:email_changeset, <%= inspect context.alias %>.change_<%= schema.singular %>_email(<%= schema.singular %>))\n    |> assign(:password_changeset, <%= inspect context.alias %>.change_<%= schema.singular %>_password(<%= schema.singular %>))\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_controller_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SettingsControllerTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  alias <%= inspect context.module %>\n  import <%= inspect context.module %>Fixtures\n\n  setup :register_and_log_in_<%= schema.singular %>\n\n  describe \"GET <%= schema.route_prefix %>/settings\" do\n    test \"renders settings page\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings\")\n      response = html_response(conn, 200)\n      assert response =~ \"Settings\"\n    end\n\n    test \"redirects if <%= schema.singular %> is not logged in\" do\n      conn = build_conn()\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n    end\n\n    @tag token_authenticated_at: <%= inspect datetime_module %>.add(<%= datetime_now %>, -11, :minute)\n    test \"redirects if <%= schema.singular %> is not in sudo mode\", %{conn: conn} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) ==\n               \"You must re-authenticate to access this page.\"\n    end\n  end\n\n  describe \"PUT <%= schema.route_prefix %>/settings (change password form)\" do\n    test \"updates the <%= schema.singular %> password and resets tokens\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      new_password_conn =\n        put(conn, ~p\"<%= schema.route_prefix %>/settings\", %{\n          \"action\" => \"update_password\",\n          \"<%= schema.singular %>\" => %{\n            \"password\" => \"new valid password\",\n            \"password_confirmation\" => \"new valid password\"\n          }\n        })\n\n      assert redirected_to(new_password_conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert get_session(new_password_conn, :<%= schema.singular %>_token) != get_session(conn, :<%= schema.singular %>_token)\n\n      assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~\n               \"Password updated successfully\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, \"new valid password\")\n    end\n\n    test \"does not update password on invalid data\", %{conn: conn} do\n      old_password_conn =\n        put(conn, ~p\"<%= schema.route_prefix %>/settings\", %{\n          \"action\" => \"update_password\",\n          \"<%= schema.singular %>\" => %{\n            \"password\" => \"too short\",\n            \"password_confirmation\" => \"does not match\"\n          }\n        })\n\n      response = html_response(old_password_conn, 200)\n      assert response =~ \"Settings\"\n      assert response =~ \"should be at least 12 character(s)\"\n      assert response =~ \"does not match password\"\n\n      assert get_session(old_password_conn, :<%= schema.singular %>_token) == get_session(conn, :<%= schema.singular %>_token)\n    end\n  end\n\n  describe \"PUT <%= schema.route_prefix %>/settings (change email form)\" do\n    @tag :capture_log\n    test \"updates the <%= schema.singular %> email\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn =\n        put(conn, ~p\"<%= schema.route_prefix %>/settings\", %{\n          \"action\" => \"update_email\",\n          \"<%= schema.singular %>\" => %{\"email\" => unique_<%= schema.singular %>_email()}\n        })\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~\n               \"A link to confirm your email\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n    end\n\n    test \"does not update email on invalid data\", %{conn: conn} do\n      conn =\n        put(conn, ~p\"<%= schema.route_prefix %>/settings\", %{\n          \"action\" => \"update_email\",\n          \"<%= schema.singular %>\" => %{\"email\" => \"with spaces\"}\n        })\n\n      response = html_response(conn, 200)\n      assert response =~ \"Settings\"\n      assert response =~ \"must have the @ sign and no spaces\"\n    end\n  end\n\n  describe \"GET <%= schema.route_prefix %>/settings/confirm-email/:token\" do\n    setup %{<%= schema.singular %>: <%= schema.singular %>} do\n      email = unique_<%= schema.singular %>_email()\n\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(%{<%= schema.singular %> | email: email}, <%= schema.singular %>.email, url)\n        end)\n\n      %{token: token, email: email}\n    end\n\n    test \"updates the <%= schema.singular %> email once\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>, token: token, email: email} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :info) =~\n               \"Email changed successfully\"\n\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(email)\n\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) =~\n               \"Email change link is invalid or it has expired\"\n    end\n\n    test \"does not update email with invalid token\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/oops\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert Phoenix.Flash.get(conn.assigns.flash, :error) =~\n               \"Email change link is invalid or it has expired\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n    end\n\n    test \"redirects if <%= schema.singular %> is not logged in\", %{token: token} do\n      conn = build_conn()\n      conn = get(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n      assert redirected_to(conn) == ~p\"<%= schema.route_prefix %>/log-in\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_edit.html.heex.eex",
    "content": "<Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n  <div class=\"text-center\">\n    <.header>\n      Account Settings\n      <:subtitle>Manage your account email address and password settings</:subtitle>\n    </.header>\n  </div>\n\n  <.form :let={f} for={@email_changeset} action={~p\"<%= schema.route_prefix %>/settings\"} id=\"update_email\">\n    <input type=\"hidden\" name=\"action\" value=\"update_email\" />\n\n    <.input\n      field={f[:email]}\n      type=\"email\"\n      label=\"Email\"\n      autocomplete=\"username\"\n      spellcheck=\"false\"\n      required\n    />\n\n    <.button variant=\"primary\" phx-disable-with=\"Changing...\">Change Email</.button>\n  </.form>\n\n  <div class=\"divider\" />\n\n  <.form :let={f} for={@password_changeset} action={~p\"<%= schema.route_prefix %>/settings\"} id=\"update_password\">\n    <input type=\"hidden\" name=\"action\" value=\"update_password\" />\n\n    <.input\n      field={f[:password]}\n      type=\"password\"\n      label=\"New password\"\n      autocomplete=\"new-password\"\n      spellcheck=\"false\"\n      required\n    />\n    <.input\n      field={f[:password_confirmation]}\n      type=\"password\"\n      label=\"Confirm new password\"\n      autocomplete=\"new-password\"\n      spellcheck=\"false\"\n      required\n    />\n    <.button variant=\"primary\" phx-disable-with=\"Changing...\">\n      Save Password\n    </.button>\n  </.form>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_html.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>SettingsHTML do\n  use <%= inspect context.web_module %>, :html\n\n  embed_templates \"<%= schema.singular %>_settings_html/*\"\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_live.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Settings do\n  use <%= inspect context.web_module %>, :live_view\n\n  on_mount {<%= inspect auth_module %>, :require_sudo_mode}\n\n  alias <%= inspect context.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash} <%= scope_config.scope.assign_key %>={@<%= scope_config.scope.assign_key %>}>\n      <div class=\"text-center\">\n        <.header>\n          Account Settings\n          <:subtitle>Manage your account email address and password settings</:subtitle>\n        </.header>\n      </div>\n\n      <.form for={@email_form} id=\"email_form\" phx-submit=\"update_email\" phx-change=\"validate_email\">\n        <.input\n          field={@email_form[:email]}\n          type=\"email\"\n          label=\"Email\"\n          autocomplete=\"username\"\n          spellcheck=\"false\"\n          required\n        />\n        <.button variant=\"primary\" phx-disable-with=\"Changing...\">Change Email</.button>\n      </.form>\n\n      <div class=\"divider\" />\n\n      <.form\n        for={@password_form}\n        id=\"password_form\"\n        action={~p\"<%= schema.route_prefix %>/update-password\"}\n        method=\"post\"\n        phx-change=\"validate_password\"\n        phx-submit=\"update_password\"\n        phx-trigger-action={@trigger_submit}\n      >\n        <input\n          name={@password_form[:email].name}\n          type=\"hidden\"\n          id=\"hidden_<%= schema.singular %>_email\"\n          spellcheck=\"false\"\n          value={@current_email}\n        />\n        <.input\n          field={@password_form[:password]}\n          type=\"password\"\n          label=\"New password\"\n          autocomplete=\"new-password\"\n          spellcheck=\"false\"\n          required\n        />\n        <.input\n          field={@password_form[:password_confirmation]}\n          type=\"password\"\n          label=\"Confirm new password\"\n          autocomplete=\"new-password\"\n          spellcheck=\"false\"\n        />\n        <.button variant=\"primary\" phx-disable-with=\"Saving...\">\n          Save Password\n        </.button>\n      </.form>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(%{\"token\" => token}, _session, socket) do\n    socket =\n      case <%= inspect context.alias %>.update_<%= schema.singular %>_email(socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>, token) do\n        {:ok, _<%= schema.singular %>} ->\n          put_flash(socket, :info, \"Email changed successfully.\")\n\n        {:error, _} ->\n          put_flash(socket, :error, \"Email change link is invalid or it has expired.\")\n      end\n\n    {:ok, push_navigate(socket, to: ~p\"<%= schema.route_prefix %>/settings\")}\n  end\n\n  def mount(_params, _session, socket) do\n    <%= schema.singular %> = socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n    email_changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_email(<%= schema.singular %>, %{}, validate_unique: false)\n    password_changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_password(<%= schema.singular %>, %{}, hash_password: false)\n\n    socket =\n      socket\n      |> assign(:current_email, <%= schema.singular %>.email)\n      |> assign(:email_form, to_form(email_changeset))\n      |> assign(:password_form, to_form(password_changeset))\n      |> assign(:trigger_submit, false)\n\n    {:ok, socket}\n  end\n\n  @impl true\n  def handle_event(\"validate_email\", params, socket) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n\n    email_form =\n      socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n      |> <%= inspect context.alias %>.change_<%= schema.singular %>_email(<%= schema.singular %>_params, validate_unique: false)\n      |> Map.put(:action, :validate)\n      |> to_form()\n\n    {:noreply, assign(socket, email_form: email_form)}\n  end\n\n  def handle_event(\"update_email\", params, socket) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n    <%= schema.singular %> = socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n    true = <%= inspect context.alias %>.sudo_mode?(<%= schema.singular %>)\n\n    case <%= inspect context.alias %>.change_<%= schema.singular %>_email(<%= schema.singular %>, <%= schema.singular %>_params) do\n      %{valid?: true} = changeset ->\n        <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(\n          Ecto.Changeset.apply_action!(changeset, :insert),\n          <%= schema.singular %>.email,\n          &url(~p\"<%= schema.route_prefix %>/settings/confirm-email/#{&1}\")\n        )\n\n        info = \"A link to confirm your email change has been sent to the new address.\"\n        {:noreply, socket |> put_flash(:info, info)}\n\n      changeset ->\n        {:noreply, assign(socket, :email_form, to_form(changeset, action: :insert))}\n    end\n  end\n\n  def handle_event(\"validate_password\", params, socket) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n\n    password_form =\n      socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n      |> <%= inspect context.alias %>.change_<%= schema.singular %>_password(<%= schema.singular %>_params, hash_password: false)\n      |> Map.put(:action, :validate)\n      |> to_form()\n\n    {:noreply, assign(socket, password_form: password_form)}\n  end\n\n  def handle_event(\"update_password\", params, socket) do\n    %{\"<%= schema.singular %>\" => <%= schema.singular %>_params} = params\n    <%= schema.singular %> = socket.assigns.<%= scope_config.scope.assign_key %>.<%= schema.singular %>\n    true = <%= inspect context.alias %>.sudo_mode?(<%= schema.singular %>)\n\n    case <%= inspect context.alias %>.change_<%= schema.singular %>_password(<%= schema.singular %>, <%= schema.singular %>_params) do\n      %{valid?: true} = changeset ->\n        {:noreply, assign(socket, trigger_submit: true, password_form: to_form(changeset))}\n\n      changeset ->\n        {:noreply, assign(socket, password_form: to_form(changeset, action: :insert))}\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/settings_live_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.SettingsTest do\n  use <%= inspect context.web_module %>.ConnCase<%= test_case_options %>\n\n  alias <%= inspect context.module %>\n  import Phoenix.LiveViewTest\n  import <%= inspect context.module %>Fixtures\n\n  describe \"Settings page\" do\n    test \"renders settings page\", %{conn: conn} do\n      {:ok, _lv, html} =\n        conn\n        |> log_in_<%= schema.singular %>(<%= schema.singular %>_fixture())\n        |> live(~p\"<%= schema.route_prefix %>/settings\")\n\n      assert html =~ \"Change Email\"\n      assert html =~ \"Save Password\"\n    end\n\n    test \"redirects if <%= schema.singular %> is not logged in\", %{conn: conn} do\n      assert {:error, redirect} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      assert {:redirect, %{to: path, flash: flash}} = redirect\n      assert path == ~p\"<%= schema.route_prefix %>/log-in\"\n      assert %{\"error\" => \"You must log in to access this page.\"} = flash\n    end\n\n    test \"redirects if <%= schema.singular %> is not in sudo mode\", %{conn: conn} do\n      {:ok, conn} =\n        conn\n        |> log_in_<%= schema.singular %>(<%= schema.singular %>_fixture(),\n          token_authenticated_at: <%= inspect datetime_module %>.add(<%= datetime_now %>, -11, :minute)\n        )\n        |> live(~p\"<%= schema.route_prefix %>/settings\")\n        |> follow_redirect(conn, ~p\"<%= schema.route_prefix %>/log-in\")\n\n      assert conn.resp_body =~ \"You must re-authenticate to access this page.\"\n    end\n  end\n\n  describe \"update email form\" do\n    setup %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      %{conn: log_in_<%= schema.singular %>(conn, <%= schema.singular %>), <%= schema.singular %>: <%= schema.singular %>}\n    end\n\n    test \"updates the <%= schema.singular %> email\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      new_email = unique_<%= schema.singular %>_email()\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      result =\n        lv\n        |> form(\"#email_form\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => new_email}\n        })\n        |> render_submit()\n\n      assert result =~ \"A link to confirm your email\"\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n    end\n\n    test \"renders errors with invalid data (phx-change)\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      result =\n        lv\n        |> element(\"#email_form\")\n        |> render_change(%{\n          \"action\" => \"update_email\",\n          \"<%= schema.singular %>\" => %{\"email\" => \"with spaces\"}\n        })\n\n      assert result =~ \"Change Email\"\n      assert result =~ \"must have the @ sign and no spaces\"\n    end\n\n    test \"renders errors with invalid data (phx-submit)\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      result =\n        lv\n        |> form(\"#email_form\", %{\n          \"<%= schema.singular %>\" => %{\"email\" => <%= schema.singular %>.email}\n        })\n        |> render_submit()\n\n      assert result =~ \"Change Email\"\n      assert result =~ \"did not change\"\n    end\n  end\n\n  describe \"update password form\" do\n    setup %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      %{conn: log_in_<%= schema.singular %>(conn, <%= schema.singular %>), <%= schema.singular %>: <%= schema.singular %>}\n    end\n\n    test \"updates the <%= schema.singular %> password\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      new_password = valid_<%= schema.singular %>_password()\n\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      form =\n        form(lv, \"#password_form\", %{\n          \"<%= schema.singular %>\" => %{\n            \"email\" => <%= schema.singular %>.email,\n            \"password\" => new_password,\n            \"password_confirmation\" => new_password\n          }\n        })\n\n      render_submit(form)\n\n      new_password_conn = follow_trigger_action(form, conn)\n\n      assert redirected_to(new_password_conn) == ~p\"<%= schema.route_prefix %>/settings\"\n\n      assert get_session(new_password_conn, :<%= schema.singular %>_token) != get_session(conn, :<%= schema.singular %>_token)\n\n      assert Phoenix.Flash.get(new_password_conn.assigns.flash, :info) =~\n               \"Password updated successfully\"\n\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, new_password)\n    end\n\n    test \"renders errors with invalid data (phx-change)\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      result =\n        lv\n        |> element(\"#password_form\")\n        |> render_change(%{\n          \"<%= schema.singular %>\" => %{\n            \"password\" => \"too short\",\n            \"password_confirmation\" => \"does not match\"\n          }\n        })\n\n      assert result =~ \"Save Password\"\n      assert result =~ \"should be at least 12 character(s)\"\n      assert result =~ \"does not match password\"\n    end\n\n    test \"renders errors with invalid data (phx-submit)\", %{conn: conn} do\n      {:ok, lv, _html} = live(conn, ~p\"<%= schema.route_prefix %>/settings\")\n\n      result =\n        lv\n        |> form(\"#password_form\", %{\n          \"<%= schema.singular %>\" => %{\n            \"password\" => \"too short\",\n            \"password_confirmation\" => \"does not match\"\n          }\n        })\n        |> render_submit()\n\n      assert result =~ \"Save Password\"\n      assert result =~ \"should be at least 12 character(s)\"\n      assert result =~ \"does not match password\"\n    end\n  end\n\n  describe \"confirm email\" do\n    setup %{conn: conn} do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      email = unique_<%= schema.singular %>_email()\n\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(%{<%= schema.singular %> | email: email}, <%= schema.singular %>.email, url)\n        end)\n\n      %{conn: log_in_<%= schema.singular %>(conn, <%= schema.singular %>), token: token, email: email, <%= schema.singular %>: <%= schema.singular %>}\n    end\n\n    test \"updates the <%= schema.singular %> email once\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>, token: token, email: email} do\n      {:error, redirect} = live(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n\n      assert {:live_redirect, %{to: path, flash: flash}} = redirect\n      assert path == ~p\"<%= schema.route_prefix %>/settings\"\n      assert %{\"info\" => message} = flash\n      assert message == \"Email changed successfully.\"\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(email)\n\n      # use confirm token again\n      {:error, redirect} = live(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n      assert {:live_redirect, %{to: path, flash: flash}} = redirect\n      assert path == ~p\"<%= schema.route_prefix %>/settings\"\n      assert %{\"error\" => message} = flash\n      assert message == \"Email change link is invalid or it has expired.\"\n    end\n\n    test \"does not update email with invalid token\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %>} do\n      {:error, redirect} = live(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/oops\")\n      assert {:live_redirect, %{to: path, flash: flash}} = redirect\n      assert path == ~p\"<%= schema.route_prefix %>/settings\"\n      assert %{\"error\" => message} = flash\n      assert message == \"Email change link is invalid or it has expired.\"\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n    end\n\n    test \"redirects if <%= schema.singular %> is not logged in\", %{token: token} do\n      conn = build_conn()\n      {:error, redirect} = live(conn, ~p\"<%= schema.route_prefix %>/settings/confirm-email/#{token}\")\n      assert {:redirect, %{to: path, flash: flash}} = redirect\n      assert path == ~p\"<%= schema.route_prefix %>/log-in\"\n      assert %{\"error\" => message} = flash\n      assert message == \"You must log in to access this page.\"\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.auth/test_cases.exs.eex",
    "content": "  import <%= inspect context.module %>Fixtures\n  alias <%= inspect context.module %>.{<%= inspect schema.alias %>, <%= inspect schema.alias %>Token}\n\n  describe \"get_<%= schema.singular %>_by_email/1\" do\n    test \"does not return the <%= schema.singular %> if the email does not exist\" do\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(\"unknown@example.com\")\n    end\n\n    test \"returns the <%= schema.singular %> if the email exists\" do\n      %{id: id} = <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert %<%= inspect schema.alias %>{id: ^id} = <%= inspect context.alias %>.get_<%= schema.singular %>_by_email(<%= schema.singular %>.email)\n    end\n  end\n\n  describe \"get_<%= schema.singular %>_by_email_and_password/2\" do\n    test \"does not return the <%= schema.singular %> if the email does not exist\" do\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(\"unknown@example.com\", \"hello world!\")\n    end\n\n    test \"does not return the <%= schema.singular %> if the password is not valid\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture() |> set_password()\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, \"invalid\")\n    end\n\n    test \"returns the <%= schema.singular %> if the email and password are valid\" do\n      %{id: id} = <%= schema.singular %> = <%= schema.singular %>_fixture() |> set_password()\n\n      assert %<%= inspect schema.alias %>{id: ^id} =\n               <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, valid_<%= schema.singular %>_password())\n    end\n  end\n\n  describe \"get_<%= schema.singular %>!/1\" do\n    test \"raises if id is invalid\" do\n      assert_raise Ecto.NoResultsError, fn ->\n        <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= inspect schema.sample_id %>)\n      end\n    end\n\n    test \"returns the <%= schema.singular %> with the given id\" do\n      %{id: id} = <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert %<%= inspect schema.alias %>{id: ^id} = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%=schema.singular %>.id)\n    end\n  end\n\n  describe \"register_<%= schema.singular %>/1\" do\n    test \"requires email to be set\" do\n      {:error, changeset} = <%= inspect context.alias %>.register_<%= schema.singular %>(%{})\n\n      assert %{email: [\"can't be blank\"]} = errors_on(changeset)\n    end\n\n    test \"validates email when given\" do\n      {:error, changeset} = <%= inspect context.alias %>.register_<%= schema.singular %>(%{email: \"not valid\"})\n\n      assert %{email: [\"must have the @ sign and no spaces\"]} = errors_on(changeset)\n    end\n\n    test \"validates maximum values for email for security\" do\n      too_long = String.duplicate(\"db\", 100)\n      {:error, changeset} = <%= inspect context.alias %>.register_<%= schema.singular %>(%{email: too_long})\n      assert \"should be at most 160 character(s)\" in errors_on(changeset).email\n    end\n\n    test \"validates email uniqueness\" do\n      %{email: email} = <%= schema.singular %>_fixture()\n      {:error, changeset} = <%= inspect context.alias %>.register_<%= schema.singular %>(%{email: email})\n      assert \"has already been taken\" in errors_on(changeset).email\n\n      # Now try with the uppercased email too, to check that email case is ignored.\n      {:error, changeset} = <%= inspect context.alias %>.register_<%= schema.singular %>(%{email: String.upcase(email)})\n      assert \"has already been taken\" in errors_on(changeset).email\n    end\n\n    test \"registers <%= schema.plural %> without password\" do\n      email = unique_<%= schema.singular %>_email()\n      {:ok, <%= schema.singular %>} = <%= inspect context.alias %>.register_<%= schema.singular %>(valid_<%= schema.singular %>_attributes(email: email))\n      assert <%= schema.singular %>.email == email\n      assert is_nil(<%= schema.singular %>.hashed_password)\n      assert is_nil(<%= schema.singular %>.confirmed_at)\n      assert is_nil(<%= schema.singular %>.password)\n    end\n  end\n\n  describe \"sudo_mode?/2\" do\n    test \"validates the authenticated_at time\" do\n      now = <%= inspect datetime_module %>.utc_now()\n\n      assert <%= inspect context.alias %>.sudo_mode?(%<%= inspect schema.alias %>{authenticated_at: <%= inspect datetime_module %>.utc_now()})\n      assert <%= inspect context.alias %>.sudo_mode?(%<%= inspect schema.alias %>{authenticated_at: <%= inspect datetime_module %>.add(now, -19, :minute)})\n      refute <%= inspect context.alias %>.sudo_mode?(%<%= inspect schema.alias %>{authenticated_at: <%= inspect datetime_module %>.add(now, -21, :minute)})\n\n      # minute override\n      refute <%= inspect context.alias %>.sudo_mode?(\n               %<%= inspect schema.alias %>{authenticated_at: <%= inspect datetime_module %>.add(now, -11, :minute)},\n               -10\n             )\n\n      # not authenticated\n      refute <%= inspect context.alias %>.sudo_mode?(%<%= inspect schema.alias %>{})\n    end\n  end\n\n  describe \"change_<%= schema.singular %>_email/3\" do\n    test \"returns a <%= schema.singular %> changeset\" do\n      assert %Ecto.Changeset{} = changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_email(%<%= inspect schema.alias %>{})\n      assert changeset.required == [:email]\n    end\n  end\n\n  describe \"deliver_<%= schema.singular %>_update_email_instructions/3\" do\n    setup do\n      %{<%= schema.singular %>: <%= schema.singular %>_fixture()}\n    end\n\n    test \"sends token through notification\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(<%= schema.singular %>, \"current@example.com\", url)\n        end)\n\n      {:ok, token} = Base.url_decode64(token, padding: false)\n      assert <%= schema.singular %>_token = Repo.get_by(<%= inspect schema.alias %>Token, token: :crypto.hash(:sha256, token))\n      assert <%= schema.singular %>_token.<%= schema.singular %>_id == <%= schema.singular %>.id\n      assert <%= schema.singular %>_token.sent_to == <%= schema.singular %>.email\n      assert <%= schema.singular %>_token.context == \"change:current@example.com\"\n    end\n  end\n\n  describe \"update_<%= schema.singular %>_email/2\" do\n    setup do\n      <%= schema.singular %> = unconfirmed_<%= schema.singular %>_fixture()\n      email = unique_<%= schema.singular %>_email()\n\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_<%= schema.singular %>_update_email_instructions(%{<%= schema.singular %> | email: email}, <%= schema.singular %>.email, url)\n        end)\n\n      %{<%= schema.singular %>: <%= schema.singular %>, token: token, email: email}\n    end\n\n    test \"updates the email with a valid token\", %{<%= schema.singular %>: <%= schema.singular %>, token: token, email: email} do\n      assert {:ok, %{email: ^email}} = <%= inspect context.alias %>.update_<%= schema.singular %>_email(<%= schema.singular %>, token)\n      changed_<%= schema.singular %> = Repo.get!(<%= inspect schema.alias %>, <%= schema.singular %>.id)\n      assert changed_<%= schema.singular %>.email != <%= schema.singular %>.email\n      assert changed_<%= schema.singular %>.email == email\n      refute Repo.get_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n    end\n\n    test \"does not update email with invalid token\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      assert <%= inspect context.alias %>.update_<%= schema.singular %>_email(<%= schema.singular %>, \"oops\") ==\n               {:error, :transaction_aborted}\n\n      assert Repo.get!(<%= inspect schema.alias %>, <%= schema.singular %>.id).email == <%= schema.singular %>.email\n      assert Repo.get_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n    end\n\n    test \"does not update email if <%= schema.singular %> email changed\", %{<%= schema.singular %>: <%= schema.singular %>, token: token} do\n      assert <%= inspect context.alias %>.update_<%= schema.singular %>_email(%{<%= schema.singular %> | email: \"current@example.com\"}, token) ==\n               {:error, :transaction_aborted}\n\n      assert Repo.get!(<%= inspect schema.alias %>, <%= schema.singular %>.id).email == <%= schema.singular %>.email\n      assert Repo.get_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n    end\n\n    test \"does not update email if token expired\", %{<%= schema.singular %>: <%= schema.singular %>, token: token} do\n      {1, nil} = Repo.update_all(<%= inspect schema.alias %>Token, set: [inserted_at: ~N[2020-01-01 00:00:00]])\n\n      assert <%= inspect context.alias %>.update_<%= schema.singular %>_email(<%= schema.singular %>, token) ==\n               {:error, :transaction_aborted}\n\n      assert Repo.get!(<%= inspect schema.alias %>, <%= schema.singular %>.id).email == <%= schema.singular %>.email\n      assert Repo.get_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n    end\n  end\n\n  describe \"change_<%= schema.singular %>_password/3\" do\n    test \"returns a <%= schema.singular %> changeset\" do\n      assert %Ecto.Changeset{} = changeset = <%= inspect context.alias %>.change_<%= schema.singular %>_password(%<%= inspect schema.alias %>{})\n      assert changeset.required == [:password]\n    end\n\n    test \"allows fields to be set\" do\n      changeset =\n        <%= inspect context.alias %>.change_<%= schema.singular %>_password(\n          %<%= inspect schema.alias %>{},\n          %{\n            \"password\" => \"new valid password\"\n          },\n          hash_password: false\n        )\n\n      assert changeset.valid?\n      assert get_change(changeset, :password) == \"new valid password\"\n      assert is_nil(get_change(changeset, :hashed_password))\n    end\n  end\n\n  describe \"update_<%= schema.singular %>_password/2\" do\n    setup do\n      %{<%= schema.singular %>: <%= schema.singular %>_fixture()}\n    end\n\n    test \"validates password\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      {:error, changeset} =\n        <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, %{\n          password: \"not valid\",\n          password_confirmation: \"another\"\n        })\n\n      assert %{\n               password: [\"should be at least 12 character(s)\"],\n               password_confirmation: [\"does not match password\"]\n             } = errors_on(changeset)\n    end\n\n    test \"validates maximum values for password for security\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      too_long = String.duplicate(\"db\", 100)\n\n      {:error, changeset} =\n        <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, %{password: too_long})\n\n      assert \"should be at most 72 character(s)\" in errors_on(changeset).password\n    end\n\n    test \"updates the password\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      {:ok, {<%= schema.singular %>, expired_tokens}} =\n        <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, %{\n          password: \"new valid password\"\n        })\n\n      assert expired_tokens == []\n      assert is_nil(<%= schema.singular %>.password)\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>_by_email_and_password(<%= schema.singular %>.email, \"new valid password\")\n    end\n\n    test \"deletes all tokens for the given <%= schema.singular %>\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      _ = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n\n      {:ok, {_, _}} =\n        <%= inspect context.alias %>.update_<%= schema.singular %>_password(<%= schema.singular %>, %{\n          password: \"new valid password\"\n        })\n\n      refute Repo.get_by(<%= inspect schema.alias %>Token, <%= schema.singular %>_id: <%= schema.singular %>.id)\n    end\n  end\n\n  describe \"generate_<%= schema.singular %>_session_token/1\" do\n    setup do\n      %{<%= schema.singular %>: <%= schema.singular %>_fixture()}\n    end\n\n    test \"generates a token\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      assert <%= schema.singular %>_token = Repo.get_by(<%= inspect schema.alias %>Token, token: token)\n      assert <%= schema.singular %>_token.context == \"session\"\n      assert <%= schema.singular %>_token.authenticated_at != nil\n\n      # Creating the same token for another <%= schema.singular %> should fail\n      assert_raise Ecto.ConstraintError, fn ->\n        Repo.insert!(%<%= inspect schema.alias %>Token{\n          token: <%= schema.singular %>_token.token,\n          <%= schema.singular %>_id: <%= schema.singular %>_fixture().id,\n          context: \"session\"\n        })\n      end\n    end\n\n    test \"duplicates the authenticated_at of given <%= schema.singular %> in new token\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      <%= schema.singular %> = %{<%= schema.singular %> | authenticated_at: <%= inspect datetime_module %>.add(<%= datetime_now %>, -3600)}\n      token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      assert <%= schema.singular %>_token = Repo.get_by(<%= inspect schema.alias %>Token, token: token)\n      assert <%= schema.singular %>_token.authenticated_at == <%= schema.singular %>.authenticated_at\n      assert <%= inspect datetime_module %>.compare(<%= schema.singular %>_token.inserted_at, <%= schema.singular %>.authenticated_at) == :gt\n    end\n  end\n\n  describe \"get_<%= schema.singular %>_by_session_token/1\" do\n    setup do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      %{<%= schema.singular %>: <%= schema.singular %>, token: token}\n    end\n\n    test \"returns <%= schema.singular %> by token\", %{<%= schema.singular %>: <%= schema.singular %>, token: token} do\n      assert {session_<%= schema.singular %>, token_inserted_at} = <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token)\n      assert session_<%= schema.singular %>.id == <%= schema.singular %>.id\n      assert session_<%= schema.singular %>.authenticated_at != nil\n      assert token_inserted_at != nil\n    end\n\n    test \"does not return <%= schema.singular %> for invalid token\" do\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(\"oops\")\n    end\n\n    test \"does not return <%= schema.singular %> for expired token\", %{token: token} do\n      dt = ~N[2020-01-01 00:00:00]\n      {1, nil} = Repo.update_all(<%= inspect schema.alias %>Token, set: [inserted_at: dt, authenticated_at: dt])\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token)\n    end\n  end\n\n  describe \"get_<%= schema.singular %>_by_magic_link_token/1\" do\n    setup do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      {encoded_token, _hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n      %{<%= schema.singular %>: <%= schema.singular %>, token: encoded_token}\n    end\n\n    test \"returns <%= schema.singular %> by token\", %{<%= schema.singular %>: <%= schema.singular %>, token: token} do\n      assert session_<%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>_by_magic_link_token(token)\n      assert session_<%= schema.singular %>.id == <%= schema.singular %>.id\n    end\n\n    test \"does not return <%= schema.singular %> for invalid token\" do\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_magic_link_token(\"oops\")\n    end\n\n    test \"does not return <%= schema.singular %> for expired token\", %{token: token} do\n      {1, nil} = Repo.update_all(<%= inspect schema.alias %>Token, set: [inserted_at: ~N[2020-01-01 00:00:00]])\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_magic_link_token(token)\n    end\n  end\n\n  describe \"login_<%= schema.singular %>_by_magic_link/1\" do\n    test \"confirms <%= schema.singular %> and expires tokens\" do\n      <%= schema.singular %> = unconfirmed_<%= schema.singular %>_fixture()\n      refute <%= schema.singular %>.confirmed_at\n      {encoded_token, hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n\n      assert {:ok, {<%= schema.singular %>, [%{token: ^hashed_token}]}} =\n               <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(encoded_token)\n\n      assert <%= schema.singular %>.confirmed_at\n    end\n\n    test \"returns <%= schema.singular %> and (deleted) token for confirmed <%= schema.singular %>\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert <%= schema.singular %>.confirmed_at\n      {encoded_token, _hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n      assert {:ok, {^<%= schema.singular %>, []}} = <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(encoded_token)\n      # one time use only\n      assert {:error, :not_found} = <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(encoded_token)\n    end\n\n    test \"raises when unconfirmed <%= schema.singular %> has password set\" do\n      <%= schema.singular %> = unconfirmed_<%= schema.singular %>_fixture()\n      {1, nil} = Repo.update_all(<%= inspect schema.alias %>, set: [hashed_password: \"hashed\"])\n      {encoded_token, _hashed_token} = generate_<%= schema.singular %>_magic_link_token(<%= schema.singular %>)\n\n      assert_raise RuntimeError, ~r/magic link log in is not allowed/, fn ->\n        <%= inspect context.alias %>.login_<%= schema.singular %>_by_magic_link(encoded_token)\n      end\n    end\n  end\n\n  describe \"delete_<%= schema.singular %>_session_token/1\" do\n    test \"deletes the token\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      token = <%= inspect context.alias %>.generate_<%= schema.singular %>_session_token(<%= schema.singular %>)\n      assert <%= inspect context.alias %>.delete_<%= schema.singular %>_session_token(token) == :ok\n      refute <%= inspect context.alias %>.get_<%= schema.singular %>_by_session_token(token)\n    end\n  end\n\n  describe \"deliver_login_instructions/2\" do\n    setup do\n      %{<%= schema.singular %>: unconfirmed_<%= schema.singular %>_fixture()}\n    end\n\n    test \"sends token through notification\", %{<%= schema.singular %>: <%= schema.singular %>} do\n      token =\n        extract_<%= schema.singular %>_token(fn url ->\n          <%= inspect context.alias %>.deliver_login_instructions(<%= schema.singular %>, url)\n        end)\n\n      {:ok, token} = Base.url_decode64(token, padding: false)\n      assert <%= schema.singular %>_token = Repo.get_by(<%= inspect schema.alias %>Token, token: :crypto.hash(:sha256, token))\n      assert <%= schema.singular %>_token.<%= schema.singular %>_id == <%= schema.singular %>.id\n      assert <%= schema.singular %>_token.sent_to == <%= schema.singular %>.email\n      assert <%= schema.singular %>_token.context == \"login\"\n    end\n  end\n\n  describe \"inspect/2 for the <%= inspect schema.alias %> module\" do\n    test \"does not include password\" do\n      refute inspect(%<%= inspect schema.alias %>{password: \"123456\"}) =~ \"password: \\\"123456\\\"\"\n    end\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.channel/channel.ex.eex",
    "content": "defmodule <%= module %>Channel do\n  use <%= web_module %>, :channel\n\n  @impl true\n  def join(\"<%= singular %>:lobby\", payload, socket) do\n    if authorized?(payload) do\n      {:ok, socket}\n    else\n      {:error, %{reason: \"unauthorized\"}}\n    end\n  end\n\n  # Channels can be used in a request/response fashion\n  # by sending replies to requests from the client\n  @impl true\n  def handle_in(\"ping\", payload, socket) do\n    {:reply, {:ok, payload}, socket}\n  end\n\n  # It is also common to receive messages from the client and\n  # broadcast to everyone in the current topic (<%= singular %>:lobby).\n  @impl true\n  def handle_in(\"shout\", payload, socket) do\n    broadcast(socket, \"shout\", payload)\n    {:noreply, socket}\n  end\n\n  # Add authorization logic here as required.\n  defp authorized?(_payload) do\n    true\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.channel/channel_case.ex.eex",
    "content": "defmodule <%= web_module %>.ChannelCase do\n  @moduledoc \"\"\"\n  This module defines the test case to be used by\n  channel tests.\n\n  Such tests rely on `Phoenix.ChannelTest` and also\n  import other functionality to make it easier\n  to build common data structures and query the data layer.\n\n  Finally, if the test case interacts with the database,\n  we enable the SQL sandbox, so changes done to the database\n  are reverted at the end of every test. If you are using\n  PostgreSQL, you can even run database tests asynchronously\n  by setting `use <%= web_module %>.ChannelCase, async: true`, although\n  this option is not recommended for other databases.\n  \"\"\"\n\n  use ExUnit.CaseTemplate\n\n  using do\n    quote do\n      # Import conveniences for testing with channels\n      import Phoenix.ChannelTest\n      import <%= web_module %>.ChannelCase\n\n      # The default endpoint for testing\n      @endpoint <%= web_module %>.Endpoint\n    end\n  end<%= if Code.ensure_loaded?(Ecto.Adapters.SQL) do %>\n\n  setup tags do\n    <%= base %>.DataCase.setup_sandbox(tags)\n    :ok\n  end<% else %>\n\n  setup _tags do\n    :ok\n  end<% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.channel/channel_test.exs.eex",
    "content": "defmodule <%= module %>ChannelTest do\n  use <%= web_module %>.ChannelCase\n\n  setup do\n    {:ok, _, socket} =\n      <%= web_module %>.UserSocket\n      |> socket(\"user_id\", %{some: :assign})\n      |> subscribe_and_join(<%= module %>Channel, \"<%= singular %>:lobby\")\n\n    %{socket: socket}\n  end\n\n  test \"ping replies with status ok\", %{socket: socket} do\n    ref = push(socket, \"ping\", %{\"hello\" => \"there\"})\n    assert_reply ref, :ok, %{\"hello\" => \"there\"}\n  end\n\n  test \"shout broadcasts to <%= singular %>:lobby\", %{socket: socket} do\n    push(socket, \"shout\", %{\"hello\" => \"all\"})\n    assert_broadcast \"shout\", %{\"hello\" => \"all\"}\n  end\n\n  test \"broadcasts are pushed to the client\", %{socket: socket} do\n    broadcast_from!(socket, \"broadcast\", %{\"some\" => \"data\"})\n    assert_push \"broadcast\", %{\"some\" => \"data\"}\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.context/access_no_schema.ex.eex",
    "content": "\n  alias <%= inspect schema.module %>\n\n  @doc \"\"\"\n  Returns the list of <%= schema.plural %>.\n\n  ## Examples\n\n      iex> list_<%= schema.plural %>()\n      [%<%= inspect schema.alias %>{}, ...]\n\n  \"\"\"\n  def list_<%= schema.plural %> do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Gets a single <%= schema.singular %>.\n\n  Raises if the <%= schema.human_singular %> does not exist.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>!(123)\n      %<%= inspect schema.alias %>{}\n\n  \"\"\"\n  def get_<%= schema.singular %>!(<%= primary_key %>), do: raise \"TODO\"\n\n  @doc \"\"\"\n  Creates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> create_<%= schema.singular %>(%{field: value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> create_<%= schema.singular %>(%{field: bad_value})\n      {:error, ...}\n\n  \"\"\"\n  def create_<%= schema.singular %>(attrs) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Updates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: new_value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: bad_value})\n      {:error, ...}\n\n  \"\"\"\n  def update_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Deletes a <%= inspect schema.alias %>.\n\n  ## Examples\n\n      iex> delete_<%= schema.singular %>(<%= schema.singular %>)\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> delete_<%= schema.singular %>(<%= schema.singular %>)\n      {:error, ...}\n\n  \"\"\"\n  def delete_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Returns a data structure for tracking <%= schema.singular %> changes.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>(<%= schema.singular %>)\n      %Todo{...}\n\n  \"\"\"\n  def change_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, _attrs \\\\ %{}) do\n    raise \"TODO\"\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/access_no_schema_scope.ex.eex",
    "content": "\n  alias <%= inspect schema.module %>\n  alias <%= inspect scope.alias %>\n\n  @doc \"\"\"\n  Subscribes to scoped notifications about any <%= schema.singular %> changes.\n  \"\"\"\n  def subscribe_<%= schema.singular %>(%<%= inspect scope.alias %>{} = _scope) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Returns the list of <%= schema.plural %>.\n\n  ## Examples\n\n      iex> list_<%= schema.plural %>(scope)\n      [%<%= inspect schema.alias %>{}, ...]\n\n  \"\"\"\n  def list_<%= schema.plural %>(%<%= inspect scope.alias %>{} = _scope) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Gets a single <%= schema.singular %>.\n\n  Raises if the <%= schema.human_singular %> does not exist.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>!(scope, 123)\n      %<%= inspect schema.alias %>{}\n\n  \"\"\"\n  def get_<%= schema.singular %>!(%<%= inspect scope.alias %>{} = _scope, id), do: raise \"TODO\"\n\n  @doc \"\"\"\n  Creates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> create_<%= schema.singular %>(scope, %{field: value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> create_<%= schema.singular %>(scope, %{field: bad_value})\n      {:error, ...}\n\n  \"\"\"\n  def create_<%= schema.singular %>(%<%= inspect scope.alias %>{} = _scope, attrs) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Updates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> update_<%= schema.singular %>(scope, <%= schema.singular %>, %{field: new_value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> update_<%= schema.singular %>(scope, <%= schema.singular %>, %{field: bad_value})\n      {:error, ...}\n\n  \"\"\"\n  def update_<%= schema.singular %>(%<%= inspect scope.alias %>{} = _scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Deletes a <%= inspect schema.alias %>.\n\n  ## Examples\n\n      iex> delete_<%= schema.singular %>(scope, <%= schema.singular %>)\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> delete_<%= schema.singular %>(scope, <%= schema.singular %>)\n      {:error, ...}\n\n  \"\"\"\n  def delete_<%= schema.singular %>(%<%= inspect scope.alias %>{} = _scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    raise \"TODO\"\n  end\n\n  @doc \"\"\"\n  Returns a data structure for tracking <%= schema.singular %> changes.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>(scope, <%= schema.singular %>)\n      %Todo{...}\n\n  \"\"\"\n  def change_<%= schema.singular %>(%<%= inspect scope.alias %>{} = _scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>, _attrs \\\\ %{}) do\n    raise \"TODO\"\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/context.ex.eex",
    "content": "defmodule <%= inspect context.module %> do\n  @moduledoc \"\"\"\n  The <%= context.name %> context.\n  \"\"\"\n\n  import Ecto.Query, warn: false\n  alias <%= inspect schema.repo %><%= schema.repo_alias %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.context/context_test.exs.eex",
    "content": "defmodule <%= inspect context.module %>Test do\n  use <%= inspect context.base_module %>.DataCase\n\n  alias <%= inspect context.module %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.context/fixtures.ex.eex",
    "content": "<%= for {attr, {_function_name, function_def, _needs_impl?}} <- schema.fixture_unique_functions do %>  @doc \"\"\"\n  Generate a unique <%= schema.singular %> <%= attr %>.\n  \"\"\"\n<%= function_def %>\n<% end %>  @doc \"\"\"\n  Generate a <%= schema.singular %>.\n  \"\"\"\n  def <%= schema.singular %>_fixture(<%= if scope do %>scope, <% end %>attrs \\\\ %{}) do<%= if scope do %>\n    attrs =\n      Enum.into(attrs, %{\n<%= schema.fixture_params |> Enum.map(fn {key, code} -> \"        #{key}: #{code}\" end) |> Enum.join(\",\\n\") %>\n      })\n\n    {:ok, <%= schema.singular %>} = <%= inspect context.module %>.create_<%= schema.singular %>(scope, attrs)<% else %>\n    {:ok, <%= schema.singular %>} =\n      attrs\n      |> Enum.into(%{\n<%= schema.fixture_params |> Enum.map(fn {key, code} -> \"        #{key}: #{code}\" end) |> Enum.join(\",\\n\") %>\n      })\n      |> <%= inspect context.module %>.create_<%= schema.singular %>()\n<% end %>\n    <%= schema.singular %>\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/fixtures_module.ex.eex",
    "content": "defmodule <%= inspect context.module %>Fixtures do\n  @moduledoc \"\"\"\n  This module defines test helpers for creating\n  entities via the `<%= inspect context.module %>` context.\n  \"\"\"\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.context/schema_access.ex.eex",
    "content": "\n  alias <%= inspect schema.module %>\n\n  @doc \"\"\"\n  Returns the list of <%= schema.plural %>.\n\n  ## Examples\n\n      iex> list_<%= schema.plural %>()\n      [%<%= inspect schema.alias %>{}, ...]\n\n  \"\"\"\n  def list_<%= schema.plural %> do\n    Repo.all(<%= inspect schema.alias %>)\n  end\n\n  @doc \"\"\"\n  Gets a single <%= schema.singular %>.\n\n  Raises `Ecto.NoResultsError` if the <%= schema.human_singular %> does not exist.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>!(123)\n      %<%= inspect schema.alias %>{}\n\n      iex> get_<%= schema.singular %>!(456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_<%= schema.singular %>!(<%= primary_key %>), do: Repo.get!(<%= inspect schema.alias %>, <%= primary_key %>)\n\n  @doc \"\"\"\n  Creates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> create_<%= schema.singular %>(%{field: value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> create_<%= schema.singular %>(%{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_<%= schema.singular %>(attrs) do\n    %<%= inspect schema.alias %>{}\n    |> <%= inspect schema.alias %>.changeset(attrs)\n    |> Repo.insert()\n  end\n\n  @doc \"\"\"\n  Updates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: new_value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> update_<%= schema.singular %>(<%= schema.singular %>, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do\n    <%= schema.singular %>\n    |> <%= inspect schema.alias %>.changeset(attrs)\n    |> Repo.update()\n  end\n\n  @doc \"\"\"\n  Deletes a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> delete_<%= schema.singular %>(<%= schema.singular %>)\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> delete_<%= schema.singular %>(<%= schema.singular %>)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    Repo.delete(<%= schema.singular %>)\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking <%= schema.singular %> changes.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>(<%= schema.singular %>)\n      %Ecto.Changeset{data: %<%= inspect schema.alias %>{}}\n\n  \"\"\"\n  def change_<%= schema.singular %>(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs \\\\ %{}) do\n    <%= inspect schema.alias %>.changeset(<%= schema.singular %>, attrs)\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/schema_access_scope.ex.eex",
    "content": "\n  alias <%= inspect schema.module %>\n  alias <%= inspect scope.module %>\n\n  @doc \"\"\"\n  Subscribes to scoped notifications about any <%= schema.singular %> changes.\n\n  The broadcasted messages match the pattern:\n\n    * {:created, %<%= inspect schema.alias %>{}}\n    * {:updated, %<%= inspect schema.alias %>{}}\n    * {:deleted, %<%= inspect schema.alias %>{}}\n\n  \"\"\"\n  def subscribe_<%= schema.plural %>(%<%= inspect scope.alias %>{} = scope) do\n    key = scope.<%= Enum.join(scope.access_path, \".\") %>\n\n    Phoenix.PubSub.subscribe(<%= inspect context.base_module %>.PubSub, \"<%= scope.name %>:#{key}:<%= schema.plural %>\")\n  end\n\n  defp broadcast_<%= schema.singular %>(%<%= inspect scope.alias %>{} = scope, message) do\n    key = scope.<%= Enum.join(scope.access_path, \".\") %>\n\n    Phoenix.PubSub.broadcast(<%= inspect context.base_module %>.PubSub, \"<%= scope.name %>:#{key}:<%= schema.plural %>\", message)\n  end\n\n  @doc \"\"\"\n  Returns the list of <%= schema.plural %>.\n\n  ## Examples\n\n      iex> list_<%= schema.plural %>(scope)\n      [%<%= inspect schema.alias %>{}, ...]\n\n  \"\"\"\n  def list_<%= schema.plural %>(%<%= inspect scope.alias %>{} = scope) do\n    Repo.all_by(<%= inspect schema.alias %>, <%= scope.schema_key %>: scope.<%= Enum.join(scope.access_path, \".\") %>)\n  end\n\n  @doc \"\"\"\n  Gets a single <%= schema.singular %>.\n\n  Raises `Ecto.NoResultsError` if the <%= schema.human_singular %> does not exist.\n\n  ## Examples\n\n      iex> get_<%= schema.singular %>!(scope, 123)\n      %<%= inspect schema.alias %>{}\n\n      iex> get_<%= schema.singular %>!(scope, 456)\n      ** (Ecto.NoResultsError)\n\n  \"\"\"\n  def get_<%= schema.singular %>!(%<%= inspect scope.alias %>{} = scope, <%= primary_key %>) do\n    Repo.get_by!(<%= inspect schema.alias %>, <%= primary_key %>: <%= primary_key %>, <%= scope.schema_key %>: scope.<%= Enum.join(scope.access_path, \".\") %>)\n  end\n\n  @doc \"\"\"\n  Creates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> create_<%= schema.singular %>(scope, %{field: value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> create_<%= schema.singular %>(scope, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def create_<%= schema.singular %>(%<%= inspect scope.alias %>{} = scope, attrs) do\n    with {:ok, <%= schema.singular %> = %<%= inspect schema.alias %>{}} <-\n           %<%= inspect schema.alias %>{}\n           |> <%= inspect schema.alias %>.changeset(attrs, scope)\n           |> Repo.insert() do\n      broadcast_<%= schema.singular %>(scope, {:created, <%= schema.singular %>})\n      {:ok, <%= schema.singular %>}\n    end\n  end\n\n  @doc \"\"\"\n  Updates a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> update_<%= schema.singular %>(scope, <%= schema.singular %>, %{field: new_value})\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> update_<%= schema.singular %>(scope, <%= schema.singular %>, %{field: bad_value})\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def update_<%= schema.singular %>(%<%= inspect scope.alias %>{} = scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do\n    true = <%= schema.singular %>.<%= scope.schema_key %> == scope.<%= Enum.join(scope.access_path, \".\") %>\n\n    with {:ok, <%= schema.singular %> = %<%= inspect schema.alias %>{}} <-\n           <%= schema.singular %>\n           |> <%= inspect schema.alias %>.changeset(attrs, scope)\n           |> Repo.update() do\n      broadcast_<%= schema.singular %>(scope, {:updated, <%= schema.singular %>})\n      {:ok, <%= schema.singular %>}\n    end\n  end\n\n  @doc \"\"\"\n  Deletes a <%= schema.singular %>.\n\n  ## Examples\n\n      iex> delete_<%= schema.singular %>(scope, <%= schema.singular %>)\n      {:ok, %<%= inspect schema.alias %>{}}\n\n      iex> delete_<%= schema.singular %>(scope, <%= schema.singular %>)\n      {:error, %Ecto.Changeset{}}\n\n  \"\"\"\n  def delete_<%= schema.singular %>(%<%= inspect scope.alias %>{} = scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    true = <%= schema.singular %>.<%= scope.schema_key %> == scope.<%= Enum.join(scope.access_path, \".\") %>\n\n    with {:ok, <%= schema.singular %> = %<%= inspect schema.alias %>{}} <-\n           Repo.delete(<%= schema.singular %>) do\n      broadcast_<%= schema.singular %>(scope, {:deleted, <%= schema.singular %>})\n      {:ok, <%= schema.singular %>}\n    end\n  end\n\n  @doc \"\"\"\n  Returns an `%Ecto.Changeset{}` for tracking <%= schema.singular %> changes.\n\n  ## Examples\n\n      iex> change_<%= schema.singular %>(scope, <%= schema.singular %>)\n      %Ecto.Changeset{data: %<%= inspect schema.alias %>{}}\n\n  \"\"\"\n  def change_<%= schema.singular %>(%<%= inspect scope.alias %>{} = scope, %<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs \\\\ %{}) do\n    true = <%= schema.singular %>.<%= scope.schema_key %> == scope.<%= Enum.join(scope.access_path, \".\") %>\n\n    <%= inspect schema.alias %>.changeset(<%= schema.singular %>, attrs, scope)\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/test_cases.exs.eex",
    "content": "\n  describe \"<%= schema.plural %>\" do\n    alias <%= inspect schema.module %>\n\n    import <%= inspect context.module %>Fixtures\n\n    @invalid_attrs <%= Mix.Phoenix.to_text for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>\n\n    test \"list_<%= schema.plural %>/0 returns all <%= schema.plural %>\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert <%= inspect context.alias %>.list_<%= schema.plural %>() == [<%= schema.singular %>]\n    end\n\n    test \"get_<%= schema.singular %>!/1 returns the <%= schema.singular %> with given id\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.<%= primary_key %>) == <%= schema.singular %>\n    end\n\n    test \"create_<%= schema.singular %>/1 with valid data creates a <%= schema.singular %>\" do\n      valid_attrs = <%= Mix.Phoenix.to_text schema.params.create %>\n\n      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(valid_attrs)<%= for {field, value} <- schema.params.create do %>\n      assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>\n    end\n\n    test \"create_<%= schema.singular %>/1 with invalid data returns error changeset\" do\n      assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.create_<%= schema.singular %>(@invalid_attrs)\n    end\n\n    test \"update_<%= schema.singular %>/2 with valid data updates the <%= schema.singular %>\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      update_attrs = <%= Mix.Phoenix.to_text schema.params.update%>\n\n      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, update_attrs)<%= for {field, value} <- schema.params.update do %>\n      assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>\n    end\n\n    test \"update_<%= schema.singular %>/2 with invalid data returns error changeset\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.update_<%= schema.singular %>(<%= schema.singular %>, @invalid_attrs)\n      assert <%= schema.singular %> == <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.<%= primary_key %>)\n    end\n\n    test \"delete_<%= schema.singular %>/1 deletes the <%= schema.singular %>\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert {:ok, %<%= inspect schema.alias %>{}} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= schema.singular %>)\n      assert_raise Ecto.NoResultsError, fn -> <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= schema.singular %>.<%= primary_key %>) end\n    end\n\n    test \"change_<%= schema.singular %>/1 returns a <%= schema.singular %> changeset\" do\n      <%= schema.singular %> = <%= schema.singular %>_fixture()\n      assert %Ecto.Changeset{} = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= schema.singular %>)\n    end\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.context/test_cases_scope.exs.eex",
    "content": "\n  describe \"<%= schema.plural %>\" do\n    alias <%= inspect schema.module %>\n\n    import <%= inspect scope.test_data_fixture %>, only: [<%= scope.name %>_scope_fixture: 0]\n    import <%= inspect context.module %>Fixtures\n\n    @invalid_attrs <%= Mix.Phoenix.to_text for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %>\n\n    test \"list_<%= schema.plural %>/1 returns all scoped <%= schema.plural %>\" do\n      scope = <%= scope.name %>_scope_fixture()\n      other_scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      other_<%= schema.singular %> = <%= schema.singular %>_fixture(other_scope)\n      assert <%= inspect context.alias %>.list_<%= schema.plural %>(scope) == [<%= schema.singular %>]\n      assert <%= inspect context.alias %>.list_<%= schema.plural %>(other_scope) == [other_<%= schema.singular %>]\n    end\n\n    test \"get_<%= schema.singular %>!/2 returns the <%= schema.singular %> with given id\" do\n      scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      other_scope = <%= scope.name %>_scope_fixture()\n      assert <%= inspect context.alias %>.get_<%= schema.singular %>!(scope, <%= schema.singular %>.<%= schema.opts[:primary_key] || :id %>) == <%= schema.singular %>\n      assert_raise Ecto.NoResultsError, fn -> <%= inspect context.alias %>.get_<%= schema.singular %>!(other_scope, <%= schema.singular %>.<%= schema.opts[:primary_key] || :id %>) end\n    end\n\n    test \"create_<%= schema.singular %>/2 with valid data creates a <%= schema.singular %>\" do\n      valid_attrs = <%= Mix.Phoenix.to_text schema.params.create %>\n      scope = <%= scope.name %>_scope_fixture()\n\n      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.create_<%= schema.singular %>(scope, valid_attrs)<%= for {field, value} <- schema.params.create do %>\n      assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>\n      assert <%= schema.singular %>.<%= scope.schema_key %> == scope.<%= scope.access_path |> Enum.join(\".\") %>\n    end\n\n    test \"create_<%= schema.singular %>/2 with invalid data returns error changeset\" do\n      scope = <%= scope.name %>_scope_fixture()\n      assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.create_<%= schema.singular %>(scope, @invalid_attrs)\n    end\n\n    test \"update_<%= schema.singular %>/3 with valid data updates the <%= schema.singular %>\" do\n      scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      update_attrs = <%= Mix.Phoenix.to_text schema.params.update%>\n\n      assert {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} = <%= inspect context.alias %>.update_<%= schema.singular %>(scope, <%= schema.singular %>, update_attrs)<%= for {field, value} <- schema.params.update do %>\n      assert <%= schema.singular %>.<%= field %> == <%= Mix.Phoenix.Schema.value(schema, field, value) %><% end %>\n    end\n\n    test \"update_<%= schema.singular %>/3 with invalid scope raises\" do\n      scope = <%= scope.name %>_scope_fixture()\n      other_scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n\n      assert_raise MatchError, fn ->\n        <%= inspect context.alias %>.update_<%= schema.singular %>(other_scope, <%= schema.singular %>, %{})\n      end\n    end\n\n    test \"update_<%= schema.singular %>/3 with invalid data returns error changeset\" do\n      scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      assert {:error, %Ecto.Changeset{}} = <%= inspect context.alias %>.update_<%= schema.singular %>(scope, <%= schema.singular %>, @invalid_attrs)\n      assert <%= schema.singular %> == <%= inspect context.alias %>.get_<%= schema.singular %>!(scope, <%= schema.singular %>.<%= schema.opts[:primary_key] || :id %>)\n    end\n\n    test \"delete_<%= schema.singular %>/2 deletes the <%= schema.singular %>\" do\n      scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      assert {:ok, %<%= inspect schema.alias %>{}} = <%= inspect context.alias %>.delete_<%= schema.singular %>(scope, <%= schema.singular %>)\n      assert_raise Ecto.NoResultsError, fn -> <%= inspect context.alias %>.get_<%= schema.singular %>!(scope, <%= schema.singular %>.<%= schema.opts[:primary_key] || :id %>) end\n    end\n\n    test \"delete_<%= schema.singular %>/2 with invalid scope raises\" do\n      scope = <%= scope.name %>_scope_fixture()\n      other_scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      assert_raise MatchError, fn -> <%= inspect context.alias %>.delete_<%= schema.singular %>(other_scope, <%= schema.singular %>) end\n    end\n\n    test \"change_<%= schema.singular %>/2 returns a <%= schema.singular %> changeset\" do\n      scope = <%= scope.name %>_scope_fixture()\n      <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n      assert %Ecto.Changeset{} = <%= inspect context.alias %>.change_<%= schema.singular %>(scope, <%= schema.singular %>)\n    end\n  end\n"
  },
  {
    "path": "priv/templates/phx.gen.embedded/embedded_schema.ex.eex",
    "content": "defmodule <%= inspect schema.module %> do\n  use Ecto.Schema\n  import Ecto.Changeset\n  alias <%= inspect schema.module %>\n\n  embedded_schema do <%= if !Enum.empty?(schema.types) do %>\n<%= Mix.Phoenix.Schema.format_fields_for_schema(schema) %><% end %>\n<%= for {_, k, _, _} <- schema.assocs do %>    field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %>\n<% end %>  end\n\n  @doc false\n  def changeset(%<%= inspect schema.alias %>{} = <%= schema.singular %>, attrs) do\n    <%= schema.singular %>\n    |> cast(attrs, [<%= Enum.map_join(schema.attrs, \", \", &inspect(elem(&1, 0))) %>])\n    |> validate_required([<%= Enum.map_join(schema.attrs, \", \", &inspect(elem(&1, 0))) %>])\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.html/controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Controller do\n  use <%= inspect context.web_module %>, :controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect schema.module %>\n\n  def index(conn, _params) do\n    <%= schema.plural %> = <%= inspect context.alias %>.list_<%= schema.plural %>(<%= conn_scope %>)\n    render(conn, :index, <%= schema.collection %>: <%= schema.plural %>)\n  end\n\n  def new(conn, _params) do<%= if scope do %>\n    changeset =\n      <%= inspect context.alias %>.change_<%= schema.singular %>(<%= context_scope_prefix %>%<%= inspect schema.alias %>{\n        <%= scope.schema_key %>: <%= conn_scope %>.<%= Enum.join(scope.access_path, \".\") %>\n      })\n<% else %>\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(%<%= inspect schema.alias %>{})<% end %>\n    render(conn, :new, changeset: changeset)\n  end\n\n  def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params}) do\n    case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        conn\n        |> put_flash(:info, \"<%= schema.human_singular %> created successfully.\")\n        |> redirect(to: ~p\"<%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, :new, changeset: changeset)\n    end\n  end\n\n  def show(conn, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n    render(conn, :show, <%= schema.singular %>: <%= schema.singular %>)\n  end\n\n  def edit(conn, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>)\n    render(conn, :edit, <%= schema.singular %>: <%= schema.singular %>, changeset: changeset)\n  end\n\n  def update(conn, %{\"<%= primary_key %>\" => <%= primary_key %>, <%= inspect schema.singular %> => <%= schema.singular %>_params}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n\n    case <%= inspect context.alias %>.update_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>, <%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        conn\n        |> put_flash(:info, \"<%= schema.human_singular %> updated successfully.\")\n        |> redirect(to: ~p\"<%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        render(conn, :edit, <%= schema.singular %>: <%= schema.singular %>, changeset: changeset)\n    end\n  end\n\n  def delete(conn, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n    {:ok, _<%= schema.singular %>} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>)\n\n    conn\n    |> put_flash(:info, \"<%= schema.human_singular %> deleted successfully.\")\n    |> redirect(to: ~p\"<%= scope_conn_route_prefix %><%= schema.route_prefix %>\")\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.html/controller_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>ControllerTest do\n  use <%= inspect context.web_module %>.ConnCase\n\n  import <%= inspect context.module %>Fixtures\n\n  @create_attrs <%= Mix.Phoenix.to_text schema.params.create %>\n  @update_attrs <%= Mix.Phoenix.to_text schema.params.update %>\n  @invalid_attrs <%= Mix.Phoenix.to_text (for {key, _} <- schema.params.create, into: %{}, do: {key, nil}) %><%= if scope do %>\n\n  setup :<%= scope.test_setup_helper %><% end %>\n\n  describe \"index\" do\n    test \"lists all <%= schema.plural %>\", %{conn: conn<%= test_context_scope %>} do\n      conn = get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n      assert html_response(conn, 200) =~ \"Listing <%= schema.human_plural %>\"\n    end\n  end\n\n  describe \"new <%= schema.singular %>\" do\n    test \"renders form\", %{conn: conn<%= test_context_scope %>} do\n      conn = get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/new\")\n      assert html_response(conn, 200) =~ \"New <%= schema.human_singular %>\"\n    end\n  end\n\n  describe \"create <%= schema.singular %>\" do\n    test \"redirects to show when data is valid\", %{conn: conn<%= test_context_scope %>} do\n      conn = post(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\", <%= schema.singular %>: @create_attrs)\n\n      assert %{<%= primary_key %>: <%= primary_key %>} = redirected_params(conn)\n      assert redirected_to(conn) == ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}\"\n\n      conn = get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}\")\n      assert html_response(conn, 200) =~ \"<%= schema.human_singular %> #{<%= primary_key %>}\"\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn<%= test_context_scope %>} do\n      conn = post(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\", <%= schema.singular %>: @invalid_attrs)\n      assert html_response(conn, 200) =~ \"New <%= schema.human_singular %>\"\n    end\n  end\n\n  describe \"edit <%= schema.singular %>\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"renders form for editing chosen <%= schema.singular %>\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit\")\n      assert html_response(conn, 200) =~ \"Edit <%= schema.human_singular %>\"\n    end\n  end\n\n  describe \"update <%= schema.singular %>\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"redirects when data is valid\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = put(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\", <%= schema.singular %>: @update_attrs)\n      assert redirected_to(conn) == ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\"\n\n      conn = get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")<%= if schema.string_attr do %>\n      assert html_response(conn, 200) =~ <%= inspect Mix.Phoenix.Schema.default_param(schema, :update) %><% else %>\n      assert html_response(conn, 200)<% end %>\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = put(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\", <%= schema.singular %>: @invalid_attrs)\n      assert html_response(conn, 200) =~ \"Edit <%= schema.human_singular %>\"\n    end\n  end\n\n  describe \"delete <%= schema.singular %>\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"deletes chosen <%= schema.singular %>\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = delete(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n      assert redirected_to(conn) == ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\"\n\n      assert_error_sent 404, fn ->\n        get(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n      end\n    end\n  end\n\n<%= if scope do %>  defp create_<%= schema.singular %>(%{scope: scope}) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n<% else %>  defp create_<%= schema.singular %>(_) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture()\n<% end %>\n    %{<%= schema.singular %>: <%= schema.singular %>}\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.html/edit.html.heex.eex",
    "content": "<Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n  <.header>\n    Edit <%= schema.human_singular %> {@<%= schema.singular %>.<%= primary_key %>}\n    <:subtitle>Use this form to manage <%= schema.singular %> records in your database.</:subtitle>\n  </.header>\n\n  <.<%= schema.singular %>_form changeset={@changeset} action={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}\"} return_to={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>\"} />\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.html/html.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>HTML do\n  use <%= inspect context.web_module %>, :html\n\n  embed_templates \"<%= schema.singular %>_html/*\"\n\n  @doc \"\"\"\n  Renders a <%= schema.singular %> form.\n\n  The form is defined in the template at\n  <%= schema.singular %>_html/<%= schema.singular %>_form.html.heex\n  \"\"\"\n  attr :changeset, Ecto.Changeset, required: true\n  attr :action, :string, required: true\n  attr :return_to, :string, default: nil\n\n  def <%= schema.singular %>_form(assigns)\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.html/index.html.heex.eex",
    "content": "<Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n  <.header>\n    Listing <%= schema.human_plural %>\n    <:actions>\n      <.button href={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/new\"}>\n        <.icon name=\"hero-plus\" /> New <%= schema.human_singular %>\n      </.button>\n    </:actions>\n  </.header>\n\n  <.table id=\"<%= schema.plural %>\" rows={@<%= schema.collection %>} row_click={&JS.navigate(~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{&1}\")}><%= for {k, _} <- schema.attrs do %>\n    <:col :let={<%= schema.singular %>} label=\"<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>\">{<%= schema.singular %>.<%= k %>}</:col><% end %>\n    <:action :let={<%= schema.singular %>}>\n      <div class=\"sr-only\">\n        <.link navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\"}>Show</.link>\n      </div>\n      <.link navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit\"}>Edit</.link>\n    </:action>\n    <:action :let={<%= schema.singular %>}>\n      <.link href={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\"} method=\"delete\" data-confirm=\"Are you sure?\">\n        Delete\n      </.link>\n    </:action>\n  </.table>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.html/new.html.heex.eex",
    "content": "<Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n  <.header>\n    New <%= schema.human_singular %>\n    <:subtitle>Use this form to manage <%= schema.singular %> records in your database.</:subtitle>\n  </.header>\n\n  <.<%= schema.singular %>_form changeset={@changeset} action={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>\"} return_to={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>\"} />\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.html/resource_form.html.heex.eex",
    "content": "<.form :let={f} for={@changeset} action={@action}>\n<%= Mix.Tasks.Phx.Gen.Html.indent_inputs(inputs, 2) %>\n  <footer>\n    <.button variant=\"primary\">Save <%= schema.human_singular %></.button>\n    <.button :if={@return_to} href={@return_to}>Cancel</.button>\n  </footer>\n</.form>\n"
  },
  {
    "path": "priv/templates/phx.gen.html/show.html.heex.eex",
    "content": "<Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n  <.header>\n    <%= schema.human_singular %> {@<%= schema.singular %>.<%= primary_key %>}\n    <:subtitle>This is a <%= schema.singular %> record from your database.</:subtitle>\n    <:actions>\n      <.button navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>\"}>\n        <.icon name=\"hero-arrow-left\" />\n      </.button>\n      <.button variant=\"primary\" navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit?return_to=show\"}>\n        <.icon name=\"hero-pencil-square\" /> Edit <%= schema.singular %>\n      </.button>\n    </:actions>\n  </.header>\n\n  <.list><%= for {k, _} <- schema.attrs do %>\n    <:item title=\"<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>\">{@<%= schema.singular %>.<%= k %>}</:item><% end %>\n  </.list>\n</Layouts.app>\n"
  },
  {
    "path": "priv/templates/phx.gen.json/changeset_json.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.ChangesetJSON do\n  @doc \"\"\"\n  Renders changeset errors.\n  \"\"\"<%= if core_components? do %>\n  def error(%{changeset: changeset}) do\n    # When encoded, the changeset returns its errors\n    # as a JSON object. So we just pass it forward.\n    %{errors: Ecto.Changeset.traverse_errors(changeset, &<%= inspect context.web_module %>.CoreComponents.translate_error/1)}\n  end<% else %>\n  def error(%{changeset: changeset}) do\n    # When encoded, the changeset returns its errors\n    # as a JSON object. So we just pass it forward.\n    %{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}\n  end\n<%= if gettext? do %>\n  defp translate_error({msg, opts}) do\n    # set by Ecto and indicates we should also apply plural rules.\n    if count = opts[:count] do\n      Gettext.dngettext(<%= inspect context.web_module %>.Gettext, \"errors\", msg, msg, count, opts)\n    else\n      Gettext.dgettext(<%= inspect context.web_module %>.Gettext, \"errors\", msg, opts)\n    end\n  end\n<% else %>\n  defp translate_error({msg, opts}) do\n    # You can make use of gettext to translate error messages by\n    # uncommenting and adjusting the following code:\n\n    # if count = opts[:count] do\n    #   Gettext.dngettext(<%= inspect context.web_module %>.Gettext, \"errors\", msg, msg, count, opts)\n    # else\n    #   Gettext.dgettext(<%= inspect context.web_module %>.Gettext, \"errors\", msg, opts)\n    # end\n\n    Enum.reduce(opts, msg, fn {key, value}, acc ->\n      String.replace(acc, \"%{#{key}}\", fn _ -> to_string(value) end)\n    end)\n  end<% end %><% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.json/controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Controller do\n  use <%= inspect context.web_module %>, :controller\n\n  alias <%= inspect context.module %>\n  alias <%= inspect schema.module %>\n\n  action_fallback <%= inspect context.web_module %>.FallbackController\n\n  def index(conn, _params) do\n    <%= schema.plural %> = <%= inspect context.alias %>.list_<%= schema.plural %>(<%= conn_scope %>)\n    render(conn, :index, <%= schema.plural %>: <%= schema.plural %>)\n  end\n\n  def create(conn, %{<%= inspect schema.singular %> => <%= schema.singular %>_params}) do\n    with {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} <- <%= inspect context.alias %>.create_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>_params) do\n      conn\n      |> put_status(:created)\n      |> put_resp_header(\"location\", ~p\"<%= schema.api_route_prefix %><%= scope_conn_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n      |> render(:show, <%= schema.singular %>: <%= schema.singular %>)\n    end\n  end\n\n  def show(conn, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n    render(conn, :show, <%= schema.singular %>: <%= schema.singular %>)\n  end\n\n  def update(conn, %{\"<%= primary_key %>\" => <%= primary_key %>, <%= inspect schema.singular %> => <%= schema.singular %>_params}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n\n    with {:ok, %<%= inspect schema.alias %>{} = <%= schema.singular %>} <- <%= inspect context.alias %>.update_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>, <%= schema.singular %>_params) do\n      render(conn, :show, <%= schema.singular %>: <%= schema.singular %>)\n    end\n  end\n\n  def delete(conn, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n\n    with {:ok, %<%= inspect schema.alias %>{}} <- <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>) do\n      send_resp(conn, :no_content, \"\")\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.json/controller_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>ControllerTest do\n  use <%= inspect context.web_module %>.ConnCase\n\n  import <%= inspect context.module %>Fixtures\n  alias <%= inspect schema.module %>\n\n  @create_attrs %{\n<%= schema.params.create |> Enum.map(fn {key, val} -> \"    #{key}: #{inspect(val)}\" end) |> Enum.join(\",\\n\") %>\n  }\n  @update_attrs %{\n<%= schema.params.update |> Enum.map(fn {key, val} -> \"    #{key}: #{inspect(val)}\" end) |> Enum.join(\",\\n\") %>\n  }\n  @invalid_attrs <%= Mix.Phoenix.to_text for {key, _} <- schema.params.create, into: %{}, do: {key, nil} %><%= if scope do %>\n\n  setup :<%= scope.test_setup_helper %><% end %>\n\n  setup %{conn: conn} do\n    {:ok, conn: put_req_header(conn, \"accept\", \"application/json\")}\n  end\n\n  describe \"index\" do\n    test \"lists all <%= schema.plural %>\", %{conn: conn<%= test_context_scope %>} do\n      conn = get(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n      assert json_response(conn, 200)[\"data\"] == []\n    end\n  end\n\n  describe \"create <%= schema.singular %>\" do\n    test \"renders <%= schema.singular %> when data is valid\", %{conn: conn<%= test_context_scope %>} do\n      conn = post(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>\", <%= schema.singular %>: @create_attrs)\n      assert %{\"<%= primary_key %>\" => <%= primary_key %>} = json_response(conn, 201)[\"data\"]\n\n      conn = get(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}\")\n\n      assert %{\n               \"<%= primary_key %>\" => ^<%= primary_key %><%= for {key, val} <- schema.params.create |> Phoenix.json_library().encode!() |> Phoenix.json_library().decode!() do %>,\n               \"<%= key %>\" => <%= inspect(val) %><% end %>\n             } = json_response(conn, 200)[\"data\"]\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn<%= test_context_scope %>} do\n      conn = post(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>\", <%= schema.singular %>: @invalid_attrs)\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n  end\n\n  describe \"update <%= schema.singular %>\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"renders <%= schema.singular %> when data is valid\", %{conn: conn, <%= schema.singular %>: %<%= inspect schema.alias %>{<%= primary_key %>: <%= primary_key %>} = <%= schema.singular %><%= test_context_scope %>} do\n      conn = put(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\", <%= schema.singular %>: @update_attrs)\n      assert %{\"<%= primary_key %>\" => ^<%= primary_key %>} = json_response(conn, 200)[\"data\"]\n\n      conn = get(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= primary_key %>}\")\n\n      assert %{\n               \"<%= primary_key %>\" => ^<%= primary_key %><%= for {key, val} <- schema.params.update |> Phoenix.json_library().encode!() |> Phoenix.json_library().decode!() do %>,\n               \"<%= key %>\" => <%= inspect(val) %><% end %>\n             } = json_response(conn, 200)[\"data\"]\n    end\n\n    test \"renders errors when data is invalid\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = put(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\", <%= schema.singular %>: @invalid_attrs)\n      assert json_response(conn, 422)[\"errors\"] != %{}\n    end\n  end\n\n  describe \"delete <%= schema.singular %>\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"deletes chosen <%= schema.singular %>\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      conn = delete(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n      assert response(conn, 204)\n\n      assert_error_sent 404, fn ->\n        get(conn, ~p\"<%= schema.api_route_prefix %><%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n      end\n    end\n  end\n\n<%= if scope do %>  defp create_<%= schema.singular %>(%{scope: scope}) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n<% else %>  defp create_<%= schema.singular %>(_) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture()\n<% end %>\n    %{<%= schema.singular %>: <%= schema.singular %>}\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.json/fallback_controller.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.FallbackController do\n  @moduledoc \"\"\"\n  Translates controller action results into valid `Plug.Conn` responses.\n\n  See `Phoenix.Controller.action_fallback/1` for more details.\n  \"\"\"\n  use <%= inspect context.web_module %>, :controller\n\n  <%= if schema.generate? do %># This clause handles errors returned by Ecto's insert/update/delete.\n  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do\n    conn\n    |> put_status(:unprocessable_entity)\n    |> put_view(json: <%= inspect context.web_module %>.ChangesetJSON)\n    |> render(:error, changeset: changeset)\n  end\n\n  <% end %># This clause is an example of how to handle resources that cannot be found.\n  def call(conn, {:error, :not_found}) do\n    conn\n    |> put_status(:not_found)\n    |> put_view(html: <%= inspect context.web_module %>.ErrorHTML, json: <%= inspect context.web_module %>.ErrorJSON)\n    |> render(:\"404\")\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.json/json.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>JSON do\n  alias <%= inspect schema.module %>\n\n  @doc \"\"\"\n  Renders a list of <%= schema.plural %>.\n  \"\"\"\n  def index(%{<%= schema.plural %>: <%= schema.plural %>}) do\n    %{data: for(<%= schema.singular %> <- <%= schema.plural %>, do: data(<%= schema.singular %>))}\n  end\n\n  @doc \"\"\"\n  Renders a single <%= schema.singular %>.\n  \"\"\"\n  def show(%{<%= schema.singular %>: <%= schema.singular %>}) do\n    %{data: data(<%= schema.singular %>)}\n  end\n\n  defp data(%<%= inspect schema.alias %>{} = <%= schema.singular %>) do\n    %{\n<%= [{primary_key, :id} | schema.attrs] |> Enum.map(fn {k, _} -> \"      #{k}: #{schema.singular}.#{k}\" end) |> Enum.join(\",\\n\")  %>\n    }\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.live/form.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Form do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n  alias <%= inspect schema.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n      <.header>\n        {@page_title}\n        <:subtitle>Use this form to manage <%= schema.singular %> records in your database.</:subtitle>\n      </.header>\n\n      <.form for={@form} id=\"<%= schema.singular %>-form\" phx-change=\"validate\" phx-submit=\"save\">\n<%= Mix.Tasks.Phx.Gen.Html.indent_inputs(inputs, 8) %>\n        <footer>\n          <.button phx-disable-with=\"Saving...\" variant=\"primary\">Save <%= schema.human_singular %></.button>\n          <.button navigate={return_path(<%= assign_scope_prefix %>@return_to, @<%= schema.singular %>)}>Cancel</.button>\n        </footer>\n      </.form>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(params, _session, socket) do\n    {:ok,\n     socket\n     |> assign(:return_to, return_to(params[\"return_to\"]))\n     |> apply_action(socket.assigns.live_action, params)}\n  end\n\n  defp return_to(\"show\"), do: \"show\"\n  defp return_to(_), do: \"index\"\n\n  defp apply_action(socket, :edit, %{\"<%= primary_key %>\" => <%= primary_key %>}) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n\n    socket\n    |> assign(:page_title, \"Edit <%= schema.human_singular %>\")\n    |> assign(:<%= schema.singular %>, <%= schema.singular %>)\n    |> assign(:form, to_form(<%= inspect context.alias %>.change_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>)))\n  end\n\n  defp apply_action(socket, :new, _params) do\n    <%= schema.singular %> = %<%= inspect schema.alias %>{<%= if scope do %><%= scope.schema_key %>: <%= socket_scope %>.<%= Enum.join(scope.access_path, \".\") %><% end %>}\n\n    socket\n    |> assign(:page_title, \"New <%= schema.human_singular %>\")\n    |> assign(:<%= schema.singular %>, <%= schema.singular %>)\n    |> assign(:form, to_form(<%= inspect context.alias %>.change_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>)))\n  end\n\n  @impl true\n  def handle_event(\"validate\", %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}, socket) do\n    changeset = <%= inspect context.alias %>.change_<%= schema.singular %>(<%= context_scope_prefix %>socket.assigns.<%= schema.singular %>, <%= schema.singular %>_params)\n    {:noreply, assign(socket, form: to_form(changeset, action: :validate))}\n  end\n\n  def handle_event(\"save\", %{\"<%= schema.singular %>\" => <%= schema.singular %>_params}, socket) do\n    save_<%= schema.singular %>(socket, socket.assigns.live_action, <%= schema.singular %>_params)\n  end\n\n  defp save_<%= schema.singular %>(socket, :edit, <%= schema.singular %>_params) do\n    case <%= inspect context.alias %>.update_<%= schema.singular %>(<%= context_scope_prefix %>socket.assigns.<%= schema.singular %>, <%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        {:noreply,\n         socket\n         |> put_flash(:info, \"<%= schema.human_singular %> updated successfully\")\n         <%= if scope do %>|> push_navigate(\n           to: return_path(<%= context_scope_prefix %>socket.assigns.return_to, <%= schema.singular %>)\n         )}<% else %>|> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}<% end %>\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        {:noreply, assign(socket, form: to_form(changeset))}\n    end\n  end\n\n  defp save_<%= schema.singular %>(socket, :new, <%= schema.singular %>_params) do\n    case <%= inspect context.alias %>.create_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>_params) do\n      {:ok, <%= schema.singular %>} ->\n        {:noreply,\n         socket\n         |> put_flash(:info, \"<%= schema.human_singular %> created successfully\")\n         <%= if scope do %>|> push_navigate(\n           to: return_path(<%= context_scope_prefix %>socket.assigns.return_to, <%= schema.singular %>)\n         )}<% else %>|> push_navigate(to: return_path(socket.assigns.return_to, <%= schema.singular %>))}<% end %>\n\n      {:error, %Ecto.Changeset{} = changeset} ->\n        {:noreply, assign(socket, form: to_form(changeset))}\n    end\n  end\n\n  defp return_path(<%= scope_param_prefix %>\"index\", _<%= schema.singular %>), do: ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\"\n  defp return_path(<%= scope_param_prefix %>\"show\", <%= schema.singular %>), do: ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\"\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.live/index.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Index do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n      <.header>\n        Listing <%= schema.human_plural %>\n        <:actions>\n          <.button variant=\"primary\" navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/new\"}>\n            <.icon name=\"hero-plus\" /> New <%= schema.human_singular %>\n          </.button>\n        </:actions>\n      </.header>\n\n      <.table\n        id=\"<%= schema.plural %>\"\n        rows={@streams.<%= schema.collection %>}\n        row_click={fn {_id, <%= schema.singular %>} -> JS.navigate(~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\") end}\n      ><%= for {k, _} <- schema.attrs do %>\n        <:col :let={{_id, <%= schema.singular %>}} label=\"<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>\">{<%= schema.singular %>.<%= k %>}</:col><% end %>\n        <:action :let={{_id, <%= schema.singular %>}}>\n          <div class=\"sr-only\">\n            <.link navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\"}>Show</.link>\n          </div>\n          <.link navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit\"}>Edit</.link>\n        </:action>\n        <:action :let={{id, <%= schema.singular %>}}>\n          <.link\n            phx-click={JS.push(\"delete\", value: %{<%= primary_key %>: <%= schema.singular %>.<%= primary_key %>}) |> hide(\"##{id}\")}\n            data-confirm=\"Are you sure?\"\n          >\n            Delete\n          </.link>\n        </:action>\n      </.table>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(_params, _session, socket) do<%= if scope do %>\n    if connected?(socket) do\n      <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)\n    end\n<% end %>\n    {:ok,\n     socket\n     |> assign(:page_title, \"Listing <%= schema.human_plural %>\")<%= if primary_key != :id do %>\n     |> stream_configure(:<%= schema.collection %>, dom_id: &\"<%= schema.collection %>-#{&1.<%= primary_key %>}\")<% end %>\n     |> stream(:<%= schema.collection %>, list_<%= schema.plural %>(<%= socket_scope %>))}\n  end\n\n  @impl true\n  def handle_event(\"delete\", %{\"<%= primary_key %>\" => <%= primary_key %>}, socket) do\n    <%= schema.singular %> = <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>)\n    {:ok, _} = <%= inspect context.alias %>.delete_<%= schema.singular %>(<%= context_scope_prefix %><%= schema.singular %>)\n\n    {:noreply, stream_delete(socket, :<%= schema.collection %>, <%= schema.singular %>)}\n  end<%= if scope do %>\n\n  @impl true\n  def handle_info({type, %<%= inspect schema.module %>{}}, socket)\n      when type in [:created, :updated, :deleted] do\n    {:noreply, stream(socket, :<%= schema.collection %>, list_<%= schema.plural %>(<%= socket_scope %>), reset: true)}\n  end<% end %>\n\n  defp list_<%= schema.plural %>(<%= scope && scope.assign_key %>) do\n    <%= inspect context.alias %>.list_<%= schema.plural %>(<%= scope && scope.assign_key %>)\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.live/live_test.exs.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>LiveTest do\n  use <%= inspect context.web_module %>.ConnCase\n\n  import Phoenix.LiveViewTest\n  import <%= inspect context.module %>Fixtures\n\n  @create_attrs <%= Mix.Phoenix.to_text for {key, value} <- schema.params.create, into: %{}, do: {key, Mix.Phoenix.Schema.live_form_value(value)} %>\n  @update_attrs <%= Mix.Phoenix.to_text for {key, value} <- schema.params.update, into: %{}, do: {key, Mix.Phoenix.Schema.live_form_value(value)} %>\n  @invalid_attrs <%= Mix.Phoenix.to_text for {key, value} <- schema.params.create, into: %{}, do: {key, value |> Mix.Phoenix.Schema.live_form_value() |> Mix.Phoenix.Schema.invalid_form_value()} %><%= if scope do %>\n\n  setup :<%= scope.test_setup_helper %>\n\n  defp create_<%= schema.singular %>(%{scope: scope}) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture(scope)\n<% else %>\n  defp create_<%= schema.singular %>(_) do\n    <%= schema.singular %> = <%= schema.singular %>_fixture()\n<% end %>\n    %{<%= schema.singular %>: <%= schema.singular %>}\n  end\n\n  describe \"Index\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"lists all <%= schema.plural %>\", <%= if schema.string_attr do %>%{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>}<% else %>%{conn: conn<%= test_context_scope %>}<% end %> do\n      {:ok, _index_live, html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      assert html =~ \"Listing <%= schema.human_plural %>\"<%= if schema.string_attr do %>\n      assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>\n    end\n\n    test \"saves new <%= schema.singular %>\", %{conn: conn<%= test_context_scope %>} do\n      {:ok, index_live, _html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      assert {:ok, form_live, _} =\n               index_live\n               |> element(\"a\", \"New <%= schema.human_singular %>\")\n               |> render_click()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/new\")\n\n      assert render(form_live) =~ \"New <%= schema.human_singular %>\"\n\n      assert form_live\n             |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @invalid_attrs)\n             |> render_change() =~ \"<%= Mix.Phoenix.Schema.failed_render_change_message(schema) %>\"\n\n      assert {:ok, index_live, _html} =\n               form_live\n               |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @create_attrs)\n               |> render_submit()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      html = render(index_live)\n      assert html =~ \"<%= schema.human_singular %> created successfully\"<%= if schema.string_attr do %>\n      assert html =~ \"some <%= schema.string_attr %>\"<% end %>\n    end\n\n    test \"updates <%= schema.singular %> in listing\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      {:ok, index_live, _html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      assert {:ok, form_live, _html} =\n               index_live\n               |> element(\"#<%= schema.collection %>-#{<%= schema.singular %>.<%= primary_key %>} a\", \"Edit\")\n               |> render_click()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit\")\n\n      assert render(form_live) =~ \"Edit <%= schema.human_singular %>\"\n\n      assert form_live\n             |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @invalid_attrs)\n             |> render_change() =~ \"<%= Mix.Phoenix.Schema.failed_render_change_message(schema) %>\"\n\n      assert {:ok, index_live, _html} =\n               form_live\n               |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @update_attrs)\n               |> render_submit()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      html = render(index_live)\n      assert html =~ \"<%= schema.human_singular %> updated successfully\"<%= if schema.string_attr do %>\n      assert html =~ \"some updated <%= schema.string_attr %>\"<% end %>\n    end\n\n    test \"deletes <%= schema.singular %> in listing\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      {:ok, index_live, _html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>\")\n\n      assert index_live |> element(\"#<%= schema.collection %>-#{<%= schema.singular %>.<%= primary_key %>} a\", \"Delete\") |> render_click()\n      refute has_element?(index_live, \"#<%= schema.plural %>-#{<%= schema.singular %>.<%= primary_key %>}\")\n    end\n  end\n\n  describe \"Show\" do\n    setup [:create_<%= schema.singular %>]\n\n    test \"displays <%= schema.singular %>\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      {:ok, _show_live, html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n\n      assert html =~ \"Show <%= schema.human_singular %>\"<%= if schema.string_attr do %>\n      assert html =~ <%= schema.singular %>.<%= schema.string_attr %><% end %>\n    end\n\n    test \"updates <%= schema.singular %> and returns to show\", %{conn: conn, <%= schema.singular %>: <%= schema.singular %><%= test_context_scope %>} do\n      {:ok, show_live, _html} = live(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n\n      assert {:ok, form_live, _} =\n               show_live\n               |> element(\"a\", \"Edit\")\n               |> render_click()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}/edit?return_to=show\")\n\n      assert render(form_live) =~ \"Edit <%= schema.human_singular %>\"\n\n      assert form_live\n             |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @invalid_attrs)\n             |> render_change() =~ \"<%= Mix.Phoenix.Schema.failed_render_change_message(schema) %>\"\n\n      assert {:ok, show_live, _html} =\n               form_live\n               |> form(\"#<%= schema.singular %>-form\", <%= schema.singular %>: @update_attrs)\n               |> render_submit()\n               |> follow_redirect(conn, ~p\"<%= scope_param_route_prefix %><%= schema.route_prefix %>/#{<%= schema.singular %>}\")\n\n      html = render(show_live)\n      assert html =~ \"<%= schema.human_singular %> updated successfully\"<%= if schema.string_attr do %>\n      assert html =~ \"some updated <%= schema.string_attr %>\"<% end %>\n    end\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.live/show.ex.eex",
    "content": "defmodule <%= inspect context.web_module %>.<%= inspect Module.concat(schema.web_namespace, schema.alias) %>Live.Show do\n  use <%= inspect context.web_module %>, :live_view\n\n  alias <%= inspect context.module %>\n\n  @impl true\n  def render(assigns) do\n    ~H\"\"\"\n    <Layouts.app flash={@flash}<%= if scope do %> <%= scope.assign_key %>={@<%= scope.assign_key %>}<% end %>>\n      <.header>\n        <%= schema.human_singular %> {@<%= schema.singular %>.<%= primary_key %>}\n        <:subtitle>This is a <%= schema.singular %> record from your database.</:subtitle>\n        <:actions>\n          <.button navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>\"}>\n            <.icon name=\"hero-arrow-left\" />\n          </.button>\n          <.button variant=\"primary\" navigate={~p\"<%= scope_assign_route_prefix %><%= schema.route_prefix %>/#{@<%= schema.singular %>}/edit?return_to=show\"}>\n            <.icon name=\"hero-pencil-square\" /> Edit <%= schema.singular %>\n          </.button>\n        </:actions>\n      </.header>\n\n      <.list><%= for {k, _} <- schema.attrs do %>\n        <:item title=\"<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>\">{@<%= schema.singular %>.<%= k %>}</:item><% end %>\n      </.list>\n    </Layouts.app>\n    \"\"\"\n  end\n\n  @impl true\n  def mount(%{\"<%= primary_key %>\" => <%= primary_key %>}, _session, socket) do<%= if scope do %>\n    if connected?(socket) do\n      <%= inspect context.alias %>.subscribe_<%= schema.plural %>(<%= socket_scope %>)\n    end\n<% end %>\n    {:ok,\n     socket\n     |> assign(:page_title, \"Show <%= schema.human_singular %>\")\n     |> assign(:<%= schema.singular %>, <%= inspect context.alias %>.get_<%= schema.singular %>!(<%= context_scope_prefix %><%= primary_key %>))}\n  end<%= if scope do %>\n\n  @impl true\n  def handle_info(\n        {:updated, %<%= inspect schema.module %>{<%= primary_key %>: <%= primary_key %>} = <%= schema.singular %>},\n        %{assigns: %{<%= schema.singular %>: %{<%= primary_key %>: <%= primary_key %>}}} = socket\n      ) do\n    {:noreply, assign(socket, :<%= schema.singular %>, <%= schema.singular %>)}\n  end\n\n  def handle_info(\n        {:deleted, %<%= inspect schema.module %>{<%= primary_key %>: <%= primary_key %>}},\n        %{assigns: %{<%= schema.singular %>: %{<%= primary_key %>: <%= primary_key %>}}} = socket\n      ) do\n    {:noreply,\n     socket\n     |> put_flash(:error, \"The current <%= schema.singular %> was deleted.\")\n     |> push_navigate(to: ~p\"<%= scope_socket_route_prefix %><%= schema.route_prefix %>\")}\n  end\n\n  def handle_info({type, %<%= inspect schema.module %>{}}, socket)\n      when type in [:created, :updated, :deleted] do\n    {:noreply, socket}\n  end<% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.notifier/notifier.ex.eex",
    "content": "defmodule <%= inspect context.module %> do\n  import Swoosh.Email\n  alias <%= inspect context.base_module %>.Mailer<%= for message <- notifier_messages do %>\n\n  def deliver_<%= message %>(%{name: name, email: email}) do\n    new()\n    |> to({name, email})\n    |> from({\"Phoenix Team\", \"team@example.com\"})\n    |> subject(\"Welcome to Phoenix, #{name}!\")\n    |> html_body(\"<h1>Hello, #{name}</h1>\")\n    |> text_body(\"Hello, #{name}\\n\")\n    |> Mailer.deliver()\n  end<% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.notifier/notifier_test.exs.eex",
    "content": "defmodule <%= inspect context.module %>Test do\n  use ExUnit.Case, async: true\n  import Swoosh.TestAssertions\n\n  alias <%= inspect context.module %><%= for message <- notifier_messages do %>\n\n  test \"deliver_<%= message %>/1\" do\n    user = %{name: \"Alice\", email: \"alice@example.com\"}\n\n    <%= inflections[:alias] %>.deliver_<%= message %>(user)\n\n    assert_email_sent(\n      subject: \"Welcome to Phoenix, Alice!\",\n      to: {\"Alice\", \"alice@example.com\"},\n      text_body: ~r/Hello, Alice/\n    )\n  end<% end %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.presence/presence.ex.eex",
    "content": "defmodule <%= module %> do\n  @moduledoc \"\"\"\n  Provides presence tracking to channels and processes.\n\n  See the [`Phoenix.Presence`](https://hexdocs.pm/phoenix/Phoenix.Presence.html)\n  docs for more details.\n  \"\"\"\n  use Phoenix.Presence,\n    otp_app: <%= inspect otp_app %>,\n    pubsub_server: <%= inspect pubsub_server %>\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.release/Dockerfile.eex",
    "content": "# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian\n# instead of Alpine to avoid DNS resolution issues in production.\n#\n# https://hub.docker.com/r/hexpm/elixir/tags?name=ubuntu\n# https://hub.docker.com/_/ubuntu/tags\n#\n# This file is based on these images:\n#\n#   - https://hub.docker.com/r/hexpm/elixir/tags - for the build image\n#   - https://hub.docker.com/_/debian/tags?name=<%= debian %>-<%= debian_vsn %>-slim - for the release image\n#   - https://pkgs.org/ - resource for finding needed packages\n#   - Ex: docker.io/hexpm/elixir:<%= elixir_vsn %>-erlang-<%= otp_vsn %>-debian-<%= debian %>-<%= debian_vsn %>-slim\n#\nARG ELIXIR_VERSION=<%= elixir_vsn %>\nARG OTP_VERSION=<%= otp_vsn %>\nARG DEBIAN_VERSION=<%= debian %>-<%= debian_vsn %>-slim\n\nARG BUILDER_IMAGE=\"docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}\"\nARG RUNNER_IMAGE=\"docker.io/debian:${DEBIAN_VERSION}\"\n\nFROM ${BUILDER_IMAGE} AS builder\n\n# install build dependencies\nRUN apt-get update \\\n  && apt-get install -y --no-install-recommends build-essential git \\\n  && rm -rf /var/lib/apt/lists/*\n\n# prepare build dir\nWORKDIR /app\n\n# install hex + rebar\nRUN mix local.hex --force \\\n  && mix local.rebar --force\n\n# set build ENV\nENV MIX_ENV=\"prod\"\n\n# install mix dependencies\nCOPY mix.exs mix.lock ./\nRUN mix deps.get --only $MIX_ENV\nRUN mkdir config\n\n# copy compile-time config files before we compile dependencies\n# to ensure any relevant config change will trigger the dependencies\n# to be re-compiled.\nCOPY config/config.exs config/${MIX_ENV}.exs config/\nRUN mix deps.compile\n<%= if assets_dir_exists? do %>\nRUN mix assets.setup\n<% end %>\nCOPY priv priv\n\nCOPY lib lib\n\n# Compile the release\nRUN mix compile\n<%= if assets_dir_exists? do %>\nCOPY assets assets\n\n# compile assets\nRUN mix assets.deploy\n<% end %>\n# Changes to config/runtime.exs don't require recompiling the code\nCOPY config/runtime.exs config/\n\nCOPY rel rel\nRUN mix release\n\n# start a new build stage so that the final image will only contain\n# the compiled release and other runtime necessities\nFROM ${RUNNER_IMAGE} AS final\n\nRUN apt-get update \\\n  && apt-get install -y --no-install-recommends libstdc++6 openssl libncurses6 locales ca-certificates \\\n  && rm -rf /var/lib/apt/lists/*\n\n# Set the locale\nRUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \\\n  && locale-gen\n\nENV LANG=en_US.UTF-8\nENV LANGUAGE=en_US:en\nENV LC_ALL=en_US.UTF-8\n\nWORKDIR \"/app\"\nRUN chown nobody /app\n\n# set runner ENV\nENV MIX_ENV=\"prod\"\n\n# Only copy the final release from the build stage\nCOPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/<%= otp_app %> ./\n\nUSER nobody\n\n# If using an environment that doesn't automatically reap zombie processes, it is\n# advised to add an init process such as tini via `apt-get install`\n# above and adding an entrypoint. See https://github.com/krallin/tini for details\n# ENTRYPOINT [\"/tini\", \"--\"]\n\nCMD [\"/app/bin/server\"]\n"
  },
  {
    "path": "priv/templates/phx.gen.release/dockerignore.eex",
    "content": "# This file excludes paths from the Docker build context.\n#\n# By default, Docker's build context includes all files (and folders) in the\n# current directory. Even if a file isn't copied into the container it is still sent to\n# the Docker daemon.\n#\n# There are multiple reasons to exclude files from the build context:\n#\n# 1. Prevent nested folders from being copied into the container (ex: exclude\n#    /assets/node_modules when copying /assets)\n# 2. Reduce the size of the build context and improve build time (ex. /build, /deps, /doc)\n# 3. Avoid sending files containing sensitive information\n#\n# More information on using .dockerignore is available here:\n# https://docs.docker.com/engine/reference/builder/#dockerignore-file\n\n.dockerignore\n\n# Ignore git, but keep git HEAD and refs to access current commit hash if needed:\n#\n# $ cat .git/HEAD | awk '{print \".git/\"$2}' | xargs cat\n# d0b8727759e1e0e7aa3d41707d12376e373d5ecc\n.git\n!.git/HEAD\n!.git/refs\n\n# Common development/test artifacts\n/cover/\n/doc/\n/test/\n/tmp/\n.elixir_ls\n\n# Mix artifacts\n/_build/\n/deps/\n*.ez\n\n# Generated on crash by the VM\nerl_crash.dump\n\n# Static artifacts - These should be fetched and built inside the Docker image\n# https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Release.html#module-docker\n/assets/node_modules/\n/priv/static/assets/\n/priv/static/cache_manifest.json\n"
  },
  {
    "path": "priv/templates/phx.gen.release/rel/migrate.bat.eex",
    "content": "call \"%~dp0\\<%= otp_app %>\" eval <%= app_namespace %>.Release.migrate\n"
  },
  {
    "path": "priv/templates/phx.gen.release/rel/migrate.sh.eex",
    "content": "#!/bin/sh\nset -eu\n\ncd -P -- \"$(dirname -- \"$0\")\"\nexec ./<%= otp_app %> eval <%= app_namespace %>.Release.migrate\n"
  },
  {
    "path": "priv/templates/phx.gen.release/rel/server.bat.eex",
    "content": "set PHX_SERVER=true\ncall \"%~dp0\\<%= otp_app %>\" start\n"
  },
  {
    "path": "priv/templates/phx.gen.release/rel/server.sh.eex",
    "content": "#!/bin/sh\nset -eu\n\ncd -P -- \"$(dirname -- \"$0\")\"\nPHX_SERVER=true exec ./<%= otp_app %> start\n"
  },
  {
    "path": "priv/templates/phx.gen.release/release.ex.eex",
    "content": "defmodule <%= app_namespace %>.Release do\n  @moduledoc \"\"\"\n  Used for executing DB release tasks when run in production without Mix\n  installed.\n  \"\"\"\n  @app :<%= otp_app %>\n\n  def migrate do\n    load_app()\n\n    for repo <- repos() do\n      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))\n    end\n  end\n\n  def rollback(repo, version) do\n    load_app()\n    {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))\n  end\n\n  defp repos do\n    Application.fetch_env!(@app, :ecto_repos)\n  end\n\n  defp load_app do\n    # Many platforms require SSL when connecting to the database\n    Application.ensure_all_started(:ssl)\n    Application.ensure_loaded(@app)\n  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.schema/migration.exs.eex",
    "content": "defmodule <%= inspect schema.repo %>.Migrations.Create<%= Macro.camelize(schema.table) %> do\n  use <%= inspect schema.migration_module %>\n\n  def change do\n    create table(:<%= schema.table %><%= if schema.binary_id || schema.opts[:primary_key] do %>, primary_key: false<% end %><%= if schema.prefix do %>, prefix: :<%= schema.prefix %><% end %>) do\n<%= if schema.binary_id do %>      add :<%= primary_key %>, :binary_id, primary_key: true\n<% else %><%= if schema.opts[:primary_key] do %>      add :<%= schema.opts[:primary_key] %>, :id, primary_key: true\n<% end %><% end %><%= for {k, v} <- schema.attrs do %>      add <%= inspect k %>, <%= inspect Mix.Phoenix.Schema.type_for_migration(v) %><%= schema.migration_defaults[k] %>\n<% end %><%= for {_, i, _, s} <- schema.assocs do %>      add <%= inspect(i) %>, references(<%= inspect(s) %>, on_delete: :nothing<%= if schema.binary_id do %>, type: :binary_id<% end %>)\n<% end %><%= if scope do %>      add :<%= scope.schema_key %>, <%= if scope.schema_table do %>references(:<%= scope.schema_table %>, type: <%= inspect scope.schema_migration_type %>, on_delete: :delete_all)<% else %><%= inspect scope.schema_migration_type %><% end %>\n<% end %>\n      timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}\" %>)\n    end<%= if scope do %>\n\n    create index(:<%= schema.table %>, [:<%= scope.schema_key %>])<% end %>\n<%= if Enum.any?(schema.indexes) do %><%= for index <- schema.indexes do %>\n    <%= index %><% end %>\n<% end %>  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.schema/schema.ex.eex",
    "content": "defmodule <%= inspect schema.module %> do\n  use Ecto.Schema\n  import Ecto.Changeset\n<%= if schema.prefix do %>\n  @schema_prefix :<%= schema.prefix %><% end %><%= if schema.opts[:primary_key] do %>\n  @derive {Phoenix.Param, key: :<%= schema.opts[:primary_key] %>}<% end %><%= if schema.binary_id do %>\n  @primary_key {:<%= primary_key %>, :binary_id, autogenerate: true}\n  @foreign_key_type :binary_id<% else %><%= if schema.opts[:primary_key] do %>\n  @primary_key {:<%= schema.opts[:primary_key] %>, :id, autogenerate: true}<% end %><% end %>\n  schema <%= inspect schema.table %> do\n<%= Mix.Phoenix.Schema.format_fields_for_schema(schema) %>\n<%= for {_, k, _, _} <- schema.assocs do %>    field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %>\n<% end %><%= if scope do %>    field :<%= scope.schema_key %>, <%= inspect scope.schema_type %>\n<% end %>\n    timestamps(<%= if schema.timestamp_type != :naive_datetime, do: \"type: #{inspect schema.timestamp_type}\" %>)\n  end\n\n  @doc false\n  def changeset(<%= schema.singular %>, attrs<%= if scope do %>, <%= scope.name %>_scope<% end %>) do\n    <%= schema.singular %>\n    |> cast(attrs, [<%= Enum.map_join(schema.attrs, \", \", &inspect(elem(&1, 0))) %>])\n    |> validate_required([<%= Enum.map_join(Mix.Phoenix.Schema.required_fields(schema), \", \", &inspect(elem(&1, 0))) %>])\n<%= for k <- schema.uniques do %>    |> unique_constraint(<%= inspect k %>)\n<% end %><%= if scope do %>    |> put_change(:<%= scope.schema_key %>, <%= scope.name %>_scope.<%= Enum.join(scope.access_path, \".\") %>)\n<% end %>  end\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.socket/socket.ex.eex",
    "content": "defmodule <%= module %>Socket do\n  use Phoenix.Socket\n\n  # A Socket handler\n  #\n  # It's possible to control the websocket connection and\n  # assign values that can be accessed by your channel topics.\n\n  ## Channels<%= if existing_channel do %>\n\n  channel \"<%= existing_channel[:singular] %>:*\", <%= existing_channel[:module] %>Channel\n<% else %>\n  # Uncomment the following line to define a \"room:*\" topic\n  # pointing to the `<%= web_module %>.RoomChannel`:\n  #\n  # channel \"room:*\", <%= web_module %>.RoomChannel\n  #\n  # To create a channel file, use the mix task:\n  #\n  #     mix phx.gen.channel Room\n  #\n  # See the [`Channels guide`](https://hexdocs.pm/phoenix/channels.html)\n  # for further details.\n\n<% end %>\n  # Socket params are passed from the client and can\n  # be used to verify and authenticate a user. After\n  # verification, you can put default assigns into\n  # the socket that will be set for all channels, ie\n  #\n  #     {:ok, assign(socket, :user_id, verified_user_id)}\n  #\n  # To deny connection, return `:error` or `{:error, term}`. To control the\n  # response the client receives in that case, [define an error handler in the\n  # websocket\n  # configuration](https://hexdocs.pm/phoenix/Phoenix.Endpoint.html#socket/3-websocket-configuration).\n  #\n  # See `Phoenix.Token` documentation for examples in\n  # performing token verification on connect.\n  @impl true\n  def connect(_params, socket, _connect_info) do\n    {:ok, socket}\n  end\n\n  # Socket IDs are topics that allow you to identify all sockets for a given user:\n  #\n  #     def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n  #\n  # Would allow you to broadcast a \"disconnect\" event and terminate\n  # all active sockets and channels for a given user:\n  #\n  #     <%= endpoint_module %>.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n  #\n  # Returning `nil` makes this socket anonymous.\n  @impl true\n  def id(_socket), do: nil\nend\n"
  },
  {
    "path": "priv/templates/phx.gen.socket/socket.js.eex",
    "content": "// NOTE: The contents of this file will only be executed if\n// you uncomment its entry in \"assets/js/app.js\".\n\n// Bring in Phoenix channels client library:\nimport {Socket} from \"phoenix\"\n\n// And connect to the path in \"<%= web_prefix %>/endpoint.ex\". We pass the\n// token for authentication.\n//\n// Read the [`Using Token Authentication`](https://hexdocs.pm/phoenix/channels.html#using-token-authentication)\n// section to see how the token should be used.\nlet socket = new Socket(\"/socket\", {authToken: window.userToken})\nsocket.connect()\n\n// Now that you are connected, you can join channels with a topic.\n// Let's assume you have a channel with a topic named `room` and the\n// subtopic is its id - in this case 42:\nlet channel = socket.channel(\"room:42\", {})\nchannel.join()\n  .receive(\"ok\", resp => { console.log(\"Joined successfully\", resp) })\n  .receive(\"error\", resp => { console.log(\"Unable to join\", resp) })\n\nexport default socket\n"
  },
  {
    "path": "test/fixtures/digest/cleaner/cache_manifest.json",
    "content": "{\n  \"digests\": {\n    \"app-1.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132171,\n      \"size\": 369053,\n      \"digest\": \"1\"\n    },\n    \"app-2.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132172,\n      \"size\": 369053,\n      \"digest\": \"2\"\n    },\n    \"app-3.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132173,\n      \"size\": 369053,\n      \"digest\": \"3\"\n    }\n  },\n  \"latest\": {\n    \"app.css\": \"app-3.css\"\n  },\n  \"version\": 1\n}\n"
  },
  {
    "path": "test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json",
    "content": "{\n  \"digests\": {\n    \"app-1.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132171,\n      \"size\": 369053,\n      \"digest\": \"1\"\n    },\n    \"app-2.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132172,\n      \"size\": 369053,\n      \"digest\": \"2\"\n    },\n    \"app-3.css\": {\n      \"logical_path\": \"app.css\",\n      \"mtime\": 32132170,\n      \"size\": 369053,\n      \"digest\": \"3\"\n    }\n  },\n  \"latest\": {\n    \"app.css\": \"app-3.css\"\n  },\n  \"version\": 1\n}\n"
  },
  {
    "path": "test/fixtures/digest/compile/cache_manifest.json",
    "content": "{\n  \"digests\": {\n    \"foo-d978852bea6530fcd197b5445ed008fd.css\": {\n      \"logical_path\": \"foo.css\",\n      \"mtime\": 32132171,\n      \"size\": 369053,\n      \"digest\": \"d978852bea6530fcd197b5445ed008fd\"\n    }\n  },\n  \"latest\": {\n    \"foo.css\": \"foo-d978852bea6530fcd197b5445ed008fd.css\"\n  },\n  \"version\": 1\n}\n"
  },
  {
    "path": "test/fixtures/digest/compile/cache_manifest_upgrade.json",
    "content": "{\n  \"digests\": {\n    \"foo-abcdef.css\": {\n      \"logical_path\": \"foo.css\",\n      \"mtime\": 32132171,\n      \"size\": 369053,\n      \"digest\": \"abcdev\"\n    },\n    \"foo-ghijkl.css\": {\n      \"logical_path\": \"foo.css\",\n      \"mtime\": 32193492,\n      \"size\": 372059,\n      \"digest\": \"abcdev\"\n    }\n  },\n  \"latest\": {\n    \"foo.css\": \"foo-ghijkl.css\"\n  },\n  \"version\": 1\n}\n"
  },
  {
    "path": "test/fixtures/digest/priv/output/foo-288ea8c7954498e65663c817382eeac4.css",
    "content": ".foo { background-color: blue }\n"
  },
  {
    "path": "test/fixtures/digest/priv/output/foo-d978852bea6530fcd197b5445ed008fd.css",
    "content": ".foo { background-color: red }\n"
  },
  {
    "path": "test/fixtures/digest/priv/static/app.js",
    "content": "(function() {\n  console.log('Hello World!');\n})();\n//# sourceMappingURL=app.js.map\n"
  },
  {
    "path": "test/fixtures/digest/priv/static/css/app.css",
    "content": ".absolute_path_logo {\n  background-image: url(\"/phoenix.png\");\n}\n\n.relative_path_logo {\n  background-image: url('../images/relative.png');\n}\n\n.absolute_url_logo {\n  background-image: url(http://www.phoenixframework.org/absolute.png);\n}\n\n.absolute_path_logo{background-image:url(/phoenix.png)}\n.relative_path_logo{background-image:url(../images/relative.png)}\n.absolute_url_logo{background-image:url(http://www.phoenixframework.org/absolute.png)}\n"
  },
  {
    "path": "test/fixtures/digest/priv/static/foo.css",
    "content": ".foo { background-color: red }\n"
  },
  {
    "path": "test/fixtures/digest/priv/static/manifest.json",
    "content": "{\n  \"name\": \"MyPhoenixApp\",\n  \"short_name\": \"MyPhoenixApp\",\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"background_color\": \"#F67938\",\n  \"description\": \"A simple Phoenix app.\",\n  \"icons\": [{\n    \"src\": \"images/touch/homescreen48.png\",\n    \"sizes\": \"48x48\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"images/touch/homescreen72.png\",\n    \"sizes\": \"72x72\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"images/touch/homescreen96.png\",\n    \"sizes\": \"96x96\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"images/touch/homescreen144.png\",\n    \"sizes\": \"144x144\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"images/touch/homescreen168.png\",\n    \"sizes\": \"168x168\",\n    \"type\": \"image/png\"\n  }, {\n    \"src\": \"images/touch/homescreen192.png\",\n    \"sizes\": \"192x192\",\n    \"type\": \"image/png\"\n  }]\n}\n"
  },
  {
    "path": "test/fixtures/digest/priv/static/precompressed.js.br",
    "content": "Brotli\n"
  },
  {
    "path": "test/fixtures/hello.txt",
    "content": "world"
  },
  {
    "path": "test/fixtures/ssl/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEwTCCA6mgAwIBAgIJAJmNQmFFDZgVMA0GCSqGSIb3DQEBBQUAMIGbMQswCQYD\nVQQGEwJVUzENMAsGA1UECBMET2hpbzEPMA0GA1UEBxMGRGF5dG9uMRowGAYDVQQK\nExFQaG9lbml4IEZyYW1ld29yazEaMBgGA1UECxMRU3lzdGVtIEFyY2hpdGVjdHMx\nEjAQBgNVBAMTCWxvY2FsaG9zdDEgMB4GCSqGSIb3DQEJARYRcGhvZW5peEBsb2Nh\nbGhvc3QwHhcNMTQwNTI1MDAxNTM5WhcNMTUwNTI1MDAxNTM5WjCBmzELMAkGA1UE\nBhMCVVMxDTALBgNVBAgTBE9oaW8xDzANBgNVBAcTBkRheXRvbjEaMBgGA1UEChMR\nUGhvZW5peCBGcmFtZXdvcmsxGjAYBgNVBAsTEVN5c3RlbSBBcmNoaXRlY3RzMRIw\nEAYDVQQDEwlsb2NhbGhvc3QxIDAeBgkqhkiG9w0BCQEWEXBob2VuaXhAbG9jYWxo\nb3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0g3Mlh7ZRYiE+btN\nbePpRGzdj4NIyleZj5LXxRe8xZs/WHb4j1+UfUxRrAO141UYcE1ZXkpg8x64uUiR\nm281JPvHGe8CqZlckZQGVF77Bv8oH6gGTxyrI7Gdni2CJgMqR9A4Kwdlczk7d+fV\nvQCl8B5izEmCTXWu8RG/QU1BHQLygT/DSHgeGjzQaaE0OoD/Hxa/zkVPEugjNtxF\nYVWGQ0gzqsTPFz1CkIp1+CJHzm/fhFtFaS0aDeFyMW1BRC8CTYXyYdcM9cSn/ahh\ngmKhYKY3EbRaiE4dfH2rqzvixc+OOEQVpMPDCVDG0ms/FhCrLzVBoY4/WiZVv6aq\n2jznmwIDAQABo4IBBDCCAQAwHQYDVR0OBBYEFPQA6slwgMEFuEbYd+lG4WICZb1u\nMIHQBgNVHSMEgcgwgcWAFPQA6slwgMEFuEbYd+lG4WICZb1uoYGhpIGeMIGbMQsw\nCQYDVQQGEwJVUzENMAsGA1UECBMET2hpbzEPMA0GA1UEBxMGRGF5dG9uMRowGAYD\nVQQKExFQaG9lbml4IEZyYW1ld29yazEaMBgGA1UECxMRU3lzdGVtIEFyY2hpdGVj\ndHMxEjAQBgNVBAMTCWxvY2FsaG9zdDEgMB4GCSqGSIb3DQEJARYRcGhvZW5peEBs\nb2NhbGhvc3SCCQCZjUJhRQ2YFTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA\nA4IBAQAZkmKd/KPN3FXsgYU+2iNKHAdzByEugGbTSdba5WBAwwJCumhGUy8CFXY6\nMhfrQjj7mkkU4UFHkHXZCWbucfTGxWFaizkkaStsY+5K6sKVFQZw8kc5llDD4dyG\nJNGX99VYtVvJG/GS/rXiOTHJvR4WYbgJ5DPYI+PVWBVIp5wG9T2G8TinTnguFGC6\n/EFmYf06XGUAKWsMfzNm8Dm7fkQ94W27FVtJ9RvdeObi2aJt37bE9DVqUkjZ1qtz\nfz3uJ5UyoNFn5tCBOdEooivPlvl4wqSTppIpflMNlE82KhSRnESnZF9JQUGW3Lnd\n2H3UOAYVUDhGVP/J9ZJLTcr8O8zA\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/fixtures/ssl/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA0g3Mlh7ZRYiE+btNbePpRGzdj4NIyleZj5LXxRe8xZs/WHb4\nj1+UfUxRrAO141UYcE1ZXkpg8x64uUiRm281JPvHGe8CqZlckZQGVF77Bv8oH6gG\nTxyrI7Gdni2CJgMqR9A4Kwdlczk7d+fVvQCl8B5izEmCTXWu8RG/QU1BHQLygT/D\nSHgeGjzQaaE0OoD/Hxa/zkVPEugjNtxFYVWGQ0gzqsTPFz1CkIp1+CJHzm/fhFtF\naS0aDeFyMW1BRC8CTYXyYdcM9cSn/ahhgmKhYKY3EbRaiE4dfH2rqzvixc+OOEQV\npMPDCVDG0ms/FhCrLzVBoY4/WiZVv6aq2jznmwIDAQABAoIBAFhR/QfSCME32dG3\nc6MVBWwD6lUBeoW5t5OqxpbUmEbuNABaZcDDC4hzopOVK9FeYlw16bG/zGvtKvad\nELwuUkYup1S8Ln5pQYbkmpS3Kw2SE6jb2WtCPqNPd1qe/+5Dvm9bmYJeJcYA9oRA\nMpq5vwvretcywVsYdGpgb+5hMVOkunw25I4eNeqZHX1ZKmcgq4I/UcZzoF2qPUC6\njtU4reZn8yTgki0YRJVKvw2QLKTegVbK0JHhHDQBxHyfaoj+gd1abZHHMcG0k82k\nHr86xOJYVGcmZeKk9P/VIPCASgfAbIR23lFkyTxH+GGcrUQvn/kvruKvpQqXgDRo\npz+0IUECgYEA79u2t6SNjZf9z01ZZdWvI0q4q5/v/uuIx05NCbpugJh9XVCoJ88T\njLf7Ul8EKFj9i4CG9HlrtFkIwgKoJGiiIOdUD0vNwSgFEwht8KnbxHASsqo8UU1x\nbjgKBPKz3c2hJu4iXQikjHD+wAl2m7X+K0Z62gcY2IKdFI67RpsxwWMCgYEA4DCd\ncIWtznK40PboyeUIYtgrNKSaS1kbQOb2nj3HhJ3X3cbFvk5Yl28ac4cjmidXvN7Q\n3bthZFI0ItagLs1raMbiX4kKo2ISeS2jkJZK2PQFWh7Brc4Q+RktTIRFoHyP35uO\ndvHJH+rEz0oURbjqSlDbB/eFysiOK8mRUTd38mkCgYARWu5/nzJ22laNF2WujqWb\ngh6WnH37DgPZl/rPB2RTfbUkeV+RcdRSTEWtEh705GuEGoqpSdfXNtIBZ7vO1ptU\nkihs6uk6XrDvTZ7W2RODxTA1KUgwAdCBTyC6du040VYlwPlPjf6KAusL7iNc5PA9\nJV5iRD0x/VFsWV+HnlcdTQKBgQDPn3ZPPR4n8csDi4cvYzMPB4+L410Zpt48jyma\nhzB9uwit1WZQxpH5POXMVD0+iG0S92+Lyft6Qz8RfJ9AePGeSYJgY7Q8d5kQLJos\nT2Pl5KgIPC+2XP8PEqgHEwDAjltYBOI9edKAApZeOwbnQ0eHp7YRfMSldnNkTfqM\nssgc8QKBgDflG6bfSrjOjgyppsII4tXKdpnRa/UwN98D3/ZU5VJA8jR8yzHuMico\nbpeooh03uH8xLalXlXNRAamSv03KprvXRXeA+GeAeoseB1qHJ7Z2pkkqiHFG2XxY\nu0jAsKamaT6YjcLqloxOdPSyDoXon6pKxvSsiUg1l9ZASexuTiZV\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/fixtures/templates/custom.foo",
    "content": "from foo"
  },
  {
    "path": "test/fixtures/templates/layout/app.html.eex",
    "content": "<html>\n  <title><%= @title || default_title() %></title>\n  <%= @inner_content %>\n</html>\n"
  },
  {
    "path": "test/fixtures/templates/layout/root.html.eex",
    "content": "ROOTSTART[<%= @title %>]<%= @inner_content %>ROOTEND\n"
  },
  {
    "path": "test/fixtures/templates/no_trim.text.eex",
    "content": "<%= 123 %>\n<%= 456 %>\n<%= 789 %>\n"
  },
  {
    "path": "test/fixtures/templates/path.html.eex",
    "content": "path\n"
  },
  {
    "path": "test/fixtures/templates/safe.html.eex",
    "content": "Raw <%= {:safe, @message} %>\n"
  },
  {
    "path": "test/fixtures/templates/show.html.eex",
    "content": "<div>Show! <%= @message %></div>\n<% :ok # Template is still valid %>\n"
  },
  {
    "path": "test/fixtures/templates/trim.html.eex",
    "content": "<%= 123 %>\n<%= 456 %>\n<%= 789 %>\n"
  },
  {
    "path": "test/fixtures/templates/user/index.html.eex",
    "content": "<%= escaped_title @title %>\n"
  },
  {
    "path": "test/fixtures/templates/user/profiles/admin.html.eex",
    "content": "admin profile\n"
  },
  {
    "path": "test/fixtures/templates/user/render_template.html.eex",
    "content": "rendered template for <%= @name %>\n"
  },
  {
    "path": "test/fixtures/templates/user/show.json.exs",
    "content": "%{foo: \"bar\"}\n"
  },
  {
    "path": "test/fixtures/views.exs",
    "content": "defmodule MyApp.View do\n  use Phoenix.View, root: \"test/fixtures/templates\"\n\n  def escaped_title(title) do\n    {:safe, Plug.HTML.html_escape(title)}\n  end\nend\n\ndefmodule MyApp.LayoutView do\n  use Phoenix.View, root: \"test/fixtures/templates\"\n\n  def default_title do\n    \"MyApp\"\n  end\nend\n\ndefmodule MyApp.User do\n  defstruct name: \"name\"\nend\n\ndefmodule MyApp.PathView do\n  use Phoenix.View, root: \"test/fixtures/templates\", path: \"\"\nend\n\ndefmodule MyApp.UserView do\n  use Phoenix.View, root: \"test/fixtures/templates\", pattern: \"**/*\"\n\n  import Phoenix.Controller, only: [view_module: 1, view_template: 1]\n\n  def escaped_title(title) do\n    {:safe, Plug.HTML.html_escape(title)}\n  end\n\n  def render(\"message.html\", _assigns) do\n    send(self(), :message_sent)\n    \"message sent\"\n  end\n\n  def render(\"show.text\", %{user: user, prefix: prefix}) do\n    \"show user: \" <> prefix <> user.name\n  end\n\n  def render(\"show.text\", %{user: user}) do\n    \"show user: \" <> user.name\n  end\n\n  def render(\"data.text\", %{data: data}) do\n    \"show data: \" <> data.name\n  end\n\n  def render(\"edit.html\", %{} = assigns) do\n    \"EDIT#{assigns[:layout]} - #{assigns[:title]}\"\n  end\n\n  def render(\"existing.html\", _), do: \"rendered existing\"\n\n  def render(\"inner.html\", assigns) do\n    \"\"\"\n    View module is #{view_module(assigns.conn)} and view template is #{view_template(assigns.conn)}\n    \"\"\"\n  end\n\n  def render(\"render_template.html\" = tpl, %{name: name}) do\n    render_template(tpl, %{name: String.upcase(name)})\n  end\n\n  def render(\"to_iodata.html\", %{to_iodata: to_iodata}) do\n    to_iodata\n  end\nend\n\ndefmodule MyApp.Templates.UserView do\n  use Phoenix.View, root: \"test/fixtures\"\n\n  def escaped_title(title) do\n    {:safe, Plug.HTML.html_escape(title)}\n  end\nend\n\ndefmodule MyApp.Nested.User do\n  defstruct name: \"nested name\"\nend\n\ndefmodule MyApp.Nested.UserView do\n  use Phoenix.View, root: \"test/fixtures/templates\", namespace: MyApp.Nested\n\n  def render(\"show.text\", %{user: user}) do\n    \"show nested user: \" <> user.name\n  end\n\n  def escaped_title(title) do\n    {:safe, Plug.HTML.html_escape(title)}\n  end\nend\n"
  },
  {
    "path": "test/mix/phoenix_test.exs",
    "content": "defmodule Mix.PhoenixTest do\n  use ExUnit.Case, async: true\n\n  doctest Mix.Phoenix, import: true\n\n  test \"base/0 returns the module base based on the Mix application\" do\n    assert Mix.Phoenix.base() == \"Phoenix\"\n    Application.put_env(:phoenix, :namespace, Phoenix.Sample.App)\n    assert Mix.Phoenix.base() == \"Phoenix.Sample.App\"\n  after\n    Application.delete_env(:phoenix, :namespace)\n  end\n\n  test \"modules/0 returns all modules in project\" do\n    assert Phoenix.Router in Mix.Phoenix.modules()\n  end\n\n  test \"attrs/1 defaults each type\" do\n    attrs = [\n      \"logins:array:string\",\n      \"lottery_numbers:array:integer\",\n      \"age:integer\",\n      \"temp:float\",\n      \"temp_2:decimal\",\n      \"admin:boolean\",\n      \"meta:map\",\n      \"name:text\",\n      \"date_of_birth:date\",\n      \"happy_hour:time\",\n      \"joined:naive_datetime\",\n      \"token:uuid\"\n    ]\n\n    assert Mix.Phoenix.Schema.attrs(attrs) == [\n             logins: {:array, :string},\n             lottery_numbers: {:array, :integer},\n             age: :integer,\n             temp: :float,\n             temp_2: :decimal,\n             admin: :boolean,\n             meta: :map,\n             name: :text,\n             date_of_birth: :date,\n             happy_hour: :time,\n             joined: :naive_datetime,\n             token: :uuid\n           ]\n  end\n\n  test \"attrs/1 raises with an unknown type\" do\n    assert_raise(Mix.Error, ~r\"Unknown type `:other` given to generator\", fn ->\n      Mix.Phoenix.Schema.attrs([\"other:other\"])\n    end)\n  end\n\n  test \"params/1 defaults each type\" do\n    params = [\n      logins: {:array, :string},\n      age: :integer,\n      temp: :float,\n      temp_2: :decimal,\n      admin: :boolean,\n      meta: :map,\n      name: :text,\n      date_of_birth: :date,\n      happy_hour: :time,\n      happy_hour_usec: :time_usec,\n      joined: :naive_datetime,\n      joined_utc: :utc_datetime,\n      joined_utc_usec: :utc_datetime_usec,\n      token: :uuid,\n      other: :other\n    ]\n\n    assert Mix.Phoenix.Schema.params(params) == %{\n             logins: [\"option1\", \"option2\"],\n             age: 42,\n             temp: 120.5,\n             temp_2: \"120.5\",\n             admin: true,\n             meta: %{},\n             name: \"some name\",\n             date_of_birth: Date.add(Date.utc_today(), -1),\n             happy_hour: ~T[14:00:00],\n             happy_hour_usec: ~T[14:00:00.000000],\n             joined: NaiveDateTime.truncate(build_utc_naive_datetime(), :second),\n             joined_utc: DateTime.truncate(build_utc_datetime(), :second),\n             joined_utc_usec: build_utc_datetime(),\n             token: \"7488a646-e31f-11e4-aace-600308960662\",\n             other: \"some other\"\n           }\n  end\n\n  @one_day_in_seconds 24 * 3600\n\n  defp build_utc_datetime,\n    do: DateTime.add(%{DateTime.utc_now() | second: 0, microsecond: {0, 6}}, -@one_day_in_seconds)\n\n  defp build_utc_naive_datetime,\n    do:\n      NaiveDateTime.add(\n        %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}},\n        -@one_day_in_seconds\n      )\n\n  test \"live_form_value/1\" do\n    assert Mix.Phoenix.Schema.live_form_value(~D[2020-10-09]) == \"2020-10-09\"\n    assert Mix.Phoenix.Schema.live_form_value(~T[14:00:00]) == \"14:00\"\n    assert Mix.Phoenix.Schema.live_form_value(~T[14:01:00]) == \"14:01\"\n    assert Mix.Phoenix.Schema.live_form_value(~T[14:15:40]) == \"14:15\"\n\n    assert Mix.Phoenix.Schema.live_form_value(~N[2020-10-09 14:00:00]) == \"2020-10-09T14:00:00\"\n\n    assert Mix.Phoenix.Schema.live_form_value(~U[2020-10-09T14:00:00Z]) ==\n             \"2020-10-09T14:00:00Z\"\n\n    assert Mix.Phoenix.Schema.live_form_value([1]) == [1]\n    assert Mix.Phoenix.Schema.live_form_value([\"option1\"]) == [\"option1\"]\n\n    assert Mix.Phoenix.Schema.live_form_value(:value) == :value\n  end\n\n  test \"invalid_form_value/1\" do\n    assert ~D[2020-10-09]\n           |> Mix.Phoenix.Schema.invalid_form_value() == \"2022-00\"\n\n    assert ~T[14:00:00]\n           |> Mix.Phoenix.Schema.invalid_form_value() == %{hour: 14, minute: 00}\n\n    assert ~N[2020-10-09 14:00:00]\n           |> Mix.Phoenix.Schema.invalid_form_value() == \"2022-00\"\n\n    assert ~U[2020-10-09T14:00:00Z]\n           |> Mix.Phoenix.Schema.invalid_form_value() == \"2022-00\"\n\n    assert Mix.Phoenix.Schema.invalid_form_value(true) == false\n    assert Mix.Phoenix.Schema.invalid_form_value(:anything) == nil\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.digest.clean_test.exs",
    "content": "defmodule Mix.Tasks.Phx.Digest.CleanTest do\n  use ExUnit.Case\n\n  test \"fails when the given paths are invalid\" do\n    Mix.Tasks.Phx.Digest.Clean.run([\"--output\", \"invalid_path\", \"--no-compile\"])\n\n    assert_received {:mix_shell, :error, [\"The output path \\\"invalid_path\\\" does not exist\"]}\n  end\n\n  test \"removes old versions\", config do\n    output_path = Path.join(\"tmp\", to_string(config.test))\n    input_path = \"priv/static\"\n\n    try do\n      :ok = File.mkdir_p!(output_path)\n\n      Mix.Tasks.Phx.Digest.Clean.run([input_path, \"-o\", output_path, \"--no-compile\"])\n\n      msg = \"Clean complete for \\\"#{output_path}\\\"\"\n      assert_received {:mix_shell, :info, [^msg]}\n    after\n      File.rm_rf!(output_path)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.digest_test.exs",
    "content": "Code.require_file \"../../../installer/test/mix_helper.exs\", __DIR__\n\ndefmodule Mix.Tasks.Phx.DigestTest do\n  use ExUnit.Case\n  import MixHelper\n\n  test \"logs when the path is invalid\" do\n    Mix.Tasks.Phx.Digest.run([\"invalid_path\", \"--no-compile\"])\n    assert_received {:mix_shell, :error, [\"The input path \\\"invalid_path\\\" does not exist\"]}\n  end\n\n  @output_path \"mix_phoenix_digest\"\n  test \"digests and compress files\" do\n    in_tmp @output_path, fn ->\n      File.mkdir_p!(\"priv/static\")\n      Mix.Tasks.Phx.Digest.run([\"priv/static\", \"-o\", @output_path, \"--no-compile\"])\n      assert_received {:mix_shell, :info, [\"Check your digested files at \\\"mix_phoenix_digest\\\"\"]}\n    end\n  end\n\n  @output_path \"mix_phoenix_digest_no_input\"\n  test \"digests and compress files without the input path\" do\n    in_tmp @output_path, fn ->\n      File.mkdir_p!(\"priv/static\")\n      Mix.Tasks.Phx.Digest.run([\"-o\", @output_path, \"--no-compile\"])\n      assert_received {:mix_shell, :info, [\"Check your digested files at \\\"mix_phoenix_digest_no_input\\\"\"]}\n    end\n  end\n\n  @input_path \"input_path\"\n  test \"uses the input path as output path when no output path is given\" do\n    in_tmp @input_path, fn ->\n      File.mkdir_p!(@input_path)\n      Mix.Tasks.Phx.Digest.run([@input_path, \"--no-compile\"])\n      assert_received {:mix_shell, :info, [\"Check your digested files at \\\"input_path\\\"\"]}\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.auth/injector_test.exs",
    "content": "defmodule Mix.Tasks.Phx.Gen.Auth.InjectorTest do\n  use ExUnit.Case, async: true\n\n  alias Mix.Phoenix.{Context, Schema}\n  alias Mix.Tasks.Phx.Gen.Auth.{HashingLibrary, Injector}\n\n  describe \"mix_dependency_inject/2\" do\n    test \"injects before existing dependencies\" do\n      existing_file = \"\"\"\n      defmodule RainyDay.MixProject do\n        use Mix.Project\n\n        def project do\n          [\n            app: :rainy_day,\n            version: \"0.1.0\",\n            build_path: \"../../_build\",\n            config_path: \"../../config/config.exs\",\n            deps_path: \"../../deps\",\n            lockfile: \"../../mix.lock\",\n            elixir: \"~> 1.7\",\n            elixirc_paths: elixirc_paths(Mix.env()),\n            start_permanent: Mix.env() == :prod,\n            aliases: aliases(),\n            deps: deps()\n          ]\n        end\n\n        # Configuration for the OTP application.\n        #\n        # Type `mix help compile.app` for more information.\n        def application do\n          [\n            mod: {RainyDay.Application, []},\n            extra_applications: [:logger, :runtime_tools]\n          ]\n        end\n\n        # Specifies which paths to compile per environment.\n        defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n        defp elixirc_paths(_), do: [\"lib\"]\n\n        # Specifies your project dependencies.\n        #\n        # Type `mix help deps` for examples and options.\n        defp deps do\n          [\n            {:phoenix_pubsub, \"~> 2.0-dev\", github: \"phoenixframework/phoenix_pubsub\"},\n            {:ecto_sql, \"~> 3.10\"},\n            {:postgrex, \">= 0.0.0\"},\n            {:jason, \"~> 1.0\"}\n          ]\n        end\n\n        # Aliases are shortcuts or tasks specific to the current project.\n        # For example, to create, migrate and run the seeds file at once:\n        #\n        #     $ mix ecto.setup\n        #\n        # See the documentation for `Mix` for more info on aliases.\n        defp aliases do\n          [\n            \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/seeds.exs\"],\n            \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n            test: [\"ecto.create --quiet\", \"ecto.migrate\", \"test\"]\n          ]\n        end\n      end\n      \"\"\"\n\n      inject = ~s|{:bcrypt_elixir, \"~> 2.0\"}|\n\n      assert {:ok, new_file} = Injector.mix_dependency_inject(existing_file, inject)\n\n      assert new_file == \"\"\"\n             defmodule RainyDay.MixProject do\n               use Mix.Project\n\n               def project do\n                 [\n                   app: :rainy_day,\n                   version: \"0.1.0\",\n                   build_path: \"../../_build\",\n                   config_path: \"../../config/config.exs\",\n                   deps_path: \"../../deps\",\n                   lockfile: \"../../mix.lock\",\n                   elixir: \"~> 1.7\",\n                   elixirc_paths: elixirc_paths(Mix.env()),\n                   start_permanent: Mix.env() == :prod,\n                   aliases: aliases(),\n                   deps: deps()\n                 ]\n               end\n\n               # Configuration for the OTP application.\n               #\n               # Type `mix help compile.app` for more information.\n               def application do\n                 [\n                   mod: {RainyDay.Application, []},\n                   extra_applications: [:logger, :runtime_tools]\n                 ]\n               end\n\n               # Specifies which paths to compile per environment.\n               defp elixirc_paths(:test), do: [\"lib\", \"test/support\"]\n               defp elixirc_paths(_), do: [\"lib\"]\n\n               # Specifies your project dependencies.\n               #\n               # Type `mix help deps` for examples and options.\n               defp deps do\n                 [\n                   {:bcrypt_elixir, \"~> 2.0\"},\n                   {:phoenix_pubsub, \"~> 2.0-dev\", github: \"phoenixframework/phoenix_pubsub\"},\n                   {:ecto_sql, \"~> 3.10\"},\n                   {:postgrex, \">= 0.0.0\"},\n                   {:jason, \"~> 1.0\"}\n                 ]\n               end\n\n               # Aliases are shortcuts or tasks specific to the current project.\n               # For example, to create, migrate and run the seeds file at once:\n               #\n               #     $ mix ecto.setup\n               #\n               # See the documentation for `Mix` for more info on aliases.\n               defp aliases do\n                 [\n                   \"ecto.setup\": [\"ecto.create\", \"ecto.migrate\", \"run priv/repo/seeds.exs\"],\n                   \"ecto.reset\": [\"ecto.drop\", \"ecto.setup\"],\n                   test: [\"ecto.create --quiet\", \"ecto.migrate\", \"test\"]\n                 ]\n               end\n             end\n             \"\"\"\n    end\n\n    test \"when injected dependency already exists\" do\n      existing_file = \"\"\"\n      defmodule MyApp.MixFile do\n        defp deps do\n          [\n            {:bcrypt_elixir, \"~> 2.0\"},\n            {:ecto_sql, \"~> 2.0\"}\n          ]\n        end\n      end\n      \"\"\"\n\n      inject = ~s|{:bcrypt_elixir, \"~> 2.0\"}|\n\n      assert :already_injected = Injector.mix_dependency_inject(existing_file, inject)\n    end\n\n    test \"when unable to automatically inject\" do\n      existing_file = \"\"\"\n      defmodule MyApp.MixFile do\n      end\n      \"\"\"\n\n      inject = ~s|{:bcrypt_elixir, \"~> 2.0\"}|\n\n      assert {:error, :unable_to_inject} = Injector.mix_dependency_inject(existing_file, inject)\n    end\n  end\n\n  describe \"inject_before_final_end/2\" do\n    test \"injects code when not previously injected\" do\n      existing_code = \"\"\"\n      defmodule MyApp.Router do\n        use MyApp, :router\n      end\n      \"\"\"\n\n      code_to_inject = \"\"\"\n\n        scope \"/\", MyApp do\n          resources \"/companies\", CompanyController\n        end\n      \"\"\"\n\n      assert {:ok, new_code} = Injector.inject_before_final_end(existing_code, code_to_inject)\n\n      assert new_code == \"\"\"\n             defmodule MyApp.Router do\n               use MyApp, :router\n\n               scope \"/\", MyApp do\n                 resources \"/companies\", CompanyController\n               end\n             end\n             \"\"\"\n    end\n\n    test \"returns :already_injected when code has been injected\" do\n      existing_code = \"\"\"\n      defmodule MyApp.Router do\n        use MyApp, :router\n\n        scope \"/\", MyApp do\n          resources \"/companies\", CompanyController\n        end\n      end\n      \"\"\"\n\n      code_to_inject = \"\"\"\n\n        scope \"/\", MyApp do\n          resources \"/companies\", CompanyController\n        end\n      \"\"\"\n\n      assert :already_injected = Injector.inject_before_final_end(existing_code, code_to_inject)\n    end\n  end\n\n  describe \"inject_unless_contains/3\" do\n    test \"injects when code doesn't already contain code_to_inject\" do\n      existing_code = \"\"\"\n      <html>\n        <body>\n          <h1>My App</h1>\n        </body>\n      </html>\n      \"\"\"\n\n      code_to_inject = ~s|<.user_menu current_user={@current_user} />|\n\n      assert {:ok, new_code} =\n               Injector.inject_unless_contains(\n                 existing_code,\n                 code_to_inject,\n                 &String.replace(&1, \"<body>\", \"<body>\\n    #{&2}\")\n               )\n\n      assert new_code == \"\"\"\n             <html>\n               <body>\n                 <.user_menu current_user={@current_user} />\n                 <h1>My App</h1>\n               </body>\n             </html>\n             \"\"\"\n    end\n\n    test \"returns :already_injected when the existing code already contains code_to_inject\" do\n      existing_code = \"\"\"\n      <html>\n        <body>\n          <nav>\n            <.user_menu current_user={@current_user} />\n          </nav>\n          <h1>My App</h1>\n        </body>\n      </html>\n      \"\"\"\n\n      code_to_inject = ~s|<.user_menu current_user={@current_user} />|\n\n      assert :already_injected =\n               Injector.inject_unless_contains(\n                 existing_code,\n                 code_to_inject,\n                 &String.replace(&1, \"<body>\", \"<body>\\n    #{&2}\")\n               )\n    end\n\n    test \"returns {:error, :unable_to_inject} when no change is made\" do\n      existing_code = \"\"\n\n      code_to_inject = ~s|<.user_menu current_user={@current_user} />|\n\n      assert {:error, :unable_to_inject} =\n               Injector.inject_unless_contains(\n                 existing_code,\n                 code_to_inject,\n                 &String.replace(&1, \"<body>\", \"<body>\\n    #{&2}\")\n               )\n    end\n  end\n\n  describe \"test_config_inject/2\" do\n    test \"injects after \\\"use Mix.Config\\\" when hashing_library is bcrypt\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      use Mix.Config\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               use Mix.Config\n\n               # Only in tests, remove the complexity from the password hashing algorithm\n               config :bcrypt_elixir, :log_rounds, 1\n               \"\"\"\n    end\n\n    test \"injects after \\\"use Mix.Config\\\" when hashing_library is pbkdf2\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"pbkdf2\")\n\n      input = \"\"\"\n      use Mix.Config\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               use Mix.Config\n\n               # Only in tests, remove the complexity from the password hashing algorithm\n               config :pbkdf2_elixir, :rounds, 1\n               \"\"\"\n    end\n\n    test \"injects after \\\"use Mix.Config\\\" when hashing_library is argon2\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"argon2\")\n\n      input = \"\"\"\n      use Mix.Config\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               use Mix.Config\n\n               # Only in tests, remove the complexity from the password hashing algorithm\n               config :argon2_elixir, t_cost: 1, m_cost: 8\n               \"\"\"\n    end\n\n    test \"injects after \\\"use Mix.Config\\\" when there is existing content\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      use Mix.Config\n\n      # Print only warnings and errors during test\n      config :logger, level: :warning\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               use Mix.Config\n\n               # Only in tests, remove the complexity from the password hashing algorithm\n               config :bcrypt_elixir, :log_rounds, 1\n\n               # Print only warnings and errors during test\n               config :logger, level: :warning\n               \"\"\"\n    end\n\n    test \"injects after \\\"import Config\\\" when there is existing content\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      import Config\n\n      # Print only warnings and errors during test\n      config :logger, level: :warning\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               import Config\n\n               # Only in tests, remove the complexity from the password hashing algorithm\n               config :bcrypt_elixir, :log_rounds, 1\n\n               # Print only warnings and errors during test\n               config :logger, level: :warning\n               \"\"\"\n    end\n\n    test \"injects when there are windows line endings\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      import Config\\r\n      \\r\n      # Print only warnings and errors during test\\r\n      config :logger, level: :warning\\r\n      \"\"\"\n\n      {:ok, injected} = Injector.test_config_inject(input, hashing_library)\n\n      assert injected ==\n               \"\"\"\n               import Config\\r\n               \\r\n               # Only in tests, remove the complexity from the password hashing algorithm\\r\n               config :bcrypt_elixir, :log_rounds, 1\\r\n               \\r\n               # Print only warnings and errors during test\\r\n               config :logger, level: :warning\\r\n               \"\"\"\n    end\n\n    test \"returns :already_injected when config is already found in file\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      import Config\n\n      # Print only warnings and errors during test\n      config :logger, level: :warning\n\n      # Only in tests, remove the complexity from the password hashing algorithm\n      config :bcrypt_elixir, :log_rounds, 1\n\n      \"\"\"\n\n      assert :already_injected = Injector.test_config_inject(input, hashing_library)\n    end\n\n    test \"returns :already_injected when config is already found when using windows line endings\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\"\n      import Config\\r\n      \\r\n      # Print only warnings and errors during test\\r\n      config :logger, level: :warning\\r\n      \\r\n      # Only in tests, remove the complexity from the password hashing algorithm\\r\n      config :bcrypt_elixir, :log_rounds, 1\\r\n      \\r\n      \"\"\"\n\n      assert :already_injected = Injector.test_config_inject(input, hashing_library)\n    end\n\n    test \"returns {:error, :unable_to_inject} when file doesn't confine \\\"import Config\\\" or \\\"use Mix.Config\\\"\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      input = \"\"\n\n      assert {:error, :unable_to_inject} = Injector.test_config_inject(input, hashing_library)\n    end\n  end\n\n  describe \"test_config_help_text/2\" do\n    test \"returns a string with the expected help text\" do\n      {:ok, hashing_library} = HashingLibrary.build(\"bcrypt\")\n\n      file_path = Path.expand(\"config/test.exs\")\n\n      assert Injector.test_config_help_text(file_path, hashing_library) ==\n               \"\"\"\n               Add the following to config/test.exs:\n\n                   # Only in tests, remove the complexity from the password hashing algorithm\n                   config :bcrypt_elixir, :log_rounds, 1\n               \"\"\"\n    end\n  end\n\n  describe \"router_plug_inject/2\" do\n    test \"injects after :put_secure_browser_headers\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      input = \"\"\"\n      defmodule DemoWeb.Router do\n        use DemoWeb, :router\n\n        pipeline :browser do\n          plug :accepts, [\"html\"]\n          plug :fetch_session\n          plug :fetch_flash\n          plug :protect_from_forgery\n          plug :put_secure_browser_headers\n        end\n      end\n      \"\"\"\n\n      {:ok, injected} = Injector.router_plug_inject(input, binding)\n\n      assert injected ==\n               \"\"\"\n               defmodule DemoWeb.Router do\n                 use DemoWeb, :router\n\n                 pipeline :browser do\n                   plug :accepts, [\"html\"]\n                   plug :fetch_session\n                   plug :fetch_flash\n                   plug :protect_from_forgery\n                   plug :put_secure_browser_headers\n                   plug :fetch_current_scope_for_user\n                 end\n               end\n               \"\"\"\n    end\n\n    test \"injects after :put_secure_browser_headers even when it has additional options\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      input = \"\"\"\n      defmodule DemoWeb.Router do\n        use DemoWeb, :router\n\n        pipeline :browser do\n          plug :accepts, [\"html\"]\n          plug :fetch_session\n          plug :fetch_flash\n          plug :protect_from_forgery\n          plug :put_secure_browser_headers, %{\"content-security-policy\" => @csp}\n        end\n      end\n      \"\"\"\n\n      {:ok, injected} = Injector.router_plug_inject(input, binding)\n\n      assert injected ==\n               \"\"\"\n               defmodule DemoWeb.Router do\n                 use DemoWeb, :router\n\n                 pipeline :browser do\n                   plug :accepts, [\"html\"]\n                   plug :fetch_session\n                   plug :fetch_flash\n                   plug :protect_from_forgery\n                   plug :put_secure_browser_headers, %{\"content-security-policy\" => @csp}\n                   plug :fetch_current_scope_for_user\n                 end\n               end\n               \"\"\"\n    end\n\n    test \"respects windows line endings\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      input = \"\"\"\n      defmodule DemoWeb.Router do\\r\n        use DemoWeb, :router\\r\n      \\r\n        pipeline :browser do\\r\n          plug :accepts, [\"html\"]\\r\n          plug :fetch_session\\r\n          plug :fetch_flash\\r\n          plug :protect_from_forgery\\r\n          plug :put_secure_browser_headers\\r\n        end\\r\n      end\\r\n      \"\"\"\n\n      {:ok, injected} = Injector.router_plug_inject(input, binding)\n\n      assert injected ==\n               \"\"\"\n               defmodule DemoWeb.Router do\\r\n                 use DemoWeb, :router\\r\n               \\r\n                 pipeline :browser do\\r\n                   plug :accepts, [\"html\"]\\r\n                   plug :fetch_session\\r\n                   plug :fetch_flash\\r\n                   plug :protect_from_forgery\\r\n                   plug :put_secure_browser_headers\\r\n                   plug :fetch_current_scope_for_user\\r\n                 end\\r\n               end\\r\n               \"\"\"\n    end\n\n    test \"errors when :put_secure_browser_headers_is_missing\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      input = \"\"\"\n      defmodule DemoWeb.Router do\n        use DemoWeb, :router\n\n        pipeline :browser do\n          plug :accepts, [\"html\"]\n          plug :fetch_session\n          plug :fetch_flash\n          plug :protect_from_forgery\n        end\n      end\n      \"\"\"\n\n      assert {:error, :unable_to_inject} = Injector.router_plug_inject(input, binding)\n    end\n  end\n\n  describe \"router_plug_help_text/2\" do\n    test \"returns a string with the expected help text\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      file_path = Path.expand(\"foo.ex\")\n\n      assert Injector.router_plug_help_text(file_path, binding) ==\n               \"\"\"\n               Add the :fetch_current_scope_for_user plug to the :browser pipeline in foo.ex:\n\n                   pipeline :browser do\n                     ...\n                     plug :put_secure_browser_headers\n                     plug :fetch_current_scope_for_user\n                   end\n               \"\"\"\n    end\n\n    test \"adheres to the --assign-key\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      context = Context.new(\"Accounts\", schema, [])\n      binding = [schema: schema, context: context, scope_config: %{scope: %{assign_key: :current_user_scope}}]\n\n      file_path = Path.expand(\"foo.ex\")\n\n      assert Injector.router_plug_help_text(file_path, binding) ==\n               \"\"\"\n               Add the :fetch_current_user_scope_for_user plug to the :browser pipeline in foo.ex:\n\n                   pipeline :browser do\n                     ...\n                     plug :put_secure_browser_headers\n                     plug :fetch_current_user_scope_for_user\n                   end\n               \"\"\"\n    end\n  end\n\n  describe \"app_layout_menu_inject/2\" do\n    test \"injects user menu at the bottom of nav section when it exists\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      template = \"\"\"\n      <!DOCTYPE html>\n      <html lang=\"en\">\n        <head>\n          <title>Demo · Phoenix Framework</title>\n        </head>\n        <body>\n          <header>\n            <section class=\"container\">\n              <nav>\n                <ul>\n                  <li><a href=\"https://hexdocs.pm/phoenix/overview.html\">Get Started</a></li>\n                  <%= if function_exported?(Routes, :live_dashboard_path, 2) do %>\n                    <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>\n                  <% end %>\n                </ul>\n              </nav>\n            </section>\n          </header>\n        </body>\n      </html>\n      \"\"\"\n\n      {:ok, template_str} = Injector.app_layout_menu_inject(binding, template)\n\n      assert template_str ==\n               \"\"\"\n               <!DOCTYPE html>\n               <html lang=\"en\">\n                 <head>\n                   <title>Demo · Phoenix Framework</title>\n                 </head>\n                 <body>\n                   <header>\n                     <section class=\"container\">\n                       <nav>\n                         <ul>\n                           <li><a href=\"https://hexdocs.pm/phoenix/overview.html\">Get Started</a></li>\n                           <%= if function_exported?(Routes, :live_dashboard_path, 2) do %>\n                             <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>\n                           <% end %>\n                         </ul>\n                         <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\n                           <%= if @current_scope do %>\n                             <li>\n                               {@current_scope.user.email}\n                             </li>\n                             <li>\n                               <.link href={~p\"/users/settings\"}>Settings</.link>\n                             </li>\n                             <li>\n                               <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\n                             </li>\n                           <% else %>\n                             <li>\n                               <.link href={~p\"/users/register\"}>Register</.link>\n                             </li>\n                             <li>\n                               <.link href={~p\"/users/log-in\"}>Log in</.link>\n                             </li>\n                           <% end %>\n                         </ul>\n                       </nav>\n                     </section>\n                   </header>\n                 </body>\n               </html>\n               \"\"\"\n    end\n\n    test \"injects user menu at the bottom of nav section when it exists with windows line endings\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      template = \"\"\"\n      <!DOCTYPE html>\\r\n      <html lang=\"en\">\\r\n        <head>\\r\n          <title>Demo · Phoenix Framework</title>\\r\n        </head>\\r\n        <body>\\r\n          <header>\\r\n            <section class=\"container\">\\r\n              <nav>\\r\n                <ul>\\r\n                  <li><a href=\"https://hexdocs.pm/phoenix/overview.html\">Get Started</a></li>\\r\n                  <%= if function_exported?(Routes, :live_dashboard_path, 2) do %>\\r\n                    <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>\\r\n                  <% end %>\\r\n                </ul>\\r\n              </nav>\\r\n            </section>\\r\n          </header>\\r\n        </body>\\r\n      </html>\\r\n      \"\"\"\n\n      {:ok, template_str} = Injector.app_layout_menu_inject(binding, template)\n\n      assert template_str ==\n               \"\"\"\n               <!DOCTYPE html>\\r\n               <html lang=\"en\">\\r\n                 <head>\\r\n                   <title>Demo · Phoenix Framework</title>\\r\n                 </head>\\r\n                 <body>\\r\n                   <header>\\r\n                     <section class=\"container\">\\r\n                       <nav>\\r\n                         <ul>\\r\n                           <li><a href=\"https://hexdocs.pm/phoenix/overview.html\">Get Started</a></li>\\r\n                           <%= if function_exported?(Routes, :live_dashboard_path, 2) do %>\\r\n                             <li><.link href={Routes.live_dashboard_path(@conn, :home)}>LiveDashboard</.link></li>\\r\n                           <% end %>\\r\n                         </ul>\\r\n                         <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\\r\n                           <%= if @current_scope do %>\\r\n                             <li>\\r\n                               {@current_scope.user.email}\\r\n                             </li>\\r\n                             <li>\\r\n                               <.link href={~p\"/users/settings\"}>Settings</.link>\\r\n                             </li>\\r\n                             <li>\\r\n                               <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\\r\n                             </li>\\r\n                           <% else %>\\r\n                             <li>\\r\n                               <.link href={~p\"/users/register\"}>Register</.link>\\r\n                             </li>\\r\n                             <li>\\r\n                               <.link href={~p\"/users/log-in\"}>Log in</.link>\\r\n                             </li>\\r\n                           <% end %>\\r\n                         </ul>\\r\n                       </nav>\\r\n                     </section>\\r\n                   </header>\\r\n                 </body>\\r\n               </html>\\r\n               \"\"\"\n    end\n\n    test \"injects render user_menu after the opening body tag\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      template = \"\"\"\n      <!DOCTYPE html>\n      <html lang=\"en\">\n        <head>\n          <title>Demo · Phoenix Framework</title>\n        </head>\n        <body>\n          <main class=\"container\">\n            <p class=\"alert alert-info\" role=\"alert\">{Phoenix.Flash.get(@conn, :info)}</p>\n            <p class=\"alert alert-danger\" role=\"alert\">{Phoenix.Flash.get(@conn, :error)}</p>\n            {@inner_content}\n          </main>\n        </body>\n      </html>\n      \"\"\"\n\n      {:ok, template_str} = Injector.app_layout_menu_inject(binding, template)\n\n      assert template_str ==\n               \"\"\"\n               <!DOCTYPE html>\n               <html lang=\"en\">\n                 <head>\n                   <title>Demo · Phoenix Framework</title>\n                 </head>\n                 <body>\n                   <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\n                     <%= if @current_scope do %>\n                       <li>\n                         {@current_scope.user.email}\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/settings\"}>Settings</.link>\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\n                       </li>\n                     <% else %>\n                       <li>\n                         <.link href={~p\"/users/register\"}>Register</.link>\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/log-in\"}>Log in</.link>\n                       </li>\n                     <% end %>\n                   </ul>\n                   <main class=\"container\">\n                     <p class=\"alert alert-info\" role=\"alert\">{Phoenix.Flash.get(@conn, :info)}</p>\n                     <p class=\"alert alert-danger\" role=\"alert\">{Phoenix.Flash.get(@conn, :error)}</p>\n                     {@inner_content}\n                   </main>\n                 </body>\n               </html>\n               \"\"\"\n    end\n\n    test \"works with windows line endings\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n\n      template = \"\"\"\n      <!DOCTYPE html>\\r\n      <html lang=\"en\">\\r\n        <head>\\r\n          <title>Demo · Phoenix Framework</title>\\r\n        </head>\\r\n        <body>\\r\n          <main class=\"container\">\\r\n            <p class=\"alert alert-info\" role=\"alert\">{Phoenix.Flash.get(@conn, :info)}</p>\\r\n            <p class=\"alert alert-danger\" role=\"alert\">{Phoenix.Flash.get(@conn, :error)}</p>\\r\n            {@inner_content}\\r\n          </main>\\r\n        </body>\\r\n      </html>\\r\n      \"\"\"\n\n      {:ok, template_str} = Injector.app_layout_menu_inject(binding, template)\n\n      assert template_str ==\n               \"\"\"\n               <!DOCTYPE html>\\r\n               <html lang=\"en\">\\r\n                 <head>\\r\n                   <title>Demo · Phoenix Framework</title>\\r\n                 </head>\\r\n                 <body>\\r\n                   <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\\r\n                     <%= if @current_scope do %>\\r\n                       <li>\\r\n                         {@current_scope.user.email}\\r\n                       </li>\\r\n                       <li>\\r\n                         <.link href={~p\"/users/settings\"}>Settings</.link>\\r\n                       </li>\\r\n                       <li>\\r\n                         <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\\r\n                       </li>\\r\n                     <% else %>\\r\n                       <li>\\r\n                         <.link href={~p\"/users/register\"}>Register</.link>\\r\n                       </li>\\r\n                       <li>\\r\n                         <.link href={~p\"/users/log-in\"}>Log in</.link>\\r\n                       </li>\\r\n                     <% end %>\\r\n                   </ul>\\r\n                   <main class=\"container\">\\r\n                     <p class=\"alert alert-info\" role=\"alert\">{Phoenix.Flash.get(@conn, :info)}</p>\\r\n                     <p class=\"alert alert-danger\" role=\"alert\">{Phoenix.Flash.get(@conn, :error)}</p>\\r\n                     {@inner_content}\\r\n                   </main>\\r\n                 </body>\\r\n               </html>\\r\n               \"\"\"\n    end\n\n    test \"returns :already_injected when render is already found in file\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n      template = \"\"\"\n      <!DOCTYPE html>\n      <html lang=\"en\">\n        <head>\n          <title>Demo · Phoenix Framework</title>\n        </head>\n        <body>\n          <div class=\"my-header\">\n            <ul>\n              <%= if @current_user do %>\n                <li>{@current_user.email}</li>\n                <li><.link href={~p\"/users/settings\"}>Settings</.link></li>\n                <li><.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link></li>\n              <% else %>\n                <li><.link href={~p\"/users/register\"}>Register</.link></li>\n                <li><.link href={~p\"/users/log-in\"}>Log in</.link></li>\n              <% end %>\n            </ul>\n          </div>\n          <main class=\"container\">\n            <p class=\"alert alert-info\" role=\"alert\">{Phoenix.Flash.get(@conn, :info)}</p>\n            <p class=\"alert alert-danger\" role=\"alert\">{Phoenix.Flash.get(@conn, :error)}</p>\n            {@inner_content}\n          </main>\n        </body>\n      </html>\n      \"\"\"\n\n      assert :already_injected = Injector.app_layout_menu_inject(binding, template)\n    end\n\n    test \"returns {:error, :unable_to_inject} when the body tag isn't found\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n      assert {:error, :unable_to_inject} = Injector.app_layout_menu_inject(binding, \"\")\n    end\n  end\n\n  describe \"app_layout_menu_help_text/2\" do\n    test \"returns a string with the expected help text\" do\n      schema = Schema.new(\"Accounts.User\", \"users\", [], [no_scope: true])\n      binding = [schema: schema, scope_config: %{scope: %{assign_key: :current_scope}}]\n      file_path = Path.expand(\"foo.ex\")\n\n      assert Injector.app_layout_menu_help_text(file_path, binding) =~\n               \"Add the following user menu\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.auth_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.AuthTest do\n  use ExUnit.Case\n\n  @moduletag :mix_phx_new\n  @liveview_option_message \"Do you want to create a LiveView based authentication system?\"\n\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  defp in_tmp_phx_project(test, additional_args \\\\ [], func) do\n    in_tmp(test, fn ->\n      Mix.Tasks.Phx.New.run(~w(my_app --no-install) ++ additional_args)\n\n      in_project(:my_app, \"my_app\", fn _module ->\n        func.()\n      end)\n    end)\n  end\n\n  defp in_tmp_phx_umbrella_project(test, func) do\n    in_tmp(test, fn ->\n      Mix.Tasks.Phx.New.run(~w(my_app --umbrella --no-install))\n\n      File.cd!(\"my_app_umbrella\", fn ->\n        func.()\n      end)\n    end)\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_phx_project(config.test, fn ->\n      assert_raise Mix.Error,\n                   ~r/Expected the context, \"accounts\", to be a valid module name.*phx\\.gen\\.auth/s,\n                   fn ->\n                     Gen.Auth.run(~w(accounts User users))\n                   end\n\n      assert_raise Mix.Error, ~r/Expected the schema, \"user\", to be a valid module name/, fn ->\n        Gen.Auth.run(~w(Accounts user users))\n      end\n\n      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->\n        Gen.Auth.run(~w(User User users))\n      end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate context MyApp because it has the same name as the application/,\n                   fn ->\n                     Gen.Auth.run(~w(MyApp User users))\n                   end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate schema MyApp because it has the same name as the application/,\n                   fn ->\n                     Gen.Auth.run(~w(Accounts MyApp users))\n                   end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Auth.run(~w())\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Auth.run(~w(Accounts))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments.*phx\\.gen\\.auth/s, fn ->\n        Gen.Auth.run(~w(Accounts User users name:string))\n      end\n\n      assert_raise OptionParser.ParseError, ~r/unknown option/i, fn ->\n        Gen.Auth.run(~w(Accounts User users --no-schema))\n      end\n\n      assert_raise Mix.Error, ~r/Unknown value for --hashing-lib/, fn ->\n        Gen.Auth.run(~w(Accounts User users --hashing-lib unknown))\n      end\n\n      assert_raise Mix.Error, ~r/expects a context module name/, fn ->\n        Gen.Auth.run(~w(User users))\n      end\n    end)\n  end\n\n  test \"generates with defaults (Prompt: --no-live)\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"config/config.exs\", fn file ->\n        assert file =~ \"\"\"\n               config :my_app, :scopes,\n                 user: [\n                   default: true,\n                   module: MyApp.Accounts.Scope,\n                   assign_key: :current_scope,\n                   access_path: [:user, :id],\n                   schema_key: :user_id,\n                   schema_type: :id,\n                   schema_table: :users,\n                   test_data_fixture: MyApp.AccountsFixtures,\n                   test_setup_helper: :register_and_log_in_user\n                 ]\n               \"\"\"\n      end)\n\n      assert_file(\"config/test.exs\", fn file ->\n        assert file =~ \"config :bcrypt_elixir, :log_rounds, 1\"\n      end)\n\n      assert_file(\"lib/my_app/accounts.ex\")\n      assert_file(\"lib/my_app/accounts/user.ex\")\n      assert_file(\"lib/my_app/accounts/user_token.ex\")\n\n      assert_file(\"lib/my_app/accounts/scope.ex\", fn file ->\n        assert file =~ \"def for_user(%User{} = user)\"\n        assert file =~ \"def for_user(nil), do: nil\"\n      end)\n\n      assert_file(\"lib/my_app/accounts/user_notifier.ex\", fn file ->\n        assert file =~ \"defmodule MyApp.Accounts.UserNotifier do\"\n        assert file =~ \"import Swoosh.Email\"\n        assert file =~ \"Mailer.deliver(email)\"\n        assert file =~ ~s|from({\"MyApp\", \"contact@example.com\"})|\n        assert file =~ ~s|deliver(user.email, \"Confirmation instructions\",|\n        assert file =~ ~s|deliver(user.email, \"Log in instructions\",|\n        assert file =~ ~s|deliver(user.email, \"Update email instructions\",|\n      end)\n\n      assert_file(\"test/my_app/accounts_test.exs\")\n      assert_file(\"test/support/fixtures/accounts_fixtures.ex\")\n      assert_file(\"lib/my_app_web/user_auth.ex\")\n      assert_file(\"test/my_app_web/user_auth_test.exs\")\n      assert_file(\"lib/my_app_web/controllers/user_registration_controller.ex\")\n      assert_file(\"lib/my_app_web/controllers/user_registration_html.ex\")\n      assert_file(\"test/my_app_web/controllers/user_registration_controller_test.exs\")\n      assert_file(\"lib/my_app_web/controllers/user_session_controller.ex\")\n      assert_file(\"lib/my_app_web/controllers/user_session_html/new.html.heex\")\n      assert_file(\"test/my_app_web/controllers/user_session_controller_test.exs\")\n      assert_file(\"lib/my_app_web/controllers/user_session_html.ex\")\n      assert_file(\"lib/my_app_web/controllers/user_settings_controller.ex\")\n      assert_file(\"lib/my_app_web/controllers/user_settings_html/edit.html.heex\")\n      assert_file(\"lib/my_app_web/controllers/user_settings_html.ex\")\n      assert_file(\"test/my_app_web/controllers/user_settings_controller_test.exs\")\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:users) do\"\n        assert file =~ \"create table(:users_tokens) do\"\n      end)\n\n      assert_file(\"mix.exs\", fn file ->\n        assert file =~ ~s|{:bcrypt_elixir, \"~> 3.0\"},|\n      end)\n\n      assert_file(\"lib/my_app_web/router.ex\", fn file ->\n        assert file =~ \"import MyAppWeb.UserAuth\"\n        assert file =~ \"plug :fetch_current_scope_for_user\"\n\n        assert file =~ \"\"\"\n                 ## Authentication routes\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :redirect_if_user_is_authenticated]\n\n                   get \"/users/register\", UserRegistrationController, :new\n                   post \"/users/register\", UserRegistrationController, :create\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :require_authenticated_user]\n\n                   get \"/users/settings\", UserSettingsController, :edit\n                   put \"/users/settings\", UserSettingsController, :update\n                   get \"/users/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser]\n\n                   get \"/users/log-in\", UserSessionController, :new\n                   get \"/users/log-in/:token\", UserSessionController, :confirm\n                   post \"/users/log-in\", UserSessionController, :create\n                   delete \"/users/log-out\", UserSessionController, :delete\n                 end\n               \"\"\"\n      end)\n\n      assert_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/settings\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-out\"}.*method=\"delete\".*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/register\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-in\"}.*>|s\n      end)\n\n      assert_file(\"test/support/conn_case.ex\", fn file ->\n        assert file =~ \"def register_and_log_in_user(%{conn: conn} = context)\"\n        assert file =~ \"def log_in_user(conn, user, opts \\\\\\\\ [])\"\n      end)\n\n      assert_received {:mix_shell, :info,\n                       [\"Unable to find the \\\"MyApp.Mailer\\\"\" <> mailer_notice]}\n\n      assert mailer_notice =~ ~s(A mailer module like the following is expected to be defined)\n      assert mailer_notice =~ ~s(in your application in order to send emails.)\n      assert mailer_notice =~ ~s(defmodule MyApp.Mailer do)\n      assert mailer_notice =~ ~s(use Swoosh.Mailer, otp_app: :my_app)\n      assert mailer_notice =~ ~s(def deps do)\n      assert mailer_notice =~ ~s(https://hexdocs.pm/swoosh)\n    end)\n  end\n\n  test \"generates with defaults (Prompt: --live)\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, true})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"config/config.exs\", fn file ->\n        assert file =~ \"\"\"\n               config :my_app, :scopes,\n                 user: [\n                   default: true,\n                   module: MyApp.Accounts.Scope,\n                   assign_key: :current_scope,\n                   access_path: [:user, :id],\n                   schema_key: :user_id,\n                   schema_type: :id,\n                   schema_table: :users,\n                   test_data_fixture: MyApp.AccountsFixtures,\n                   test_setup_helper: :register_and_log_in_user\n                 ]\n               \"\"\"\n      end)\n\n      assert_file(\"config/test.exs\", fn file ->\n        assert file =~ \"config :bcrypt_elixir, :log_rounds, 1\"\n      end)\n\n      assert_file(\"lib/my_app/accounts.ex\")\n      assert_file(\"lib/my_app/accounts/user.ex\")\n      assert_file(\"lib/my_app/accounts/user_token.ex\")\n\n      assert_file(\"lib/my_app/accounts/scope.ex\", fn file ->\n        assert file =~ \"def for_user(%User{} = user)\"\n        assert file =~ \"def for_user(nil), do: nil\"\n      end)\n\n      assert_file(\"lib/my_app/accounts/user_notifier.ex\", fn file ->\n        assert file =~ \"defmodule MyApp.Accounts.UserNotifier do\"\n        assert file =~ \"import Swoosh.Email\"\n        assert file =~ \"Mailer.deliver(email)\"\n        assert file =~ ~s|from({\"MyApp\", \"contact@example.com\"})|\n        assert file =~ ~s|deliver(user.email, \"Confirmation instructions\",|\n        assert file =~ ~s|deliver(user.email, \"Log in instructions\",|\n        assert file =~ ~s|deliver(user.email, \"Update email instructions\",|\n      end)\n\n      assert_file(\"lib/my_app_web/live/user_live/registration.ex\")\n      assert_file(\"test/my_app_web/live/user_live/registration_test.exs\")\n      assert_file(\"lib/my_app_web/live/user_live/login.ex\")\n      assert_file(\"test/my_app_web/live/user_live/login_test.exs\")\n      assert_file(\"lib/my_app_web/live/user_live/settings.ex\")\n      assert_file(\"test/my_app_web/live/user_live/settings_test.exs\")\n      assert_file(\"lib/my_app_web/live/user_live/confirmation.ex\")\n      assert_file(\"test/my_app_web/live/user_live/confirmation_test.exs\")\n\n      assert_file(\"lib/my_app_web/user_auth.ex\")\n      assert_file(\"test/my_app_web/user_auth_test.exs\")\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:users) do\"\n        assert file =~ \"create table(:users_tokens) do\"\n      end)\n\n      assert_file(\"mix.exs\", fn file ->\n        assert file =~ ~s|{:bcrypt_elixir, \"~> 3.0\"},|\n      end)\n\n      assert_file(\"lib/my_app_web/router.ex\", fn file ->\n        assert file =~ \"import MyAppWeb.UserAuth\"\n        assert file =~ \"plug :fetch_current_scope_for_user\"\n\n        assert file =~ \"\"\"\n                 ## Authentication routes\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :require_authenticated_user]\n\n                   live_session :require_authenticated_user,\n                     on_mount: [{MyAppWeb.UserAuth, :require_authenticated}] do\n                     live \"/users/settings\", UserLive.Settings, :edit\n                     live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n                   end\n\n                   post \"/users/update-password\", UserSessionController, :update_password\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser]\n\n                   live_session :current_user,\n                     on_mount: [{MyAppWeb.UserAuth, :mount_current_scope}] do\n                     live \"/users/register\", UserLive.Registration, :new\n                     live \"/users/log-in\", UserLive.Login, :new\n                     live \"/users/log-in/:token\", UserLive.Confirmation, :new\n                   end\n\n                   post \"/users/log-in\", UserSessionController, :create\n                   delete \"/users/log-out\", UserSessionController, :delete\n                 end\n               \"\"\"\n      end)\n\n      assert_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/users/settings\"}.*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/users/log-out\"}.*method=\"delete\".*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/users/register\"}.*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/users/log-in\"}.*>|s\n      end)\n\n      assert_file(\"test/support/conn_case.ex\", fn file ->\n        assert file =~ \"def register_and_log_in_user(%{conn: conn} = context)\"\n        assert file =~ \"def log_in_user(conn, user, opts \\\\\\\\ [])\"\n      end)\n\n      assert_received {:mix_shell, :info,\n                       [\"Unable to find the \\\"MyApp.Mailer\\\"\" <> mailer_notice]}\n\n      assert mailer_notice =~ ~s(A mailer module like the following is expected to be defined)\n      assert mailer_notice =~ ~s(in your application in order to send emails.)\n      assert mailer_notice =~ ~s(defmodule MyApp.Mailer do)\n      assert mailer_notice =~ ~s(use Swoosh.Mailer, otp_app: :my_app)\n      assert mailer_notice =~ ~s(def deps do)\n      assert mailer_notice =~ ~s(https://hexdocs.pm/swoosh)\n    end)\n  end\n\n  test \"works with apps generated with --live\", config do\n    in_tmp_phx_project(config.test, ~w(--live), fn ->\n      Gen.Auth.run(\n        ~w(Accounts User users --live --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/settings\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-out\"}.*method=\"delete\".*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/register\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-in\"}.*>|s\n      end)\n\n      assert_file(\"lib/my_app_web/router.ex\", fn file ->\n        assert file =~ \"import MyAppWeb.UserAuth\"\n        assert file =~ \"plug :fetch_current_scope_for_user\"\n\n        assert file =~ \"\"\"\n                 ## Authentication routes\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :require_authenticated_user]\n\n                   live_session :require_authenticated_user,\n                     on_mount: [{MyAppWeb.UserAuth, :require_authenticated}] do\n                     live \"/users/settings\", UserLive.Settings, :edit\n                     live \"/users/settings/confirm-email/:token\", UserLive.Settings, :confirm_email\n                   end\n\n                   post \"/users/update-password\", UserSessionController, :update_password\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser]\n\n                   live_session :current_user,\n                     on_mount: [{MyAppWeb.UserAuth, :mount_current_scope}] do\n                     live \"/users/register\", UserLive.Registration, :new\n                     live \"/users/log-in\", UserLive.Login, :new\n                     live \"/users/log-in/:token\", UserLive.Confirmation, :new\n                   end\n\n                   post \"/users/log-in\", UserSessionController, :create\n                   delete \"/users/log-out\", UserSessionController, :delete\n                 end\n               \"\"\"\n      end)\n    end)\n  end\n\n  test \"works with apps generated with --no-live\", config do\n    in_tmp_phx_project(config.test, ~w(--no-live), fn ->\n      Gen.Auth.run(\n        ~w(Accounts User users --no-live --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/settings\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-out\"}.*method=\"delete\".*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/register\"}.*>|s\n\n        assert file =~\n                 ~r|<.link.*href={~p\"/users/log-in\"}.*>|s\n      end)\n\n      assert_file(\"lib/my_app_web/router.ex\", fn file ->\n        assert file =~ \"import MyAppWeb.UserAuth\"\n        assert file =~ \"plug :fetch_current_scope_for_user\"\n\n        assert file =~ \"\"\"\n                 ## Authentication routes\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :redirect_if_user_is_authenticated]\n\n                   get \"/users/register\", UserRegistrationController, :new\n                   post \"/users/register\", UserRegistrationController, :create\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser, :require_authenticated_user]\n\n                   get \"/users/settings\", UserSettingsController, :edit\n                   put \"/users/settings\", UserSettingsController, :update\n                   get \"/users/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n                 end\n\n                 scope \"/\", MyAppWeb do\n                   pipe_through [:browser]\n\n                   get \"/users/log-in\", UserSessionController, :new\n                   get \"/users/log-in/:token\", UserSessionController, :confirm\n                   post \"/users/log-in\", UserSessionController, :create\n                   delete \"/users/log-out\", UserSessionController, :delete\n                 end\n               \"\"\"\n      end)\n    end)\n  end\n\n  test \"generates with --web option\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --web warehouse --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"lib/my_app/accounts.ex\")\n      assert_file(\"lib/my_app/accounts/user.ex\")\n      assert_file(\"lib/my_app/accounts/user_token.ex\")\n      assert_file(\"lib/my_app/accounts/user_notifier.ex\")\n      assert_file(\"test/my_app/accounts_test.exs\")\n\n      assert_file(\"test/support/fixtures/accounts_fixtures.ex\", fn file ->\n        assert file =~ ~s|def valid_user_attributes(attrs \\\\\\\\ %{}) do|\n      end)\n\n      assert_file(\"lib/my_app_web/warehouse/user_auth.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserAuth do\"\n      end)\n\n      assert_file(\"test/my_app_web/warehouse/user_auth_test.exs\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserAuthTest do\"\n      end)\n\n      assert_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn file ->\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/warehouse/users/settings\"}.*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/warehouse/users/log-out\"}.*method=\"delete\".*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/warehouse/users/register\"}.*>|s\n\n        assert file =~\n                 ~r|<\\.link.*href={~p\"/warehouse/users/log-in\"}.*>|s\n      end)\n\n      assert_file(\n        \"lib/my_app_web/controllers/warehouse/user_registration_controller.ex\",\n        fn file ->\n          assert file =~ \"defmodule MyAppWeb.Warehouse.UserRegistrationController do\"\n        end\n      )\n\n      assert_file(\"lib/my_app_web/controllers/warehouse/user_registration_html.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserRegistrationHTML do\"\n      end)\n\n      assert_file(\n        \"test/my_app_web/controllers/warehouse/user_registration_controller_test.exs\",\n        fn file ->\n          assert file =~ \"defmodule MyAppWeb.Warehouse.UserRegistrationControllerTest do\"\n        end\n      )\n\n      assert_file(\"lib/my_app_web/controllers/warehouse/user_session_controller.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserSessionController do\"\n      end)\n\n      assert_file(\n        \"lib/my_app_web/controllers/warehouse/user_session_html/new.html.heex\",\n        fn file ->\n          assert file =~\n                   ~S|<.form :let={f} for={@form} as={:user} id=\"login_form_magic\" action={~p\"/warehouse/users/log-in\"}>|\n\n          assert file =~ \"\"\"\n                   <.form :let={f} for={@form} as={:user} id=\"login_form_password\" action={~p\"/warehouse/users/log-in\"}>\n                 \"\"\"\n\n          assert file =~\n                   ~S|navigate={~p\"/warehouse/users/register\"}|\n        end\n      )\n\n      assert_file(\n        \"test/my_app_web/controllers/warehouse/user_session_controller_test.exs\",\n        fn file ->\n          assert file =~ \"defmodule MyAppWeb.Warehouse.UserSessionControllerTest do\"\n        end\n      )\n\n      assert_file(\"lib/my_app_web/controllers/warehouse/user_session_html.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserSessionHTML do\"\n      end)\n\n      assert_file(\"lib/my_app_web/controllers/warehouse/user_settings_controller.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserSettingsController do\"\n      end)\n\n      assert_file(\n        \"lib/my_app_web/controllers/warehouse/user_settings_html/edit.html.heex\",\n        fn file ->\n          assert file =~\n                   ~S|<.form :let={f} for={@email_changeset} action={~p\"/warehouse/users/settings\"} id=\"update_email\">|\n\n          assert file =~\n                   ~s|<.form :let={f} for={@password_changeset} action={~p\\\"/warehouse/users/settings\\\"} id=\\\"update_password\\\">|\n        end\n      )\n\n      assert_file(\"lib/my_app_web/controllers/warehouse/user_settings_html.ex\", fn file ->\n        assert file =~ \"defmodule MyAppWeb.Warehouse.UserSettingsHTML do\"\n      end)\n\n      assert_file(\n        \"test/my_app_web/controllers/warehouse/user_settings_controller_test.exs\",\n        fn file ->\n          assert file =~ \"defmodule MyAppWeb.Warehouse.UserSettingsControllerTest do\"\n        end\n      )\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:users) do\"\n        assert file =~ \"create table(:users_tokens) do\"\n      end)\n\n      assert_file(\"lib/my_app_web/router.ex\", fn file ->\n        assert file =~ \"import MyAppWeb.Warehouse.UserAuth\"\n        assert file =~ \"plug :fetch_current_scope_for_user\"\n\n        assert file =~ \"\"\"\n                 ## Authentication routes\n\n                 scope \"/warehouse\", MyAppWeb.Warehouse do\n                   pipe_through [:browser, :redirect_if_user_is_authenticated]\n\n                   get \"/users/register\", UserRegistrationController, :new\n                   post \"/users/register\", UserRegistrationController, :create\n                 end\n\n                 scope \"/warehouse\", MyAppWeb.Warehouse do\n                   pipe_through [:browser, :require_authenticated_user]\n\n                   get \"/users/settings\", UserSettingsController, :edit\n                   put \"/users/settings\", UserSettingsController, :update\n                   get \"/users/settings/confirm-email/:token\", UserSettingsController, :confirm_email\n                 end\n\n                 scope \"/warehouse\", MyAppWeb.Warehouse do\n                   pipe_through [:browser]\n\n                   get \"/users/log-in\", UserSessionController, :new\n                   get \"/users/log-in/:token\", UserSessionController, :confirm\n                   post \"/users/log-in\", UserSessionController, :create\n                   delete \"/users/log-out\", UserSessionController, :delete\n                 end\n               \"\"\"\n      end)\n\n      assert_file(\"test/support/conn_case.ex\", fn file ->\n        assert file =~ \"def register_and_log_in_user(%{conn: conn} = context)\"\n        assert file =~ \"def log_in_user(conn, user, opts \\\\\\\\ [])\"\n      end)\n    end)\n  end\n\n  describe \"--database option\" do\n    test \"when the database is postgres\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ ~r/execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\"$/m\n          assert file =~ ~r/add :email, :citext, null: false$/m\n        end)\n\n        assert_file(\"test/my_app_web/user_auth_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase, async: true$/m\n        end)\n\n        assert_file(\n          \"test/my_app_web/controllers/user_registration_controller_test.exs\",\n          fn file ->\n            assert file =~ ~r/use MyAppWeb\\.ConnCase, async: true$/m\n          end\n        )\n\n        assert_file(\"test/my_app_web/controllers/user_session_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase, async: true$/m\n        end)\n\n        assert_file(\"test/my_app_web/controllers/user_settings_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase, async: true$/m\n        end)\n      end)\n    end\n\n    test \"when the database is mysql\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.MyXQL\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          refute file =~ ~r/execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\"$/m\n          assert file =~ ~r/add :email, :string, null: false, size: 160$/m\n        end)\n\n        assert_file(\"test/my_app_web/user_auth_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\n          \"test/my_app_web/controllers/user_registration_controller_test.exs\",\n          fn file ->\n            assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n          end\n        )\n\n        assert_file(\"test/my_app_web/controllers/user_session_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\"test/my_app_web/controllers/user_settings_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n      end)\n    end\n\n    test \"when the database is sqlite3\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.SQLite3\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          refute file =~ ~r/execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\"$/m\n          assert file =~ ~r/add :email, :string, null: false, collate: :nocase$/m\n        end)\n\n        assert_file(\"test/my_app_web/user_auth_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\n          \"test/my_app_web/controllers/user_registration_controller_test.exs\",\n          fn file ->\n            assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n          end\n        )\n\n        assert_file(\"test/my_app_web/controllers/user_session_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\"test/my_app_web/controllers/user_settings_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n      end)\n    end\n\n    test \"when the database is mssql\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.TDS\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          refute file =~ ~r/execute \"CREATE EXTENSION IF NOT EXISTS citext\", \"\"$/m\n          assert file =~ ~r/add :email, :string, null: false, size: 160$/m\n        end)\n\n        assert_file(\"test/my_app_web/user_auth_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\n          \"test/my_app_web/controllers/user_registration_controller_test.exs\",\n          fn file ->\n            assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n          end\n        )\n\n        assert_file(\"test/my_app_web/controllers/user_session_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n\n        assert_file(\"test/my_app_web/controllers/user_settings_controller_test.exs\", fn file ->\n          assert file =~ ~r/use MyAppWeb\\.ConnCase$/m\n        end)\n      end)\n    end\n  end\n\n  test \"allows utc_datetime\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      with_generator_env(:my_app, [timestamp_type: :utc_datetime], fn ->\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ \"timestamps(type: :utc_datetime)\"\n          assert file =~ \"timestamps(type: :utc_datetime, updated_at: false)\"\n        end)\n\n        assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n          assert file =~ \"field :confirmed_at, :utc_datetime\"\n          assert file =~ \"timestamps(type: :utc_datetime)\"\n          assert file =~ \"now = DateTime.utc_now(:second)\"\n        end)\n\n        assert_file(\"lib/my_app/accounts/user_token.ex\", fn file ->\n          assert file =~ \"timestamps(type: :utc_datetime, updated_at: false)\"\n        end)\n\n        assert_file(\"lib/my_app/accounts.ex\", fn file ->\n          assert file =~\n                   \"sudo_mode?(%User{authenticated_at: ts}, minutes) when is_struct(ts, DateTime)\"\n        end)\n      end)\n    end)\n  end\n\n  test \"generates migration with a custom migration module\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      try do\n        Application.put_env(:ecto_sql, :migration_module, MyCustomApp.MigrationModule)\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ \"use MyCustomApp.MigrationModule\"\n        end)\n      after\n        Application.delete_env(:ecto_sql, :migration_module)\n      end\n    end)\n  end\n\n  test \"supports --binary-id option\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --binary-id --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n        assert file =~ \"@primary_key {:id, :binary_id, autogenerate: true}\"\n        assert file =~ \"@foreign_key_type :binary_id\"\n      end)\n\n      assert_file(\"lib/my_app/accounts/user_token.ex\", fn file ->\n        assert file =~ \"@primary_key {:id, :binary_id, autogenerate: true}\"\n        assert file =~ \"@foreign_key_type :binary_id\"\n      end)\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users_auth_tables.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:users, primary_key: false)\"\n        assert file =~ \"create table(:users_tokens, primary_key: false)\"\n        assert file =~ \"add :id, :binary_id, primary_key: true\"\n      end)\n    end)\n  end\n\n  describe \"--hashing-lib option\" do\n    test \"when bcrypt\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --hashing-lib bcrypt --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_file(\"mix.exs\", fn file ->\n          assert file =~ ~s|{:bcrypt_elixir, \"~> 3.0\"}|\n        end)\n\n        assert_file(\"config/test.exs\", fn file ->\n          assert file =~ \"config :bcrypt_elixir, :log_rounds, 1\"\n        end)\n\n        assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n          assert file =~ \"Bcrypt.verify_pass(password, hashed_password)\"\n        end)\n      end)\n    end\n\n    test \"when pbkdf2\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --hashing-lib pbkdf2 --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_file(\"mix.exs\", fn file ->\n          assert file =~ ~s|{:pbkdf2_elixir, \"~> 2.0\"}|\n        end)\n\n        assert_file(\"config/test.exs\", fn file ->\n          assert file =~ \"config :pbkdf2_elixir, :rounds, 1\"\n        end)\n\n        assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n          assert file =~ \"Pbkdf2.verify_pass(password, hashed_password)\"\n        end)\n      end)\n    end\n\n    test \"when argon2\", config do\n      in_tmp_phx_project(config.test, fn ->\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --hashing-lib argon2 --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_file(\"mix.exs\", fn file ->\n          assert file =~ ~s|{:argon2_elixir, \"~> 4.0\"}|\n        end)\n\n        assert_file(\"config/test.exs\", fn file ->\n          assert file =~ \"\"\"\n                 config :argon2_elixir, t_cost: 1, m_cost: 8\n                 \"\"\"\n        end)\n\n        assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n          assert file =~ \"Argon2.verify_pass(password, hashed_password)\"\n        end)\n      end)\n    end\n  end\n\n  test \"with --table option\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --table my_users --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"lib/my_app/accounts/user.ex\", fn file ->\n        assert file =~ ~S|schema \"my_users\" do|\n      end)\n\n      assert_file(\"lib/my_app/accounts/user_token.ex\", fn file ->\n        assert file =~ ~S|schema \"my_users_tokens\" do|\n      end)\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_my_users_auth_tables.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:my_users) do\"\n        assert file =~ \"create table(:my_users_tokens) do\"\n      end)\n    end)\n  end\n\n  describe \"inside umbrella\" do\n    test \"without context_app generators config uses web dir\", config do\n      in_tmp_phx_umbrella_project(config.test, fn ->\n        in_project(:my_app, \"apps/my_app\", fn _module ->\n          with_generator_env(:my_app_web, [context_app: nil], fn ->\n            send(self(), {:mix_shell_input, :yes?, false})\n\n            Gen.Auth.run(\n              ~w(Accounts User users --no-compile),\n              ecto_adapter: Ecto.Adapters.Postgres\n            )\n\n            assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n          end)\n        end)\n\n        assert_file(\"apps/my_app/lib/my_app/accounts.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user_token.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user_notifier.ex\")\n        assert_file(\"apps/my_app/test/my_app/accounts_test.exs\")\n        assert_file(\"apps/my_app/test/support/fixtures/accounts_fixtures.ex\")\n        assert_file(\"apps/my_app/lib/my_app_web/user_auth.ex\")\n        assert_file(\"apps/my_app/test/my_app_web/user_auth_test.exs\")\n\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_registration_controller.ex\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_registration_html.ex\")\n\n        assert_file(\n          \"apps/my_app/test/my_app_web/controllers/user_registration_controller_test.exs\"\n        )\n\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_session_controller.ex\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_session_html/new.html.heex\")\n        assert_file(\"apps/my_app/test/my_app_web/controllers/user_session_controller_test.exs\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_session_html.ex\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_settings_controller.ex\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_settings_html/edit.html.heex\")\n        assert_file(\"apps/my_app/lib/my_app_web/controllers/user_settings_html.ex\")\n        assert_file(\"apps/my_app/test/my_app_web/controllers/user_settings_controller_test.exs\")\n      end)\n    end\n\n    test \"with context_app generators config does not use web dir\", config do\n      in_tmp_phx_umbrella_project(config.test, fn ->\n        in_project(:my_app_web, \"apps/my_app_web\", fn _module ->\n          with_generator_env(:my_app_web, [context_app: :my_app], fn ->\n            send(self(), {:mix_shell_input, :yes?, false})\n\n            Gen.Auth.run(\n              ~w(Accounts User users --no-compile),\n              ecto_adapter: Ecto.Adapters.Postgres\n            )\n\n            assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n          end)\n        end)\n\n        assert_file(\"apps/my_app/lib/my_app/accounts.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user_token.ex\")\n        assert_file(\"apps/my_app/lib/my_app/accounts/user_notifier.ex\")\n        assert_file(\"apps/my_app/test/my_app/accounts_test.exs\")\n        assert_file(\"apps/my_app/test/support/fixtures/accounts_fixtures.ex\")\n        assert_file(\"apps/my_app_web/lib/my_app_web/user_auth.ex\")\n        assert_file(\"apps/my_app_web/test/my_app_web/user_auth_test.exs\")\n\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_registration_controller.ex\")\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_registration_html.ex\")\n\n        assert_file(\n          \"apps/my_app_web/test/my_app_web/controllers/user_registration_controller_test.exs\"\n        )\n\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_session_controller.ex\")\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_session_html/new.html.heex\")\n\n        assert_file(\n          \"apps/my_app_web/test/my_app_web/controllers/user_session_controller_test.exs\"\n        )\n\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_session_html.ex\")\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_settings_controller.ex\")\n\n        assert_file(\n          \"apps/my_app_web/lib/my_app_web/controllers/user_settings_html/edit.html.heex\"\n        )\n\n        assert_file(\"apps/my_app_web/lib/my_app_web/controllers/user_settings_html.ex\")\n\n        assert_file(\n          \"apps/my_app_web/test/my_app_web/controllers/user_settings_controller_test.exs\"\n        )\n      end)\n    end\n\n    test \"raises with false context_app\", config do\n      in_tmp_phx_umbrella_project(config.test, fn ->\n        in_project(:my_app_web, \"apps/my_app_web\", fn _module ->\n          with_generator_env(:my_app_web, [context_app: false], fn ->\n            assert_raise Mix.Error, ~r/no context_app configured/, fn ->\n              Gen.Auth.run(\n                ~w(Accounts User users --no-compile),\n                ecto_adapter: Ecto.Adapters.Postgres\n              )\n            end\n          end)\n        end)\n      end)\n    end\n  end\n\n  describe \"user prompts\" do\n    test \"when unable to inject dependencies in mix.exs\", config do\n      in_tmp_phx_project(config.test, fn ->\n        File.write!(\"mix.exs\", \"\")\n\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_received {:mix_shell, :info,\n                         [\n                           \"\"\"\n\n                           Add your {:bcrypt_elixir, \"~> 3.0\"} dependency to mix.exs:\n\n                               defp deps do\n                                 [\n                                   {:bcrypt_elixir, \"~> 3.0\"},\n                                   ...\n                                 ]\n                               end\n                           \"\"\"\n                         ]}\n      end)\n    end\n\n    test \"when unable to inject authentication import into router.ex\", config do\n      in_tmp_phx_project(config.test, fn ->\n        modify_file(\"lib/my_app_web/router.ex\", fn file ->\n          String.replace(file, \"use MyAppWeb, :router\", \"\")\n        end)\n\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_received {:mix_shell, :info,\n                         [\n                           \"\"\"\n\n                           Add your MyAppWeb.UserAuth import to lib/my_app_web/router.ex:\n\n                               defmodule MyAppWeb.Router do\n                                 use MyAppWeb, :router\n\n                                 # Import authentication plugs\n                                 import MyAppWeb.UserAuth\n\n                                 ...\n                               end\n\n                           \"\"\"\n                         ]}\n      end)\n    end\n\n    test \"when unable to inject plugs into router.ex\", config do\n      in_tmp_phx_project(config.test, fn ->\n        modify_file(\"lib/my_app_web/router.ex\", fn file ->\n          String.replace(file, \"plug :put_secure_browser_headers\\n\", \"\")\n        end)\n\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_received {:mix_shell, :info,\n                         [\n                           \"\"\"\n\n                           Add the :fetch_current_scope_for_user plug to the :browser pipeline in lib/my_app_web/router.ex:\n\n                               pipeline :browser do\n                                 ...\n                                 plug :put_secure_browser_headers\n                                 plug :fetch_current_scope_for_user\n                               end\n\n                           \"\"\"\n                         ]}\n      end)\n    end\n\n    test \"when layout file is not found\", config do\n      in_tmp_phx_project(config.test, fn ->\n        File.rm!(\"lib/my_app_web/components/layouts/root.html.heex\")\n\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        assert_receive {:mix_shell, :error, [error]}\n\n        assert error == \"\"\"\n\n               Unable to find the root layout file to inject user menu items.\n\n               Missing files:\n\n                 * lib/my_app_web/components/layouts/root.html.heex\n\n               Please ensure this phoenix app was not generated with\n               --no-html. If you have changed the name of your root\n               layout file, please add the following code to it where you'd\n               like the user menu items to be rendered.\n\n                   <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\n                     <%= if @current_scope do %>\n                       <li>\n                         {@current_scope.user.email}\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/settings\"}>Settings</.link>\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\n                       </li>\n                     <% else %>\n                       <li>\n                         <.link href={~p\"/users/register\"}>Register</.link>\n                       </li>\n                       <li>\n                         <.link href={~p\"/users/log-in\"}>Log in</.link>\n                       </li>\n                     <% end %>\n                   </ul>\n               \"\"\"\n      end)\n    end\n\n    test \"when user menu can't be injected into layout\", config do\n      in_tmp_phx_project(config.test, fn ->\n        modify_file(\"lib/my_app_web/components/layouts/root.html.heex\", fn _file ->\n          \"\"\n        end)\n\n        send(self(), {:mix_shell_input, :yes?, false})\n\n        Gen.Auth.run(\n          ~w(Accounts User users --no-compile),\n          ecto_adapter: Ecto.Adapters.Postgres\n        )\n\n        assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n        help_text = \"\"\"\n\n        Add the following user menu items to your lib/my_app_web/components/layouts/root.html.heex layout file:\n\n            <ul class=\"menu menu-horizontal w-full relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end\">\n              <%= if @current_scope do %>\n                <li>\n                  {@current_scope.user.email}\n                </li>\n                <li>\n                  <.link href={~p\"/users/settings\"}>Settings</.link>\n                </li>\n                <li>\n                  <.link href={~p\"/users/log-out\"} method=\"delete\">Log out</.link>\n                </li>\n              <% else %>\n                <li>\n                  <.link href={~p\"/users/register\"}>Register</.link>\n                </li>\n                <li>\n                  <.link href={~p\"/users/log-in\"}>Log in</.link>\n                </li>\n              <% end %>\n            </ul>\n\n        \"\"\"\n\n        assert_received {:mix_shell, :info, [^help_text]}\n      end)\n    end\n\n    test \"when default scope already exists\", config do\n      in_tmp_phx_project(config.test, fn ->\n        with_scope_env(\n          :my_app,\n          [\n            user: [\n              default: true,\n              module: MyApp.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:user, :id],\n              schema_key: :user_id,\n              schema_type: :id,\n              schema_table: :users\n            ]\n          ],\n          fn ->\n            send(self(), {:mix_shell_input, :yes?, true})\n\n            Gen.Auth.run(\n              ~w(Accounts User users --no-compile --live),\n              ecto_adapter: Ecto.Adapters.Postgres\n            )\n\n            help_text = \"\"\"\n            Your application configuration already contains a default scope: :user.\n\n            phx.gen.auth will create a new accounts_user scope.\n\n            Note that if you run `phx.gen.live` multiple times, the generated assign key for\n            the generated scopes can conflict with each other. You can pass `--assign-key` to customize\n            the assign key for the generated scope.\n\n            Do you want to proceed with the generation?\\\n            \"\"\"\n\n            assert_received {:mix_shell, :yes?, [question]}\n            assert question == help_text\n          end\n        )\n      end)\n    end\n\n    test \"when scope name cannot be generated\", config do\n      in_tmp_phx_project(config.test, fn ->\n        with_scope_env(\n          :my_app,\n          [\n            user: [\n              default: true,\n              module: MyApp.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:user, :id],\n              schema_key: :user_id,\n              schema_type: :id,\n              schema_table: :users\n            ],\n            accounts_user: [\n              default: false,\n              module: MyApp.Accounts.Scope,\n              access_path: []\n            ],\n            my_app_accounts_user: [\n              default: false,\n              module: MyApp.Accounts.Scope,\n              access_path: []\n            ]\n          ],\n          fn ->\n            send(self(), {:mix_shell_input, :yes?, true})\n\n            assert_raise Mix.Error, ~r/Could not generate a scope name for user!/, fn ->\n              Gen.Auth.run(\n                ~w(Accounts User users --no-compile --live),\n                ecto_adapter: Ecto.Adapters.Postgres\n              )\n            end\n          end\n        )\n      end)\n    end\n\n    test \"when given scope already exists\", config do\n      in_tmp_phx_project(config.test, fn ->\n        with_scope_env(\n          :my_app,\n          [\n            user: [\n              default: true,\n              module: MyApp.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:user, :id],\n              schema_key: :user_id,\n              schema_type: :id,\n              schema_table: :users\n            ]\n          ],\n          fn ->\n            send(self(), {:mix_shell_input, :yes?, true})\n\n            Gen.Auth.run(\n              ~w(Accounts User users --no-compile --live --scope user),\n              ecto_adapter: Ecto.Adapters.Postgres\n            )\n\n            help_text = \"\"\"\n            The scope user is already configured.\n\n            phx.gen.auth expects the configured scope module MyApp.Accounts.Scope to include\n            a `for_user/1` function that returns a `%MyApp.Accounts.User{}` struct:\n\n                def for_user(nil), do: %__MODULE__{user: nil}\n\n                def for_user(%<%= inspect schema.alias %>{} = user) do\n                  %__MODULE__{user: user}\n                end\n\n            Please ensure that your scope module includes such code.\n\n            Do you want to proceed with the generation?\\\n            \"\"\"\n\n            assert_received {:mix_shell, :yes?, [question]}\n            assert question == help_text\n          end\n        )\n      end)\n    end\n  end\n\n  test \"allows templates to be overridden\", config do\n    in_tmp_phx_project(config.test, fn ->\n      File.mkdir_p!(\"priv/templates/phx.gen.auth\")\n      File.write!(\"priv/templates/phx.gen.auth/auth.ex\", \"#it works!\")\n\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts Admin admins --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"lib/my_app_web/admin_auth.ex\", fn file ->\n        assert file =~ ~S|it works!|\n      end)\n    end)\n  end\n\n  test \"with --no-agents-md does not inject content to AGENTS.md\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --no-agents-md --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      # AGENTS.md should still exist from phx.new but should not contain phx.gen.auth content\n      assert_file(\"AGENTS.md\", fn file ->\n        refute file =~ \"phoenix-gen-auth-start\"\n        refute file =~ \"phoenix-gen-auth-end\"\n      end)\n    end)\n  end\n\n  test \"injects phx.gen.auth content into AGENTS.md at the correct location\", config do\n    in_tmp_phx_project(config.test, fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      # Should inject content before usage-rules-start\n      assert_file(\"AGENTS.md\", fn file ->\n        assert file =~ \"phoenix-gen-auth-start\"\n        assert file =~ \"phoenix-gen-auth-end\"\n        assert file =~ \"usage-rules-start\"\n\n        # Verify the order: gen.auth content comes before usage-rules\n        auth_start_pos = :binary.match(file, \"phoenix-gen-auth-start\") |> elem(0)\n        usage_rules_pos = :binary.match(file, \"usage-rules-start\") |> elem(0)\n        assert auth_start_pos < usage_rules_pos\n      end)\n    end)\n  end\n\n  test \"does not duplicate phx.gen.auth content when run multiple times\", config do\n    in_tmp_phx_project(config.test, fn ->\n      # First run\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Accounts User users --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      # Second run with different schema\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Admins Admin admins --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      # Should only have one instance of the gen.auth markers\n      assert_file(\"AGENTS.md\", fn file ->\n        auth_start_matches = Regex.scan(~r/phoenix-gen-auth-start/, file)\n        assert length(auth_start_matches) == 1\n\n        auth_end_matches = Regex.scan(~r/phoenix-gen-auth-end/, file)\n        assert length(auth_end_matches) == 1\n      end)\n    end)\n  end\n\n  test \"injects different content for --live vs non-live\", config do\n    in_tmp_phx_project(config.test, fn ->\n      # First test with --live\n      Gen.Auth.run(\n        ~w(Accounts User users --live --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_file(\"AGENTS.md\", fn file ->\n        # Check for LiveView-specific content\n        assert file =~ \"live_session\"\n        assert file =~ \"LiveViews that require login\"\n        assert file =~ \"live_session :require_authenticated_user\"\n        assert file =~ \"live_session :current_user\"\n        assert file =~ \"on_mount:\"\n        assert file =~ \"MyAppWeb.UserAuth, :require_authenticated\"\n        assert file =~ \"MyAppWeb.UserAuth, :mount_current_scope\"\n        assert file =~ \"or LiveViews\"\n        assert file =~ \"**Never** duplicate `live_session` names\"\n      end)\n\n      # Clean up AGENTS.md for second test\n      File.rm!(\"AGENTS.md\")\n      # Re-create it as phx.new would\n      File.write!(\"AGENTS.md\", \"\"\"\n      ## Project-specific information\n\n      <!-- usage-rules-start -->\n      <!-- usage-rules-end -->\n      \"\"\")\n\n      # Now test without --live\n      send(self(), {:mix_shell_input, :yes?, false})\n\n      Gen.Auth.run(\n        ~w(Admins Admin admins --no-compile),\n        ecto_adapter: Ecto.Adapters.Postgres\n      )\n\n      assert_received {:mix_shell, :yes?, [@liveview_option_message]}\n\n      assert_file(\"AGENTS.md\", fn file ->\n        # Should not have LiveView-specific content\n        refute file =~ \"live_session\"\n        refute file =~ \"LiveViews that require login\"\n        refute file =~ \"on_mount:\"\n        refute file =~ \"or LiveViews\"\n        refute file =~ \"**Never** duplicate `live_session` names\"\n\n        # But should still have general auth content\n        assert file =~ \"Authentication\"\n        assert file =~ \"require_authenticated_admin\"\n        assert file =~ \"Controller routes must be placed\"\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.cert_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.CertTest do\n  use ExUnit.Case\n\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  @timeout 5_000\n\n  test \"write certificate and key files\" do\n    in_tmp(\"mix_phx_gen_cert\", fn ->\n      Gen.Cert.run([])\n\n      assert_received {:mix_shell, :info, [\"* creating priv/cert/selfsigned_key.pem\"]}\n      assert_received {:mix_shell, :info, [\"* creating priv/cert/selfsigned.pem\"]}\n\n      assert_file(\"priv/cert/selfsigned_key.pem\", \"-----BEGIN RSA PRIVATE KEY-----\")\n      assert_file(\"priv/cert/selfsigned.pem\", \"-----BEGIN CERTIFICATE-----\")\n    end)\n  end\n\n  test \"write certificate and key with custom filename\" do\n    in_tmp(\"mix_phx_gen_cert\", fn ->\n      Gen.Cert.run([\"-o\", \"priv/cert/localhost\"])\n\n      assert_received {:mix_shell, :info, [\"* creating priv/cert/localhost_key.pem\"]}\n      assert_received {:mix_shell, :info, [\"* creating priv/cert/localhost.pem\"]}\n\n      assert_file(\"priv/cert/localhost_key.pem\", \"-----BEGIN RSA PRIVATE KEY-----\")\n      assert_file(\"priv/cert/localhost.pem\", \"-----BEGIN CERTIFICATE-----\")\n    end)\n  end\n\n  test \"TLS connection with generated certificate and key\" do\n    Application.ensure_all_started(:ssl)\n\n    in_tmp(\"mix_phx_gen_cert\", fn ->\n      Gen.Cert.run([])\n\n      assert {:ok, server} =\n               :ssl.listen(\n                 0,\n                 certfile: \"priv/cert/selfsigned.pem\",\n                 keyfile: \"priv/cert/selfsigned_key.pem\"\n               )\n\n      {:ok, {_, port}} = :ssl.sockname(server)\n\n      spawn_link(fn ->\n        with {:ok, conn} <- :ssl.transport_accept(server, @timeout),\n             :ok <- :ssl.handshake(conn, @timeout) do\n          :ssl.close(conn)\n        end\n      end)\n\n      # We don't actually verify the server cert contents, we just check that\n      # the client and server are able to complete the TLS handshake\n      assert {:ok, client} = :ssl.connect(~c\"localhost\", port, [verify: :verify_none], @timeout)\n      :ssl.close(client)\n      :ssl.close(server)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.channel_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule PhoenixWeb.DupChannel do\nend\n\ndefmodule Ecto.Adapters.SQL do\nend\n\ndefmodule Mix.Tasks.Phx.Gen.ChannelTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"generates channel\" do\n    in_tmp_project(\"generates channel\", fn ->\n      # Ensure the `user_socket.ex` exists first.\n      Gen.Socket.run([\"User\"])\n      Gen.Channel.run([\"Room\"])\n\n      assert_file(\"lib/phoenix_web/channels/room_channel.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannel do|\n        assert file =~ ~S|use PhoenixWeb, :channel|\n        assert file =~ ~S|def join(\"room:lobby\", payload, socket) do|\n\n        assert file =~ ~S|def handle_in(\"ping\", payload, socket) do|\n        assert file =~ ~S|{:reply, {:ok, payload}, socket}|\n        assert file =~ ~S|def handle_in(\"shout\", payload, socket) do|\n        assert file =~ ~S|broadcast(socket, \"shout\", payload)|\n        assert file =~ ~S|{:noreply, socket}|\n      end)\n\n      assert_file(\"test/support/channel_case.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.ChannelCase|\n        assert file =~ ~S|@endpoint PhoenixWeb.Endpoint|\n        assert file =~ ~S|Phoenix.DataCase.setup_sandbox|\n      end)\n\n      assert_file(\"test/phoenix_web/channels/room_channel_test.exs\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannelTest|\n        assert file =~ ~S|use PhoenixWeb.ChannelCase|\n        assert file =~ ~S|> socket(\"user_id\", %{some: :assign}|\n        assert file =~ ~S|> subscribe_and_join(PhoenixWeb.RoomChannel|\n\n        assert file =~ ~S|test \"ping replies with status ok\"|\n        assert file =~ ~S|ref = push(socket, \"ping\", %{\"hello\" => \"there\"})|\n        assert file =~ ~S|assert_reply ref, :ok, %{\"hello\" => \"there\"}|\n\n        assert file =~ ~S|test \"shout broadcasts to room:lobby\"|\n        assert file =~ ~S|push(socket, \"shout\", %{\"hello\" => \"all\"})|\n        assert file =~ ~S|assert_broadcast \"shout\", %{\"hello\" => \"all\"}|\n\n        assert file =~ ~S|test \"broadcasts are pushed to the client\"|\n        assert file =~ ~S|broadcast_from!(socket, \"broadcast\", %{\"some\" => \"data\"})|\n        assert file =~ ~S|assert_push \"broadcast\", %{\"some\" => \"data\"}|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\n                       \"\"\"\n\n                       Add the channel to your `lib/phoenix_web/channels/user_socket.ex` handler, for example:\n\n                           channel \"room:lobby\", PhoenixWeb.RoomChannel\n                       \"\"\"\n                     ]}\n  end\n\n  test \"generates channel and ask to create UserSocket\" do\n    in_tmp_project(\"generates channel\", fn ->\n      # Accepts creation of the UserSocket\n      send(self(), {:mix_shell_input, :yes?, true})\n      Gen.Channel.run([\"Room\"])\n\n      assert_file(\"lib/phoenix_web/channels/room_channel.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannel do|\n        assert file =~ ~S|use PhoenixWeb, :channel|\n      end)\n\n      assert_file(\"test/phoenix_web/channels/room_channel_test.exs\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannelTest|\n        assert file =~ ~S|use PhoenixWeb.ChannelCase|\n      end)\n\n      assert_received {:mix_shell, :info,\n                       [\n                         \"\\nThe default socket handler - PhoenixWeb.UserSocket - was not found\" <>\n                           _\n                       ]}\n\n      assert_received {:mix_shell, :yes?, [question]}\n      assert question =~ \"Do you want to create it?\"\n\n      assert_received {:mix_shell, :info,\n                       [\"\\nAdd the socket handler to your `lib/phoenix_web/endpoint.ex`\" <> _]}\n\n      assert_file(\"lib/phoenix_web/channels/user_socket.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.UserSocket do|\n        assert file =~ ~S|channel \"room:*\", PhoenixWeb.RoomChannel|\n      end)\n    end)\n  end\n\n  test \"generates channel and give instructions when UserSocket does not exist\" do\n    in_tmp_project(\"generates channel\", fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n      Gen.Channel.run([\"Room\"])\n\n      assert_file(\"lib/phoenix_web/channels/room_channel.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannel do|\n        assert file =~ ~S|use PhoenixWeb, :channel|\n      end)\n\n      assert_file(\"test/phoenix_web/channels/room_channel_test.exs\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannelTest|\n        assert file =~ ~S|use PhoenixWeb.ChannelCase|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nThe default socket handler - PhoenixWeb.UserSocket - was not found\" <> _]}\n\n    assert_received {:mix_shell, :yes?, [question]}\n    assert question =~ \"Do you want to create it?\"\n\n    assert_received {:mix_shell, :info, [\"\\nTo create it, please run the mix task:\" <> _]}\n  end\n\n  test \"in an umbrella with a context_app, generates the files\" do\n    in_tmp_umbrella_project(\"generates channels\", fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n      Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n      Gen.Channel.run([\"Room\"])\n\n      assert_file(\"lib/phoenix/channels/room_channel.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannel do|\n        assert file =~ ~S|use PhoenixWeb, :channel|\n      end)\n\n      assert_file(\"test/support/channel_case.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.ChannelCase|\n        assert file =~ ~S|@endpoint PhoenixWeb.Endpoint|\n        assert file =~ ~S|Phoenix.DataCase.setup_sandbox|\n      end)\n\n      assert_file(\"test/phoenix/channels/room_channel_test.exs\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomChannelTest|\n        assert file =~ ~S|subscribe_and_join(PhoenixWeb.RoomChannel|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nThe default socket handler - PhoenixWeb.UserSocket - was not found\" <> _]}\n\n    assert_received {:mix_shell, :yes?, [question]}\n    assert question =~ \"Do you want to create it?\"\n\n    assert_received {:mix_shell, :info, [\"\\nTo create it, please run the mix task\" <> _]}\n  end\n\n  test \"generates nested channel\" do\n    in_tmp_project(\"generates nested channel\", fn ->\n      send(self(), {:mix_shell_input, :yes?, false})\n      Gen.Channel.run([\"Admin.Room\"])\n\n      assert_file(\"lib/phoenix_web/channels/admin/room_channel.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.Admin.RoomChannel do|\n        assert file =~ ~S|use PhoenixWeb, :channel|\n      end)\n\n      assert_file(\"test/phoenix_web/channels/admin/room_channel_test.exs\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.Admin.RoomChannelTest|\n        assert file =~ ~S|use PhoenixWeb.ChannelCase|\n        assert file =~ ~S|subscribe_and_join(PhoenixWeb.Admin.RoomChannel|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nThe default socket handler - PhoenixWeb.UserSocket - was not found\" <> _]}\n\n    assert_received {:mix_shell, :yes?, [question]}\n    assert question =~ \"Do you want to create it?\"\n\n    assert_received {:mix_shell, :info, [\"\\nTo create it, please run the mix task\" <> _]}\n  end\n\n  test \"passing no args raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Channel.run([])\n    end\n  end\n\n  test \"passing invalid name raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Channel.run([\"room\"])\n    end\n  end\n\n  test \"passing extra args raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Channel.run([\"Admin.Room\", \"new_message\"])\n    end\n  end\n\n  test \"name is already defined\" do\n    assert_raise Mix.Error, ~r/DupChannel is already taken/, fn ->\n      Gen.Channel.run([\"Dup\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.context_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Phoenix.DupContext do\nend\n\ndefmodule Mix.Tasks.Phx.Gen.ContextTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n  alias Mix.Phoenix.{Context, Schema}\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"new context\", config do\n    in_tmp_project(config.test, fn ->\n      schema = Schema.new(\"Blog.Post\", \"posts\", [], no_scope: true)\n      context = Context.new(\"Blog\", schema, [])\n\n      assert %Context{\n               alias: Blog,\n               base_module: Phoenix,\n               basename: \"blog\",\n               module: Phoenix.Blog,\n               web_module: PhoenixWeb,\n               schema: %Mix.Phoenix.Schema{\n                 alias: Post,\n                 human_plural: \"Posts\",\n                 human_singular: \"Post\",\n                 module: Phoenix.Blog.Post,\n                 plural: \"posts\",\n                 singular: \"post\"\n               }\n             } = context\n\n      assert String.ends_with?(context.dir, \"lib/phoenix/blog\")\n      assert String.ends_with?(context.file, \"lib/phoenix/blog.ex\")\n      assert String.ends_with?(context.test_file, \"test/phoenix/blog_test.exs\")\n\n      assert String.ends_with?(\n               context.test_fixtures_file,\n               \"test/support/fixtures/blog_fixtures.ex\"\n             )\n\n      assert String.ends_with?(context.schema.file, \"lib/phoenix/blog/post.ex\")\n    end)\n  end\n\n  test \"optional context name\", config do\n    in_tmp_project(config.test, fn ->\n      {context, _} =\n        Mix.Tasks.Phx.Gen.Context.build([\"User\", \"users\", \"name\"], name_optional: true)\n\n      assert %Context{\n               alias: Users,\n               base_module: Phoenix,\n               basename: \"users\",\n               module: Phoenix.Users,\n               web_module: PhoenixWeb,\n               schema: %Mix.Phoenix.Schema{\n                 alias: User,\n                 human_plural: \"Users\",\n                 human_singular: \"User\",\n                 module: Phoenix.Users.User,\n                 plural: \"users\",\n                 singular: \"user\"\n               }\n             } = context\n\n      assert String.ends_with?(context.dir, \"lib/phoenix/users\")\n      assert String.ends_with?(context.file, \"lib/phoenix/users.ex\")\n      assert String.ends_with?(context.test_file, \"test/phoenix/users_test.exs\")\n\n      assert String.ends_with?(\n               context.test_fixtures_file,\n               \"test/support/fixtures/users_fixtures.ex\"\n             )\n\n      assert String.ends_with?(context.schema.file, \"lib/phoenix/users/user.ex\")\n    end)\n  end\n\n  test \"optional context name raises if schema is plural\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Please pass an explicit context option/, fn ->\n        Mix.Tasks.Phx.Gen.Context.build([\"Users\", \"users\", \"name\"], name_optional: true)\n      end\n    end)\n  end\n\n  test \"context name raises is required by default\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error,\n                   ~r/expect a context module name, followed by singular and plural names/,\n                   fn ->\n                     Mix.Tasks.Phx.Gen.Context.build([\"Users\", \"users\", \"name\"])\n                   end\n    end)\n  end\n\n  test \"new nested context\", config do\n    in_tmp_project(config.test, fn ->\n      schema = Schema.new(\"Site.Blog.Post\", \"posts\", [], no_scope: true)\n      context = Context.new(\"Site.Blog\", schema, [])\n\n      assert %Context{\n               alias: Blog,\n               base_module: Phoenix,\n               basename: \"blog\",\n               module: Phoenix.Site.Blog,\n               web_module: PhoenixWeb,\n               schema: %Mix.Phoenix.Schema{\n                 alias: Post,\n                 human_plural: \"Posts\",\n                 human_singular: \"Post\",\n                 module: Phoenix.Site.Blog.Post,\n                 plural: \"posts\",\n                 singular: \"post\"\n               }\n             } = context\n\n      assert String.ends_with?(context.dir, \"lib/phoenix/site/blog\")\n      assert String.ends_with?(context.file, \"lib/phoenix/site/blog.ex\")\n      assert String.ends_with?(context.test_file, \"test/phoenix/site/blog_test.exs\")\n\n      assert String.ends_with?(\n               context.test_fixtures_file,\n               \"test/support/fixtures/site/blog_fixtures.ex\"\n             )\n\n      assert String.ends_with?(context.schema.file, \"lib/phoenix/site/blog/post.ex\")\n    end)\n  end\n\n  test \"new existing context\", config do\n    in_tmp_project(config.test, fn ->\n      File.mkdir_p!(\"lib/phoenix/blog\")\n\n      File.write!(\"lib/phoenix/blog.ex\", \"\"\"\n      defmodule Phoenix.Blog do\n      end\n      \"\"\")\n\n      schema = Schema.new(\"Blog.Post\", \"posts\", [], no_scope: true)\n      context = Context.new(\"Blog\", schema, [])\n      assert Context.pre_existing?(context)\n      refute Context.pre_existing_tests?(context)\n      refute Context.pre_existing_test_fixtures?(context)\n\n      File.mkdir_p!(\"test/phoenix/blog\")\n\n      File.write!(context.test_file, \"\"\"\n      defmodule Phoenix.BlogTest do\n      end\n      \"\"\")\n\n      assert Context.pre_existing_tests?(context)\n\n      File.mkdir_p!(\"test/support/fixtures\")\n\n      File.write!(context.test_fixtures_file, \"\"\"\n      defmodule Phoenix.BlogFixtures do\n      end\n      \"\"\")\n\n      assert Context.pre_existing_test_fixtures?(context)\n    end)\n  end\n\n  test \"new with default scope\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :id,\n            schema_table: :users\n          ]\n        ],\n        fn ->\n          schema = Schema.new(\"Blog.Post\", \"posts\", [], [])\n          context = Context.new(\"Blog\", schema, [])\n\n          assert %Context{\n                   scope: %Mix.Phoenix.Scope{\n                     name: :user,\n                     default: true,\n                     module: MyApp.Accounts.Scope,\n                     assign_key: :current_scope,\n                     access_path: [:user, :id],\n                     schema_key: :user_id,\n                     schema_type: :id,\n                     schema_table: :users\n                   }\n                 } = context\n        end\n      )\n    end)\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Expected the context, \"blog\", to be a valid module name/, fn ->\n        Gen.Context.run(~w(blog Post posts title:string))\n      end\n\n      assert_raise Mix.Error, ~r/Expected the schema, \"posts\", to be a valid module name/, fn ->\n        Gen.Context.run(~w(Post posts title:string))\n      end\n\n      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->\n        Gen.Context.run(~w(Blog Blog blogs))\n      end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate context Phoenix because it has the same name as the application/,\n                   fn ->\n                     Gen.Context.run(~w(Phoenix Post blogs))\n                   end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate schema Phoenix because it has the same name as the application/,\n                   fn ->\n                     Gen.Context.run(~w(Blog Phoenix blogs))\n                   end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Context.run(~w(Blog.Post posts))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Context.run(~w(Blog Post))\n      end\n    end)\n  end\n\n  test \"generates context and handles existing contexts\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts slug:unique secret:redact title:string))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"field :title, :string\"\n        assert file =~ \"field :secret, :string, redact: true\"\n      end)\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n      end)\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        assert file =~ \"use Phoenix.DataCase\"\n        assert file =~ \"describe \\\"posts\\\" do\"\n        assert file =~ \"import Phoenix.BlogFixtures\"\n      end)\n\n      assert_file(\"test/support/fixtures/blog_fixtures.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.BlogFixtures do\"\n        assert file =~ \"def post_fixture(attrs \\\\\\\\ %{})\"\n        assert file =~ \"title: \\\"some title\\\"\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:posts)\"\n        assert file =~ \"add :title, :string\"\n        assert file =~ \"add :secret, :string\"\n        assert file =~ \"create unique_index(:posts, [:slug])\"\n      end)\n\n      send(self(), {:mix_shell_input, :yes?, true})\n      Gen.Context.run(~w(Blog Comment comments title:string))\n\n      assert_received {:mix_shell, :info,\n                       [\"You are generating into an existing context\" <> notice]}\n\n      assert notice =~\n               \"Phoenix.Blog context currently has 6 functions and 1 file in its directory\"\n\n      assert_received {:mix_shell, :yes?, [\"Would you like to proceed?\"]}\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :title, :string\"\n      end)\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        assert file =~ \"use Phoenix.DataCase\"\n        assert file =~ \"describe \\\"comments\\\" do\"\n        assert file =~ \"import Phoenix.BlogFixtures\"\n      end)\n\n      assert_file(\"test/support/fixtures/blog_fixtures.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.BlogFixtures do\"\n        assert file =~ \"def comment_fixture(attrs \\\\\\\\ %{})\"\n        assert file =~ \"title: \\\"some title\\\"\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_comments.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:comments)\"\n        assert file =~ \"add :title, :string\"\n      end)\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_comment!\"\n        assert file =~ \"def list_comments\"\n        assert file =~ \"def create_comment\"\n        assert file =~ \"def update_comment\"\n        assert file =~ \"def delete_comment\"\n        assert file =~ \"def change_comment\"\n      end)\n    end)\n  end\n\n  test \"generates with unique fields\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts\n            slug:string:unique\n            subject:unique\n            body:text:unique\n            order:integer:unique\n            price:decimal:unique\n            published_at:utc_datetime:unique\n            author:references:users:unique\n            published?:boolean\n          ))\n\n      assert_received {:mix_shell, :info,\n                       [\n                         \"\"\"\n\n                         Some of the generated database columns are unique. Please provide\n                         unique implementations for the following fixture function(s) in\n                         test/support/fixtures/blog_fixtures.ex:\n\n                             def unique_post_price do\n                               raise \"implement the logic to generate a unique post price\"\n                             end\n\n                             def unique_post_published_at do\n                               raise \"implement the logic to generate a unique post published_at\"\n                             end\n                         \"\"\"\n                       ]}\n\n      assert_file(\"test/support/fixtures/blog_fixtures.ex\", fn file ->\n        assert file =~ ~S|def unique_post_order, do: System.unique_integer([:positive])|\n\n        assert file =~\n                 ~S|def unique_post_slug, do: \"some slug#{System.unique_integer([:positive])}\"|\n\n        assert file =~\n                 ~S|def unique_post_body, do: \"some body#{System.unique_integer([:positive])}\"|\n\n        assert file =~\n                 ~S|def unique_post_subject, do: \"some subject#{System.unique_integer([:positive])}\"|\n\n        refute file =~ ~S|def unique_post_author|\n\n        assert file =~ \"\"\"\n                 def unique_post_price do\n                   raise \"implement the logic to generate a unique post price\"\n                 end\n               \"\"\"\n\n        assert file =~ \"\"\"\n                       body: unique_post_body(),\n                       order: unique_post_order(),\n                       price: unique_post_price(),\n                       published?: true,\n                       published_at: unique_post_published_at(),\n                       slug: unique_post_slug(),\n                       subject: unique_post_subject()\n               \"\"\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:posts)\"\n        assert file =~ \"create unique_index(:posts, [:order])\"\n        assert file =~ \"create unique_index(:posts, [:price])\"\n        assert file =~ \"create unique_index(:posts, [:slug])\"\n        assert file =~ \"create unique_index(:posts, [:subject])\"\n      end)\n    end)\n  end\n\n  test \"does not prompt on unimplemented functions with only string, text and integer unique fields\",\n       config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts\n            slug:string:unique\n            subject:unique\n            body:text:unique\n            order:integer:unique\n          ))\n\n      refute_received {:mix_shell, :info,\n                       [\"\\nSome of the generated database columns are unique.\" <> _]}\n    end)\n  end\n\n  test \"generates into existing context without prompt with --merge-with-existing-context\",\n       config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts title))\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n      end)\n\n      Gen.Context.run(~w(Blog Comment comments message:string --merge-with-existing-context))\n\n      refute_received {:mix_shell, :info,\n                       [\"You are generating into an existing context\" <> _notice]}\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_comment!\"\n        assert file =~ \"def list_comments\"\n        assert file =~ \"def create_comment\"\n        assert file =~ \"def update_comment\"\n        assert file =~ \"def delete_comment\"\n        assert file =~ \"def change_comment\"\n      end)\n    end)\n  end\n\n  test \"when more than 50 attributes are given\", config do\n    in_tmp_project(config.test, fn ->\n      long_attribute_list = Enum.map_join(0..55, \" \", &\"attribute#{&1}:string\")\n      Gen.Context.run(~w(Blog Post posts title #{long_attribute_list}))\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        refute file =~ \"...}\"\n      end)\n    end)\n  end\n\n  test \"generates schema with no migration option\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts title:string --no-migration))\n\n      assert Path.wildcard(\"priv/repo/migrations/*\") == []\n    end)\n  end\n\n  test \"generates context with no schema and repo option\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(~w(Blog Post posts title:string --no-schema --repo=Foo.RepoX))\n\n      refute_file(\"lib/phoenix/blog/post.ex\")\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"alias Foo.RepoX, as: Repo\"\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n        assert file =~ \"raise \\\"TODO\\\"\"\n      end)\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        assert file =~ \"use Phoenix.DataCase\"\n        assert file =~ \"describe \\\"posts\\\" do\"\n        assert file =~ \"import Phoenix.BlogFixtures\"\n      end)\n\n      assert_file(\"test/support/fixtures/blog_fixtures.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.BlogFixtures do\"\n        assert file =~ \"def post_fixture(attrs \\\\\\\\ %{})\"\n        assert file =~ \"title: \\\"some title\\\"\"\n      end)\n\n      assert Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\") == []\n    end)\n  end\n\n  test \"generates context with enum\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Context.run(\n        ~w(Accounts User users email:text:unique password:text:redact status:enum:verified:unverified:disabled)\n      )\n\n      assert_file(\"lib/phoenix/accounts/user.ex\", fn file ->\n        assert file =~ \"field :status, Ecto.Enum, values: [:verified, :unverified, :disabled]\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_users.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:users)\"\n        assert file =~ \"add :status, :string\"\n      end)\n    end)\n  end\n\n  test \"generates scoped context\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :binary_id,\n            schema_table: :users,\n            test_data_fixture: MyApp.AccountsFixtures,\n            test_setup_helper: :register_and_log_in_user\n          ]\n        ],\n        fn ->\n          Gen.Context.run(~w(Blog Post posts title:string))\n\n          assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n            assert file =~ \"field :title, :string\"\n            assert file =~ \"field :user_id, :binary_id\"\n          end)\n\n          assert_file(\"lib/phoenix/blog.ex\", fn file ->\n            assert file =~ \"def subscribe_posts(%Scope{} = scope)\"\n            assert file =~ \"defp broadcast_post(%Scope{} = scope, message)\"\n            assert file =~ \"def get_post!(%Scope{} = scope, id)\"\n            assert file =~ \"def list_posts(%Scope{} = scope)\"\n            assert file =~ \"def create_post(%Scope{} = scope, attrs)\"\n            assert file =~ \"def update_post(%Scope{} = scope, %Post{} = post, attrs)\"\n            assert file =~ \"def delete_post(%Scope{} = scope, %Post{} = post)\"\n            assert file =~ \"def change_post(%Scope{} = scope, %Post{} = post, attrs \\\\\\\\ %{})\"\n          end)\n\n          assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n            assert file =~ \"use Phoenix.DataCase\"\n            assert file =~ \"describe \\\"posts\\\" do\"\n            assert file =~ \"import Phoenix.BlogFixtures\"\n          end)\n\n          assert_file(\"test/support/fixtures/blog_fixtures.ex\", fn file ->\n            assert file =~ \"defmodule Phoenix.BlogFixtures do\"\n            assert file =~ \"def post_fixture(scope, attrs \\\\\\\\ %{})\"\n            assert file =~ \"title: \\\"some title\\\"\"\n          end)\n\n          assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n          assert_file(path, fn file ->\n            assert file =~ \"create table(:posts)\"\n            assert file =~ \"add :title, :string\"\n\n            assert file =~\n                     \"add :user_id, references(:users, type: :binary_id, on_delete: :delete_all)\"\n          end)\n        end\n      )\n    end)\n  end\n\n  test \"raises when a reference conflicts with the scope\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :binary_id,\n            schema_table: :users\n          ]\n        ],\n        fn ->\n          assert_raise Mix.Error,\n                       ~r\"Reference :user_id has the same name as the scope schema key, either skip the reference or pass it with the --no-scope flag.\",\n                       fn ->\n                         Gen.Context.run(\n                           ~w(Blog Post posts title:string user_id:references:users)\n                         )\n                       end\n        end\n      )\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.embedded_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.EmbeddedTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n  alias Mix.Phoenix.Schema\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"build\" do\n    in_tmp_project(\"embedded build\", fn ->\n      schema = Gen.Embedded.build(~w(Blog.Post title:string))\n\n      assert %Schema{\n               alias: Post,\n               module: Phoenix.Blog.Post,\n               repo: Phoenix.Repo,\n               migration?: false,\n               migration_defaults: %{title: \"\"},\n               plural: nil,\n               singular: \"post\",\n               human_plural: \"Nil\",\n               human_singular: \"Post\",\n               attrs: [title: :string],\n               types: [title: :string],\n               embedded?: true,\n               defaults: %{title: \"\"}\n             } = schema\n\n      assert String.ends_with?(schema.file, \"lib/phoenix/blog/post.ex\")\n    end)\n  end\n\n  test \"generates embedded schema\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(~w(Blog.Post title:string))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"embedded_schema do\"\n      end)\n    end)\n  end\n\n  test \"generates nested embedded schema\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(~w(Blog.Admin.User name:string))\n\n      assert_file(\"lib/phoenix/blog/admin/user.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.Blog.Admin.User do\"\n        assert file =~ \"embedded_schema do\"\n      end)\n    end)\n  end\n\n  test \"generates embedded schema with proper datetime types\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(\n        ~w(Blog.Comment title:string drafted_at:datetime published_at:naive_datetime edited_at:utc_datetime)\n      )\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :drafted_at, :naive_datetime\"\n        assert file =~ \"field :published_at, :naive_datetime\"\n        assert file =~ \"field :edited_at, :utc_datetime\"\n      end)\n    end)\n  end\n\n  test \"generates embedded schema with enum\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(\n        ~w(Blog.Comment comments title:string status:enum:unpublished:published:deleted)\n      )\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :status, Ecto.Enum, values: [:unpublished, :published, :deleted]\"\n      end)\n    end)\n  end\n\n  test \"generates embedded schema with redact option\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(~w(Blog.Comment comments title:string secret:redact))\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :secret, :string, redact: true\"\n      end)\n    end)\n  end\n\n  test \"generates embedded schema with references\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Embedded.run(\n        ~w(Blog.Comment comments body word_count:integer author_id:references:author)\n      )\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :author_id, :id\"\n        assert file =~ \"field :body, :string\"\n        assert file =~ \"field :word_count, :integer\"\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.html_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.HtmlTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Expected the context, \"blog\", to be a valid module name/, fn ->\n        Gen.Html.run(~w(blog Post posts title:string))\n      end\n\n      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->\n        Gen.Html.run(~w(Blog Blog blogs))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Html.run(~w(Blog.Post posts))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Html.run(~w(Blog Post))\n      end\n\n      assert_raise Mix.Error, ~r/Enum type requires at least one value/, fn ->\n        Gen.Html.run(~w(Blog Post posts status:enum))\n      end\n\n      assert_raise Mix.Error, ~r/requires at least one attribute/, fn ->\n        Gen.Html.run(~w(Blog Post posts))\n      end\n    end)\n  end\n\n  test \"generates html resource and handles existing contexts\", config do\n    one_day_in_seconds = 24 * 3600\n    naive_datetime = %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}}\n    datetime = %{DateTime.utc_now() | second: 0, microsecond: {0, 6}}\n\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Post posts title content:text slug:unique votes:integer cost:decimal\n                      tags:array:text popular:boolean drafted_at:datetime\n                      status:enum:unpublished:published:deleted\n                      published_at:utc_datetime\n                      published_at_usec:utc_datetime_usec\n                      deleted_at:naive_datetime\n                      deleted_at_usec:naive_datetime_usec\n                      alarm:time\n                      alarm_usec:time_usec\n                      secret:uuid:redact announcement_date:date alarm:time\n                      metadata:map\n                      weight:float user_id:references:users\n                     ))\n\n      assert_file(\"lib/phoenix/blog/post.ex\")\n      assert_file(\"lib/phoenix/blog.ex\")\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        assert file =~ \"alarm: ~T[15:01:01]\"\n        assert file =~ \"alarm_usec: ~T[15:01:01.000000]\"\n        assert file =~ \"announcement_date: #{Date.utc_today() |> Date.add(-1) |> inspect()}\"\n\n        assert file =~\n                 \"deleted_at: #{naive_datetime |> NaiveDateTime.add(-one_day_in_seconds) |> NaiveDateTime.truncate(:second) |> inspect()}\"\n\n        assert file =~\n                 \"deleted_at_usec: #{naive_datetime |> NaiveDateTime.add(-one_day_in_seconds) |> inspect()}\"\n\n        assert file =~ \"cost: \\\"120.5\\\"\"\n\n        assert file =~\n                 \"published_at: #{datetime |> DateTime.add(-one_day_in_seconds) |> DateTime.truncate(:second) |> inspect()}\"\n\n        assert file =~\n                 \"published_at_usec: #{datetime |> DateTime.add(-one_day_in_seconds) |> inspect()}\"\n\n        assert file =~ \"weight: 120.5\"\n        assert file =~ \"status: :published\"\n\n        assert file =~ \"assert post.announcement_date == #{inspect(Date.utc_today())}\"\n\n        assert file =~\n                 \"assert post.deleted_at == #{naive_datetime |> NaiveDateTime.truncate(:second) |> inspect()}\"\n\n        assert file =~ \"assert post.deleted_at_usec == #{inspect(naive_datetime)}\"\n\n        assert file =~\n                 \"assert post.published_at == #{datetime |> DateTime.truncate(:second) |> inspect()}\"\n\n        assert file =~ \"assert post.published_at_usec == #{inspect(datetime)}\"\n        assert file =~ \"assert post.alarm == ~T[15:01:01]\"\n        assert file =~ \"assert post.alarm_usec == ~T[15:01:01.000000]\"\n        assert file =~ \"assert post.cost == Decimal.new(\\\"120.5\\\")\"\n        assert file =~ \"assert post.weight == 120.5\"\n        assert file =~ \"assert post.status == :published\"\n      end)\n\n      assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostControllerTest\"\n        assert file =~ ~s|~p\"/posts|\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:posts)\"\n        assert file =~ \"add :title, :string\"\n        assert file =~ \"add :content, :text\"\n        assert file =~ \"add :status, :string\"\n        assert file =~ \"create unique_index(:posts, [:slug])\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ \"Blog.get_post!\"\n        assert file =~ \"Blog.list_posts\"\n        assert file =~ \"Blog.create_post\"\n        assert file =~ \"Blog.update_post\"\n        assert file =~ \"Blog.delete_post\"\n        assert file =~ \"Blog.change_post\"\n        assert file =~ ~s|redirect(to: ~p\"/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostHTML\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/index.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/new.html.heex\", fn file ->\n        assert file =~ ~S(action={~p\"/posts\"})\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/post_form.html.heex\")\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/show.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/edit.html.heex\", fn file ->\n        assert file =~ ~S(action={~p\"/posts/#{@post}\"})\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/post_form.html.heex\", fn file ->\n        assert file =~ ~S(<.form :let={f} for={@changeset} action={@action}>)\n        assert file =~ ~s(<.input field={f[:title]} type=\"text\")\n        assert file =~ ~s(<.input field={f[:content]} type=\"textarea\")\n        assert file =~ ~s(<.input field={f[:votes]} type=\"number\")\n        assert file =~ ~s(<.input field={f[:cost]} type=\"number\" label=\"Cost\" step=\"any\")\n\n        assert file =~ \"\"\"\n                 <.input\n                   field={f[:tags]}\n                   type=\"select\"\n                   multiple\n               \"\"\"\n\n        assert file =~ ~s(<.input field={f[:popular]} type=\"checkbox\")\n        assert file =~ ~s(<.input field={f[:drafted_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={f[:published_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={f[:deleted_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={f[:announcement_date]} type=\"date\")\n        assert file =~ ~s(<.input field={f[:alarm]} type=\"time\")\n        assert file =~ ~s(<.input field={f[:secret]} type=\"text\" label=\"Secret\" />)\n        refute file =~ ~s(field={f[:metadata]})\n\n        assert file =~ \"\"\"\n                 <.input\n                   field={f[:status]}\n                   type=\"select\"\n               \"\"\"\n\n        assert file =~ ~s|Ecto.Enum.values(Phoenix.Blog.Post, :status)|\n\n        refute file =~ ~s(<.input field={f[:user_id]})\n      end)\n\n      send(self(), {:mix_shell_input, :yes?, true})\n      Gen.Html.run(~w(Blog Comment comments title:string))\n      assert_received {:mix_shell, :info, [\"You are generating into an existing context\" <> _]}\n\n      assert_file(\"lib/phoenix/blog/comment.ex\")\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_comments.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:comments)\"\n        assert file =~ \"add :title, :string\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ \"Blog.get_comment!\"\n        assert file =~ \"Blog.list_comments\"\n        assert file =~ \"Blog.create_comment\"\n        assert file =~ \"Blog.update_comment\"\n        assert file =~ \"Blog.delete_comment\"\n        assert file =~ \"Blog.change_comment\"\n        assert file =~ ~s|redirect(to: ~p\"/comments|\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the resource to your browser scope in lib/phoenix_web/router.ex:\n\n                            resources \"/posts\", PostController\n                        \"\"\"\n                      ]}\n    end)\n  end\n\n  test \"generates without explicit context\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Post posts title content:text slug:unique votes:integer cost:decimal\n                        tags:array:text popular:boolean drafted_at:datetime\n                        status:enum:unpublished:published:deleted\n                        published_at:utc_datetime\n                        published_at_usec:utc_datetime_usec\n                        deleted_at:naive_datetime\n                        deleted_at_usec:naive_datetime_usec\n                        alarm:time\n                        alarm_usec:time_usec\n                        secret:uuid:redact announcement_date:date alarm:time\n                        metadata:map\n                        weight:float user_id:references:users\n                      ))\n\n      assert_file(\"lib/phoenix/posts/post.ex\")\n      assert_file(\"lib/phoenix/posts.ex\")\n\n      assert_file(\"test/phoenix/posts_test.exs\", fn file ->\n        assert file =~ \"alarm: ~T[15:01:01]\"\n        assert file =~ \"alarm_usec: ~T[15:01:01.000000]\"\n        assert file =~ \"announcement_date: #{Date.utc_today() |> Date.add(-1) |> inspect()}\"\n      end)\n    end)\n  end\n\n  test \"generates into existing context without prompt with --merge-with-existing-context\",\n       config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Post posts title))\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n      end)\n\n      Gen.Html.run(~w(Blog Comment comments message:string --merge-with-existing-context))\n\n      refute_received {:mix_shell, :info,\n                       [\"You are generating into an existing context\" <> _notice]}\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_comment!\"\n        assert file =~ \"def list_comments\"\n        assert file =~ \"def create_comment\"\n        assert file =~ \"def update_comment\"\n        assert file =~ \"def delete_comment\"\n        assert file =~ \"def change_comment\"\n      end)\n    end)\n  end\n\n  test \"with --web namespace generates namespaced web modules and directories\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Post posts title:string --web Blog))\n\n      assert_file(\"test/phoenix_web/controllers/blog/post_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostControllerTest\"\n        assert file =~ ~s|~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ ~s|redirect(to: ~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_html/edit.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_html/index.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_html/new.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_html/show.html.heex\", fn file ->\n        assert file =~ ~s|~p\"/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_html.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostHTML\"\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the resource to your Blog :browser scope in lib/phoenix_web/router.ex:\n\n                            scope \"/blog\", PhoenixWeb.Blog do\n                              pipe_through :browser\n                              ...\n                              resources \"/posts\", PostController\n                            end\n                        \"\"\"\n                      ]}\n    end)\n  end\n\n  test \"with --no-context skips context and schema file generation\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Comment comments title:string --no-context))\n\n      refute_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/comment.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_html.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentHTML\"\n      end)\n    end)\n  end\n\n  test \"with a matching plural and singular term\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Tracker Series series value:integer))\n\n      assert_file(\"lib/phoenix_web/controllers/series_controller.ex\", fn file ->\n        assert file =~ \"render(conn, :index, series_collection: series)\"\n      end)\n    end)\n  end\n\n  test \"with --no-context no warning is emitted when context exists\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Post posts title:string))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      assert_file(\"lib/phoenix/blog/post.ex\")\n\n      Gen.Html.run(~w(Blog Comment comments title:string --no-context))\n      refute_received {:mix_shell, :info, [\"You are generating into an existing context\" <> _]}\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_html.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentHTML\"\n      end)\n    end)\n  end\n\n  test \"with --no-schema skips schema file generation\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Comment comments title:string --no-schema))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/comment.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_html.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentHTML\"\n      end)\n    end)\n  end\n\n  test \"when more than 50 arguments are given\", config do\n    in_tmp_project(config.test, fn ->\n      long_attribute_list = Enum.map_join(0..55, \" \", &\"attribute#{&1}:string\")\n      Gen.Html.run(~w(Blog Post posts #{long_attribute_list}))\n\n      assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n        refute file =~ \"...}\"\n      end)\n    end)\n  end\n\n  test \"with custom primary key\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Html.run(~w(Blog Post posts title:string --primary-key post_id))\n\n      assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n        assert file =~ ~s[%{\"post_id\" => post_id}]\n        assert file =~ ~s[%{\"post_id\" => post_id, \"post\" => post_params}]\n        assert file =~ ~s[Blog.get_post!(post_id)]\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/show.html.heex\", fn file ->\n        assert file =~ ~S(Post {@post.post_id})\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_html/edit.html.heex\", fn file ->\n        assert file =~ ~S(Edit Post {@post.post_id})\n      end)\n    end)\n  end\n\n  describe \"inside umbrella\" do\n    test \"without context_app generators config uses web dir\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: nil)\n        Gen.Html.run(~w(Accounts User users name:string))\n\n        assert_file(\"lib/phoenix/accounts.ex\")\n        assert_file(\"lib/phoenix/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix_web/controllers/user_controller.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserController\"\n          assert file =~ \"use PhoenixWeb, :controller\"\n        end)\n\n        assert_file(\"lib/phoenix_web/controllers/user_html.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserHTML\"\n        end)\n\n        assert_file(\"test/phoenix_web/controllers/user_controller_test.exs\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserControllerTest\"\n        end)\n      end)\n    end\n\n    test \"raises with false context_app\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: false)\n\n        assert_raise Mix.Error, ~r/no context_app configured/, fn ->\n          Gen.Html.run(~w(Accounts User users name:string))\n        end\n      end)\n    end\n\n    test \"with context_app generators config does not use web dir\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        File.mkdir!(\"another_app\")\n        Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n\n        Gen.Html.run(~w(Accounts User users name:string))\n\n        assert_file(\"another_app/lib/another_app/accounts.ex\")\n        assert_file(\"another_app/lib/another_app/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix/controllers/user_controller.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserController\"\n          assert file =~ \"use Phoenix, :controller\"\n        end)\n\n        assert_file(\"lib/phoenix/controllers/user_html.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserHTML\"\n        end)\n\n        assert_file(\"test/phoenix/controllers/user_controller_test.exs\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserControllerTest\"\n        end)\n      end)\n    end\n\n    test \"allows enum type with at least one value\", config do\n      in_tmp_project(config.test, fn ->\n        Gen.Html.run(~w(Blog Post posts status:enum:new))\n\n        assert_file(\"lib/phoenix_web/controllers/post_html/post_form.html.heex\", fn file ->\n          assert file =~ ~s|Ecto.Enum.values(Phoenix.Blog.Post, :status)|\n        end)\n      end)\n    end\n\n    test \"respect route_prefix in scopes\", config do\n      in_tmp_project(config.test, fn ->\n        with_scope_env(\n          :phoenix,\n          [\n            organization: [\n              module: Phoenix.Organizations.Scope,\n              assign_key: :current_organization,\n              access_path: [:organization, :id],\n              route_access_path: [:organization, :slug],\n              route_prefix: \"/orgs/:slug\"\n            ]\n          ],\n          fn ->\n            Gen.Html.run(~w(Blog Post posts title:string --scope organization))\n\n            assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n              assert file =~\n                       ~s|redirect(to: ~p\"/orgs/\\#{conn.assigns.current_organization.organization.slug}/posts|\n            end)\n\n            assert_file(\"lib/phoenix_web/controllers/post_html/index.html.heex\", fn file ->\n              assert file =~\n                       ~s|href={~p\"/orgs/\\#{@current_organization.organization.slug}/posts/new\"|\n\n              assert file =~\n                       ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts/|\n            end)\n\n            assert_file(\"lib/phoenix_web/controllers/post_html/show.html.heex\", fn file ->\n              assert file =~\n                       ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts\"|\n            end)\n\n            assert_file(\"lib/phoenix_web/controllers/post_html/edit.html.heex\", fn file ->\n              assert file =~\n                       ~s|action={~p\"/orgs/\\#{@current_organization.organization.slug}/posts/\\#{@post}\"|\n            end)\n\n            assert_file(\"lib/phoenix_web/controllers/post_html/new.html.heex\", fn file ->\n              assert file =~\n                       ~s|action={~p\"/orgs/\\#{@current_organization.organization.slug}/posts\"|\n            end)\n\n            assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n              assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts\"|\n              assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/new\"|\n              assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/\\#{post}\"|\n              assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/\\#{post}/edit\"|\n            end)\n          end\n        )\n      end)\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.json_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.JsonTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Expected the context, \"blog\", to be a valid module name/, fn ->\n        Gen.Json.run(~w(blog Post posts title:string))\n      end\n\n      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->\n        Gen.Json.run(~w(Blog Blog blogs))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Json.run(~w(Blog.Post posts))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Json.run(~w(Blog Post))\n      end\n\n      assert_raise Mix.Error, ~r/requires at least one attribute/, fn ->\n        Gen.Json.run(~w(Blog Post posts))\n      end\n    end)\n  end\n\n  test \"generates json resource\", config do\n    one_day_in_seconds = 24 * 3600\n\n    naive_datetime =\n      %{NaiveDateTime.utc_now() | second: 0, microsecond: {0, 6}}\n      |> NaiveDateTime.add(-one_day_in_seconds)\n\n    datetime =\n      %{DateTime.utc_now() | second: 0, microsecond: {0, 6}}\n      |> DateTime.add(-one_day_in_seconds)\n\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Post posts title slug:unique votes:integer cost:decimal\n                     tags:array:text popular:boolean drafted_at:datetime\n                     params:map\n                     published_at:utc_datetime\n                     published_at_usec:utc_datetime_usec\n                     deleted_at:naive_datetime\n                     deleted_at_usec:naive_datetime_usec\n                     alarm:time\n                     alarm_usec:time_usec\n                     secret:uuid:redact announcement_date:date\n                     weight:float user_id:references:users\n                    ))\n\n      assert_file(\"lib/phoenix/blog/post.ex\")\n      assert_file(\"lib/phoenix/blog.ex\")\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        assert file =~ \"use Phoenix.DataCase\"\n      end)\n\n      assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostControllerTest\"\n\n        assert file =~ \"\"\"\n                     assert %{\n                              \"id\" => ^id,\n                              \"alarm\" => \"14:00:00\",\n                              \"alarm_usec\" => \"14:00:00.000000\",\n                              \"announcement_date\" => \"#{Date.add(Date.utc_today(), -1)}\",\n                              \"cost\" => \"120.5\",\n                              \"deleted_at\" => \"#{naive_datetime |> NaiveDateTime.truncate(:second) |> NaiveDateTime.to_iso8601()}\",\n                              \"deleted_at_usec\" => \"#{NaiveDateTime.to_iso8601(naive_datetime)}\",\n                              \"drafted_at\" => \"#{datetime |> NaiveDateTime.truncate(:second) |> NaiveDateTime.to_iso8601()}\",\n                              \"params\" => %{},\n                              \"popular\" => true,\n                              \"published_at\" => \"#{datetime |> DateTime.truncate(:second) |> DateTime.to_iso8601()}\",\n                              \"published_at_usec\" => \"#{DateTime.to_iso8601(datetime)}\",\n                              \"secret\" => \"7488a646-e31f-11e4-aace-600308960662\",\n                              \"slug\" => \"some slug\",\n                              \"tags\" => [],\n                              \"title\" => \"some title\",\n                              \"votes\" => 42,\n                              \"weight\" => 120.5\n                            } = json_response(conn, 200)[\"data\"]\n               \"\"\"\n      end)\n\n      assert [_] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(\"lib/phoenix_web/controllers/fallback_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.FallbackController\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ \"Blog.get_post!\"\n        assert file =~ \"Blog.list_posts\"\n        assert file =~ \"Blog.create_post\"\n        assert file =~ \"Blog.update_post\"\n        assert file =~ \"Blog.delete_post\"\n        assert file =~ ~s|~p\"/api/posts|\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the resource to the \"/api\" scope in lib/phoenix_web/router.ex:\n\n                            resources \"/posts\", PostController, except: [:new, :edit]\n                        \"\"\"\n                      ]}\n    end)\n  end\n\n  test \"generates without explicit context\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Post posts title slug:unique votes:integer cost:decimal\n                     tags:array:text popular:boolean drafted_at:datetime\n                     params:map\n                     published_at:utc_datetime\n                     published_at_usec:utc_datetime_usec\n                     deleted_at:naive_datetime\n                     deleted_at_usec:naive_datetime_usec\n                     alarm:time\n                     alarm_usec:time_usec\n                     secret:uuid:redact announcement_date:date\n                     weight:float user_id:references:users\n                    ))\n\n      assert_file(\"lib/phoenix/posts/post.ex\")\n      assert_file(\"lib/phoenix/posts.ex\")\n\n      assert_file(\"test/phoenix/posts_test.exs\", fn file ->\n        assert file =~ \"use Phoenix.DataCase\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ \"Posts.get_post!\"\n        assert file =~ \"Posts.list_posts\"\n        assert file =~ \"Posts.create_post\"\n        assert file =~ \"Posts.update_post\"\n        assert file =~ \"Posts.delete_post\"\n        assert file =~ ~s|~p\"/api/posts|\n      end)\n    end)\n  end\n\n  test \"generates into existing context without prompt with --merge-with-existing-context\",\n       config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Post posts title))\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n      end)\n\n      Gen.Json.run(~w(Blog Comment comments message:string --merge-with-existing-context))\n\n      refute_received {:mix_shell, :info,\n                       [\"You are generating into an existing context\" <> _notice]}\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_comment!\"\n        assert file =~ \"def list_comments\"\n        assert file =~ \"def create_comment\"\n        assert file =~ \"def update_comment\"\n        assert file =~ \"def delete_comment\"\n        assert file =~ \"def change_comment\"\n      end)\n    end)\n  end\n\n  test \"when more than 50 arguments are given\", config do\n    in_tmp_project(config.test, fn ->\n      long_attribute_list = Enum.map_join(0..55, \" \", &\"attribute#{&1}:string\")\n      Gen.Json.run(~w(Blog Post posts #{long_attribute_list}))\n\n      assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n        refute file =~ \"...}\"\n      end)\n    end)\n  end\n\n  test \"with json --web namespace generates namespaced web modules and directories\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Post posts title:string --web Blog))\n\n      assert_file(\"test/phoenix_web/controllers/blog/post_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostControllerTest\"\n        assert file =~ ~s|~p\"/api/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n        assert file =~ ~s|~p\"/api/blog/posts|\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/blog/post_json.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostJSON\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/changeset_json.ex\", fn file ->\n        assert file =~ \"Ecto.Changeset.traverse_errors(changeset, &translate_error/1)\"\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the resource to your Blog :api scope in lib/phoenix_web/router.ex:\n\n                            scope \"/blog\", PhoenixWeb.Blog do\n                              pipe_through :api\n                              ...\n                              resources \"/posts\", PostController\n                            end\n                        \"\"\"\n                      ]}\n    end)\n  end\n\n  test \"with --no-context skips context and schema file generation\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Comment comments title:string --no-context))\n\n      refute_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/comment.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_json.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentJSON\"\n      end)\n    end)\n  end\n\n  test \"with --no-context no warning is emitted when context exists\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Post posts title:string))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      assert_file(\"lib/phoenix/blog/post.ex\")\n\n      Gen.Json.run(~w(Blog Comment comments title:string --no-context))\n      refute_received {:mix_shell, :info, [\"You are generating into an existing context\" <> _]}\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_json.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentJSON\"\n      end)\n    end)\n  end\n\n  test \"with --no-schema skips schema file generation\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Comment comments title:string --no-schema))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/comment.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"test/phoenix_web/controllers/comment_controller_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentControllerTest\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_controller.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentController\"\n        assert file =~ \"use PhoenixWeb, :controller\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/comment_json.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentJSON\"\n      end)\n    end)\n  end\n\n  describe \"inside umbrella\" do\n    test \"without context_app generators config uses web dir\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Gen.Json.run(~w(Accounts User users name:string))\n\n        assert_file(\"lib/phoenix/accounts.ex\")\n        assert_file(\"lib/phoenix/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix_web/controllers/user_controller.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserController\"\n          assert file =~ \"use PhoenixWeb, :controller\"\n        end)\n\n        assert_file(\"lib/phoenix_web/controllers/user_json.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserJSON\"\n        end)\n\n        assert_file(\"test/phoenix_web/controllers/user_controller_test.exs\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserControllerTest\"\n        end)\n      end)\n    end\n\n    test \"raises with false context_app\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: false)\n\n        assert_raise Mix.Error, ~r/no context_app configured/, fn ->\n          Gen.Json.run(~w(Accounts User users name:string))\n        end\n      end)\n    end\n\n    test \"with context_app generators config does not use web dir\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        File.mkdir!(\"another_app\")\n        Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n\n        Gen.Json.run(~w(Accounts User users name:string))\n\n        assert_file(\"another_app/lib/another_app/accounts.ex\")\n        assert_file(\"another_app/lib/another_app/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix/controllers/user_controller.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserController\"\n          assert file =~ \"use Phoenix, :controller\"\n        end)\n\n        assert_file(\"lib/phoenix/controllers/user_json.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserJSON\"\n        end)\n\n        assert_file(\"test/phoenix/controllers/user_controller_test.exs\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserControllerTest\"\n        end)\n      end)\n    end\n  end\n\n  test \"with existing core_components.ex file\", config do\n    in_tmp_project(config.test, fn ->\n      File.mkdir_p!(\"lib/phoenix_web/components\")\n\n      File.write!(\"lib/phoenix_web/components/core_components.ex\", \"\"\"\n      defmodule PhoenixWeb.CoreComponents do\n      end\n      \"\"\")\n\n      [{module, _}] = Code.compile_file(\"lib/phoenix_web/components/core_components.ex\")\n\n      Gen.Json.run(~w(Blog Post posts title:string --web Blog))\n\n      assert_file(\"lib/phoenix_web/controllers/changeset_json.ex\", fn file ->\n        assert file =~\n                 \"Ecto.Changeset.traverse_errors(changeset, &PhoenixWeb.CoreComponents.translate_error/1)\"\n      end)\n\n      # Clean up test case specific compile artifact so it doesn't leak to other test cases\n      :code.purge(module)\n      :code.delete(module)\n    end)\n  end\n\n  test \"with custom primary key\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Json.run(~w(Blog Post posts title:string --primary-key post_id))\n\n      assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n        refute file =~ ~s[%{\"id\" =>]\n        assert file =~ ~s[%{\"post_id\" =>]\n        assert file =~ \"Blog.get_post!(post_id)\"\n      end)\n\n      assert_file(\"lib/phoenix_web/controllers/post_json.ex\", fn file ->\n        assert file =~ \"post_id: post.post_id,\"\n      end)\n\n      assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n        assert file =~ ~s[\"post_id\" => ^post_id,]\n      end)\n    end)\n  end\n\n  test \"respect route_prefix in scopes\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          organization: [\n            module: Phoenix.Organizations.Scope,\n            assign_key: :current_organization,\n            access_path: [:organization, :id],\n            route_access_path: [:organization, :slug],\n            route_prefix: \"/orgs/:slug\"\n          ]\n        ],\n        fn ->\n          Gen.Json.run(~w(Blog Post posts title:string --scope organization))\n\n          assert_file(\"lib/phoenix_web/controllers/post_controller.ex\", fn file ->\n            assert file =~\n                     ~s|~p\"/api/orgs/\\#{conn.assigns.current_organization.organization.slug}/posts|\n          end)\n\n          assert_file(\"test/phoenix_web/controllers/post_controller_test.exs\", fn file ->\n            assert file =~ ~s|~p\"/api/orgs/\\#{scope.organization.slug}/posts\"|\n            assert file =~ ~s|~p\"/api/orgs/\\#{scope.organization.slug}/posts/\\#{post}\"|\n          end)\n        end\n      )\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.live_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.LiveTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  defp in_tmp_live_project(test, func) do\n    in_tmp_project(test, fn ->\n      File.mkdir_p!(\"lib\")\n      File.touch!(\"lib/phoenix_web.ex\")\n      File.touch!(\"lib/phoenix.ex\")\n      func.()\n    end)\n  end\n\n  defp in_tmp_live_umbrella_project(test, func) do\n    in_tmp_umbrella_project(test, fn ->\n      File.mkdir_p!(\"phoenix/lib\")\n      File.mkdir_p!(\"phoenix_web/lib\")\n      File.touch!(\"phoenix/lib/phoenix.ex\")\n      File.touch!(\"phoenix_web/lib/phoenix_web.ex\")\n      func.()\n    end)\n  end\n\n  test \"components are in sync with installer\" do\n    assert File.read!(\"priv/templates/phx.gen.live/core_components.ex.eex\") ==\n             File.read!(\"installer/templates/phx_web/components/core_components.ex.eex\")\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_live_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Expected the context, \"blog\", to be a valid module name/, fn ->\n        Gen.Live.run(~w(blog Post posts title:string))\n      end\n\n      assert_raise Mix.Error, ~r/The context and schema should have different names/, fn ->\n        Gen.Live.run(~w(Blog Blog blogs))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Live.run(~w(Blog.Post posts))\n      end\n\n      assert_raise Mix.Error, ~r/Invalid arguments/, fn ->\n        Gen.Live.run(~w(Blog Post))\n      end\n\n      assert_raise Mix.Error, ~r/requires at least one attribute/, fn ->\n        Gen.Live.run(~w(Blog Post posts))\n      end\n    end)\n  end\n\n  test \"generates live resource and handles existing contexts\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title content:text slug:unique votes:integer cost:decimal\n                      tags:array:text popular:boolean drafted_at:datetime\n                      status:enum:unpublished:published:deleted\n                      published_at:utc_datetime\n                      published_at_usec:utc_datetime_usec\n                      deleted_at:naive_datetime\n                      deleted_at_usec:naive_datetime_usec\n                      alarm:time\n                      alarm_usec:time_usec\n                      secret:uuid:redact announcement_date:date alarm:time\n                      metadata:map\n                      weight:float user_id:references:users\n                     ))\n\n      assert_file(\"lib/phoenix/blog/post.ex\")\n      assert_file(\"lib/phoenix/blog.ex\")\n      assert_file(\"test/phoenix/blog_test.exs\")\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Index\"\n        refute file =~ \"dom_id:\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/show.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Show\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/form.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Form\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:posts)\"\n        assert file =~ \"add :title, :string\"\n        assert file =~ \"add :content, :text\"\n        assert file =~ \"create unique_index(:posts, [:slug])\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\", fn file ->\n        assert file =~ ~S|~p\"/posts/#{post}\"|\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/show.ex\", fn file ->\n        assert file =~ ~S|~p\"/posts\"|\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/form.ex\", fn file ->\n        assert file =~ ~s(<.form)\n        assert file =~ ~s(<.input field={@form[:title]} type=\"text\")\n        assert file =~ ~s(<.input field={@form[:content]} type=\"textarea\")\n        assert file =~ ~s(<.input field={@form[:votes]} type=\"number\")\n        assert file =~ ~s(<.input field={@form[:cost]} type=\"number\" label=\"Cost\" step=\"any\")\n\n        assert file =~ \"\"\"\n                       <.input\n                         field={@form[:tags]}\n                         type=\"select\"\n                         multiple\n               \"\"\"\n\n        assert file =~ ~s(<.input field={@form[:popular]} type=\"checkbox\")\n        assert file =~ ~s(<.input field={@form[:drafted_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={@form[:published_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={@form[:deleted_at]} type=\"datetime-local\")\n        assert file =~ ~s(<.input field={@form[:announcement_date]} type=\"date\")\n        assert file =~ ~s(<.input field={@form[:alarm]} type=\"time\")\n        assert file =~ ~s(<.input field={@form[:secret]} type=\"text\" label=\"Secret\" />)\n        refute file =~ ~s(<field={@form[:metadata]})\n\n        assert file =~ \"\"\"\n                       <.input\n                         field={@form[:status]}\n                         type=\"select\"\n               \"\"\"\n\n        assert file =~ ~s|Ecto.Enum.values(Phoenix.Blog.Post, :status)|\n\n        refute file =~ ~s(<.input field={@form[:user_id]})\n      end)\n\n      assert_file(\"test/phoenix_web/live/post_live_test.exs\", fn file ->\n        assert file =~ ~r\"@invalid_attrs.*popular: false\"\n        assert file =~ ~S|~p\"/posts\"|\n        assert file =~ ~S|~p\"/posts/new\"|\n        assert file =~ ~S|~p\"/posts/#{post}\"|\n        assert file =~ ~S|~p\"/posts/#{post}/edit\"|\n      end)\n\n      send(self(), {:mix_shell_input, :yes?, true})\n      Gen.Live.run(~w(Blog Comment comments title:string))\n      assert_received {:mix_shell, :info, [\"You are generating into an existing context\" <> _]}\n\n      assert_file(\"lib/phoenix/blog/comment.ex\")\n\n      assert_file(\"test/phoenix_web/live/comment_live_test.exs\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentLiveTest\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_comments.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:comments)\"\n        assert file =~ \"add :title, :string\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/comment_live/index.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentLive.Index\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/comment_live/show.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentLive.Show\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/comment_live/form.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.CommentLive.Form\"\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the live routes to your browser scope in lib/phoenix_web/router.ex:\n\n                            live \"/comments\", CommentLive.Index, :index\n                            live \"/comments/new\", CommentLive.Form, :new\n                            live \"/comments/:id\", CommentLive.Show, :show\n                            live \"/comments/:id/edit\", CommentLive.Form, :edit\n                        \"\"\"\n                      ]}\n\n      assert_receive(\n        {:mix_shell, :info,\n         [\n           \"\"\"\n\n           You must update :phoenix_live_view to v0.18 or later and\n           :phoenix_live_dashboard to v0.7 or later to use the features\n           in this generator.\n           \"\"\"\n         ]}\n      )\n    end)\n  end\n\n  test \"generates without explicit context\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Post posts title content:text slug:unique votes:integer cost:decimal\n                      tags:array:text popular:boolean drafted_at:datetime\n                      status:enum:unpublished:published:deleted\n                      published_at:utc_datetime\n                      published_at_usec:utc_datetime_usec\n                      deleted_at:naive_datetime\n                      deleted_at_usec:naive_datetime_usec\n                      alarm:time\n                      alarm_usec:time_usec\n                      secret:uuid:redact announcement_date:date alarm:time\n                      metadata:map\n                      weight:float user_id:references:users\n                     ))\n\n      assert_file(\"lib/phoenix/posts/post.ex\")\n      assert_file(\"lib/phoenix/posts.ex\")\n      assert_file(\"test/phoenix/posts_test.exs\")\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Index\"\n        refute file =~ \"dom_id:\"\n      end)\n    end)\n  end\n\n  test \"generates into existing context without prompt with --merge-with-existing-context\",\n       config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title))\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_post!\"\n        assert file =~ \"def list_posts\"\n        assert file =~ \"def create_post\"\n        assert file =~ \"def update_post\"\n        assert file =~ \"def delete_post\"\n        assert file =~ \"def change_post\"\n      end)\n\n      Gen.Live.run(~w(Blog Comment comments message:string --merge-with-existing-context))\n\n      refute_received {:mix_shell, :info,\n                       [\"You are generating into an existing context\" <> _notice]}\n\n      assert_file(\"lib/phoenix/blog.ex\", fn file ->\n        assert file =~ \"def get_comment!\"\n        assert file =~ \"def list_comments\"\n        assert file =~ \"def create_comment\"\n        assert file =~ \"def update_comment\"\n        assert file =~ \"def delete_comment\"\n        assert file =~ \"def change_comment\"\n      end)\n    end)\n  end\n\n  test \"with --web namespace generates namespaced web modules and directories\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title:string --web Blog))\n\n      assert_file(\"lib/phoenix/blog/post.ex\")\n      assert_file(\"lib/phoenix/blog.ex\")\n      assert_file(\"test/phoenix/blog_test.exs\")\n\n      assert_file(\"lib/phoenix_web/live/blog/post_live/index.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostLive.Index\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/blog/post_live/show.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostLive.Show\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/blog/post_live/form.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.Blog.PostLive.Form\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:posts)\"\n        assert file =~ \"add :title, :string\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/blog/post_live/index.ex\", fn file ->\n        assert file =~ ~S|~p\"/blog/posts/#{post}/edit\"|\n        assert file =~ ~S|~p\"/blog/posts/new\"|\n        assert file =~ ~S|~p\"/blog/posts/#{post}\"|\n      end)\n\n      assert_file(\"lib/phoenix_web/live/blog/post_live/show.ex\", fn file ->\n        assert file =~ ~S|~p\"/blog/posts\"|\n        assert file =~ ~S|~p\"/blog/posts/#{@post}/edit?return_to=show\"|\n      end)\n\n      assert_file(\"test/phoenix_web/live/blog/post_live_test.exs\", fn file ->\n        assert file =~ ~S|~p\"/blog/posts\"|\n        assert file =~ ~S|~p\"/blog/posts/new\"|\n        assert file =~ ~S|~p\"/blog/posts/#{post}/edit\"|\n      end)\n\n      assert_receive {:mix_shell, :info,\n                      [\n                        \"\"\"\n\n                        Add the live routes to your Blog :browser scope in lib/phoenix_web/router.ex:\n\n                            scope \"/blog\", PhoenixWeb.Blog do\n                              pipe_through :browser\n                              ...\n\n                              live \"/posts\", PostLive.Index, :index\n                              live \"/posts/new\", PostLive.Form, :new\n                              live \"/posts/:id\", PostLive.Show, :show\n                              live \"/posts/:id/edit\", PostLive.Form, :edit\n                            end\n                        \"\"\"\n                      ]}\n    end)\n  end\n\n  test \"with --no-context skips context and schema file generation\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title:string --no-context))\n\n      refute_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/post.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\")\n      assert_file(\"lib/phoenix_web/live/post_live/show.ex\")\n      assert_file(\"lib/phoenix_web/live/post_live/form.ex\")\n\n      assert_file(\"test/phoenix_web/live/post_live_test.exs\")\n    end)\n  end\n\n  test \"with --no-schema skips schema file generation\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title:string --no-schema))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      refute_file(\"lib/phoenix/blog/post.ex\")\n      assert Path.wildcard(\"priv/repo/migrations/*.exs\") == []\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\")\n      assert_file(\"lib/phoenix_web/live/post_live/show.ex\")\n      assert_file(\"lib/phoenix_web/live/post_live/form.ex\")\n\n      assert_file(\"test/phoenix_web/live/post_live_test.exs\")\n    end)\n  end\n\n  test \"with --no-context does not emit warning when context exists\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title:string))\n\n      assert_file(\"lib/phoenix/blog.ex\")\n      assert_file(\"lib/phoenix/blog/post.ex\")\n\n      Gen.Live.run(~w(Blog Comment comments title:string --no-context))\n      refute_received {:mix_shell, :info, [\"You are generating into an existing context\" <> _]}\n\n      assert_file(\"lib/phoenix_web/live/comment_live/index.ex\")\n      assert_file(\"lib/phoenix_web/live/comment_live/show.ex\")\n      assert_file(\"lib/phoenix_web/live/comment_live/form.ex\")\n\n      assert_file(\"test/phoenix_web/live/comment_live_test.exs\")\n    end)\n  end\n\n  test \"with same singular and plural\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Tracker Series series value:integer))\n\n      assert_file(\"lib/phoenix/tracker.ex\")\n      assert_file(\"lib/phoenix/tracker/series.ex\")\n\n      assert_file(\"lib/phoenix_web/live/series_live/index.ex\", fn file ->\n        assert file =~ \"|> stream(:series_collection, list_series())\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/series_live/show.ex\")\n      assert_file(\"lib/phoenix_web/live/series_live/form.ex\")\n\n      assert_file(\"lib/phoenix_web/live/series_live/index.ex\", fn file ->\n        assert file =~ \"@streams.series_collection\"\n      end)\n\n      assert_file(\"test/phoenix_web/live/series_live_test.exs\")\n    end)\n  end\n\n  test \"when more than 50 attributes are given\", config do\n    in_tmp_live_project(config.test, fn ->\n      long_attribute_list = Enum.map_join(0..55, \" \", &\"attribute#{&1}:string\")\n      Gen.Live.run(~w(Blog Post posts title #{long_attribute_list}))\n\n      assert_file(\"test/phoenix/blog_test.exs\", fn file ->\n        refute file =~ \"...}\"\n      end)\n\n      assert_file(\"test/phoenix_web/live/post_live_test.exs\", fn file ->\n        refute file =~ \"...}\"\n      end)\n    end)\n  end\n\n  describe \"inside umbrella\" do\n    test \"without context_app generators config uses web dir\", config do\n      in_tmp_live_umbrella_project(config.test, fn ->\n        File.cd!(\"phoenix_web\")\n\n        Application.put_env(:phoenix, :generators, context_app: nil)\n        Gen.Live.run(~w(Accounts User users name:string))\n\n        assert_file(\"lib/phoenix/accounts.ex\")\n        assert_file(\"lib/phoenix/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix_web/live/user_live/index.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserLive.Index\"\n          assert file =~ \"use PhoenixWeb, :live_view\"\n        end)\n\n        assert_file(\"lib/phoenix_web/live/user_live/show.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserLive.Show\"\n          assert file =~ \"use PhoenixWeb, :live_view\"\n        end)\n\n        assert_file(\"lib/phoenix_web/live/user_live/form.ex\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserLive.Form\"\n          assert file =~ \"use PhoenixWeb, :live_view\"\n        end)\n\n        assert_file(\"test/phoenix_web/live/user_live_test.exs\", fn file ->\n          assert file =~ \"defmodule PhoenixWeb.UserLiveTest\"\n        end)\n      end)\n    end\n\n    test \"raises with false context_app\", config do\n      in_tmp_live_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: false)\n\n        assert_raise Mix.Error, ~r/no context_app configured/, fn ->\n          Gen.Live.run(~w(Accounts User users name:string))\n        end\n      end)\n    end\n\n    test \"with context_app generators config does not use web dir\", config do\n      in_tmp_live_umbrella_project(config.test, fn ->\n        File.mkdir!(\"another_app\")\n        Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n\n        File.cd!(\"phoenix\")\n\n        Gen.Live.run(~w(Accounts User users name:string))\n\n        assert_file(\"another_app/lib/another_app/accounts.ex\")\n        assert_file(\"another_app/lib/another_app/accounts/user.ex\")\n\n        assert_file(\"lib/phoenix/live/user_live/index.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserLive.Index\"\n          assert file =~ \"use Phoenix, :live_view\"\n        end)\n\n        assert_file(\"lib/phoenix/live/user_live/show.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserLive.Show\"\n          assert file =~ \"use Phoenix, :live_view\"\n        end)\n\n        assert_file(\"lib/phoenix/live/user_live/form.ex\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserLive.Form\"\n          assert file =~ \"use Phoenix, :live_view\"\n        end)\n\n        assert_file(\"test/phoenix/live/user_live_test.exs\", fn file ->\n          assert file =~ \"defmodule Phoenix.UserLiveTest\"\n        end)\n      end)\n    end\n  end\n\n  test \"with custom primary key\", config do\n    in_tmp_live_project(config.test, fn ->\n      Gen.Live.run(~w(Blog Post posts title:string --primary-key post_id))\n\n      assert_file(\"lib/phoenix_web/live/post_live/index.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Index\"\n        assert file =~ ~S[dom_id: &\"posts-#{&1.post_id}\"]\n        assert file =~ ~s[JS.push(\"delete\", value: %{post_id: post.post_id})]\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/show.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Show\"\n        assert file =~ ~s[def mount(%{\"post_id\" => post_id}, _session, socket)]\n        assert file =~ \"Blog.get_post!(post_id)\"\n      end)\n\n      assert_file(\"lib/phoenix_web/live/post_live/form.ex\", fn file ->\n        assert file =~ \"defmodule PhoenixWeb.PostLive.Form\"\n        assert file =~ ~s[defp apply_action(socket, :edit, %{\"post_id\" => post_id}) do]\n      end)\n    end)\n  end\n\n  test \"raises on schema named form\" do\n    assert_raise Mix.Error,\n                 ~r/cannot use form as the schema name because it conflicts with the LiveView assigns/,\n                 fn ->\n                   Mix.Tasks.Phx.Gen.Live.run(~w(Blog Form forms title:string))\n                 end\n  end\n\n  test \"respect route_prefix in scopes\", config do\n    in_tmp_live_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          organization: [\n            module: Phoenix.Organizations.Scope,\n            assign_key: :current_organization,\n            access_path: [:organization, :id],\n            route_access_path: [:organization, :slug],\n            route_prefix: \"/orgs/:slug\"\n          ]\n        ],\n        fn ->\n          Gen.Live.run(~w(Blog Post posts title:string --scope organization))\n\n          assert_file(\"lib/phoenix_web/live/post_live/index.ex\", fn file ->\n            assert file =~\n                     ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts|\n\n            assert file =~\n                     ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts/new\"|\n          end)\n\n          assert_file(\"lib/phoenix_web/live/post_live/show.ex\", fn file ->\n            assert file =~\n                     ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts\"|\n\n            assert file =~\n                     ~s|navigate={~p\"/orgs/\\#{@current_organization.organization.slug}/posts/\\#{@post}/edit|\n          end)\n\n          assert_file(\"lib/phoenix_web/live/post_live/form.ex\", fn file ->\n            assert file =~\n                     ~s|defp return_path(scope, \"index\", _post), do: ~p\"/orgs/\\#{scope.organization.slug}/posts\"|\n          end)\n\n          assert_file(\"test/phoenix_web/live/post_live_test.exs\", fn file ->\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts\"|\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/new\"|\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/\\#{post}\"|\n            assert file =~ ~s|~p\"/orgs/\\#{scope.organization.slug}/posts/\\#{post}/edit\"|\n          end)\n        end\n      )\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.notifier_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule MyTestApp.Mailer do\nend\n\ndefmodule Mix.Tasks.Phx.Gen.NotifierTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"new notifier\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Notifier.run(~w(Accounts User welcome_user reset_password --no-compile))\n\n      assert_file(\"lib/phoenix/accounts/user_notifier.ex\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Accounts.UserNotifier do|\n        assert file =~ ~S|import Swoosh.Email|\n        assert file =~ ~S|alias Phoenix.Mailer|\n\n        assert file =~ ~S|def deliver_welcome_user(%{name: name, email: email}) do|\n        assert file =~ ~S|from({\"Phoenix Team\", \"team@example.com\"})|\n\n        assert file =~ ~S|def deliver_reset_password(%{name: name, email: email}) do|\n        assert file =~ ~S|from({\"Phoenix Team\", \"team@example.com\"})|\n\n        assert file =~ ~S|Mailer.deliver()|\n      end)\n\n      assert_file(\"test/phoenix/accounts/user_notifier_test.exs\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Accounts.UserNotifierTest do|\n      end)\n\n      send(self(), {:mix_shell_input, :yes?, true})\n      send(self(), {:mix_shell_input, :yes?, true})\n      send(self(), {:mix_shell_input, :yes?, true})\n\n      Gen.Notifier.run(~w(Accounts User account_confirmation --no-compile))\n\n      assert_received {:mix_shell, :info,\n                       [\"The following files conflict with new files to be generated:\" <> notice]}\n\n      assert notice =~ \"user_notifier.ex\"\n      assert notice =~ \"user_notifier_test.exs\"\n\n      assert_received {:mix_shell, :yes?, [question]}\n\n      assert question =~ \"Proceed with interactive overwrite?\"\n\n      assert_file(\"lib/phoenix/accounts/user_notifier.ex\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Accounts.UserNotifier do|\n        assert file =~ ~S|import Swoosh.Email|\n        assert file =~ ~S|alias Phoenix.Mailer|\n\n        assert file =~ ~S|def deliver_account_confirmation(%{name: name, email: email}) do|\n        assert file =~ ~S|from({\"Phoenix Team\", \"team@example.com\"})|\n\n        refute file =~ ~S|def deliver_welcome_user(%{name: name, email: email}) do|\n        refute file =~ ~S|def deliver_reset_password(%{name: name, email: email}) do|\n      end)\n    end)\n  end\n\n  test \"generates nested notifier\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Notifier.run(~w(Admin.Accounts User welcome_user reset_password --no-compile))\n\n      assert_file(\"lib/phoenix/admin/accounts/user_notifier.ex\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Admin.Accounts.UserNotifier do|\n        assert file =~ ~S|import Swoosh.Email|\n        assert file =~ ~S|alias Phoenix.Mailer|\n\n        assert file =~ ~S|def deliver_welcome_user(%{name: name, email: email}) do|\n        assert file =~ ~S|from({\"Phoenix Team\", \"team@example.com\"})|\n\n        assert file =~ ~S|def deliver_reset_password(%{name: name, email: email}) do|\n        assert file =~ ~S|from({\"Phoenix Team\", \"team@example.com\"})|\n\n        assert file =~ ~S|Mailer.deliver()|\n      end)\n    end)\n  end\n\n  test \"in an umbrella with a context_app, generates the notifier\", config do\n    in_tmp_umbrella_project(config.test, fn ->\n      Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n      Gen.Notifier.run(~w(Accounts User welcome_user reset_password --no-compile))\n\n      assert_file(\"another_app/lib/another_app/accounts/user_notifier.ex\", fn file ->\n        assert file =~ ~S|defmodule AnotherApp.Accounts.UserNotifier do|\n        assert file =~ ~S|import Swoosh.Email|\n        assert file =~ ~S|alias AnotherApp.Mailer|\n\n        assert file =~ ~S|Mailer.deliver()|\n      end)\n    end)\n  end\n\n  test \"invalid mix arguments\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/Expected the context, \"blog\", to be a valid module name/, fn ->\n        Gen.Notifier.run(~w(blog Post new_post --no-compile))\n      end\n\n      assert_raise Mix.Error, ~r/Expected the notifier, \"posts\", to be a valid module name/, fn ->\n        Gen.Notifier.run(~w(Post posts new_post --no-compile))\n      end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate context Phoenix because it has the same name as the application/,\n                   fn ->\n                     Gen.Notifier.run(~w(Phoenix Post new_blog_post --no-compile))\n                   end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate notifier Phoenix because it has the same name as the application/,\n                   fn ->\n                     Gen.Notifier.run(~w(Blog Phoenix new_blog_post --no-compile))\n                   end\n\n      assert_raise Mix.Error,\n                   ~r/Cannot generate notifier \"Post\" because one of the messages is invalid: \"NewPost\"/,\n                   fn ->\n                     Gen.Notifier.run(~w(Blog Post NewPost --no-compile))\n                   end\n    end)\n  end\n\n  test \"maybe_print_mailer_installation_instructions/1\", config do\n    in_tmp_project(config.test, fn ->\n      context = Mix.Phoenix.Context.new(MyContext.UserNotifier, [])\n\n      Gen.Notifier.maybe_print_mailer_installation_instructions(context)\n\n      assert_received {:mix_shell, :info, [\"Unable to find the \\\"Phoenix.Mailer\\\"\" <> notice]}\n\n      assert notice =~ ~s(A mailer module like the following is expected to be defined)\n      assert notice =~ ~s(in your application in order to send emails.)\n      assert notice =~ ~s(defmodule Phoenix.Mailer do)\n      assert notice =~ ~s(use Swoosh.Mailer, otp_app: :phoenix)\n      assert notice =~ ~s(def deps do)\n      assert notice =~ ~s(https://hexdocs.pm/swoosh)\n\n      context_with_mailer = %{context | base_module: MyTestApp}\n\n      Gen.Notifier.maybe_print_mailer_installation_instructions(context_with_mailer)\n\n      refute_received {:mix_shell, :info, _info}\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.presence_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.PresenceTest do\n  use ExUnit.Case\n  import MixHelper\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"generates presence\" do\n    in_tmp_project(\"generates presence\", fn ->\n      Mix.Tasks.Phx.Gen.Presence.run([\"MyPresence\"])\n\n      assert_file(\"lib/phoenix_web/channels/my_presence.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.MyPresence do|\n        assert file =~ ~S|use Phoenix.Presence|\n        assert file =~ ~S|otp_app: :phoenix|\n        assert file =~ ~S|pubsub_server: Phoenix.PubSub|\n      end)\n    end)\n  end\n\n  test \"passing no args defaults to Presence\" do\n    in_tmp_project(\"generates presence\", fn ->\n      Mix.Tasks.Phx.Gen.Presence.run([])\n\n      assert_file(\"lib/phoenix_web/channels/presence.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.Presence do|\n        assert file =~ ~S|use Phoenix.Presence|\n        assert file =~ ~S|otp_app: :phoenix|\n        assert file =~ ~S|pubsub_server: Phoenix.PubSub|\n      end)\n    end)\n  end\n\n  test \"in an umbrella with a context_app, the file goes in lib/app/channels\" do\n    in_tmp_umbrella_project(\"generates presences\", fn ->\n      Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n      Mix.Tasks.Phx.Gen.Presence.run([])\n      assert_file(\"lib/phoenix/channels/presence.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.Presence do|\n        assert file =~ ~S|use Phoenix.Presence|\n        assert file =~ ~S|otp_app: :phoenix|\n        assert file =~ ~S|pubsub_server: AnotherApp.PubSub|\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.release_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Mix.Tasks.Phx.Gen.ReleaseTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  @moduletag :capture_log\n\n  setup do\n    Mix.Task.clear()\n\n    Process.put({Mix.Tasks.Phx.Gen.Release, :http_client}, fn url ->\n      case to_string(url) do\n        \"https://hub.docker.com/v2/namespaces/hexpm/repositories/elixir/tags?\" <> _ ->\n          Phoenix.json_library().encode!(%{\n            results: [%{name: \"1.18.4-erlang-25.3.2.17-debian-trixie-20251117-slim\"}]\n          })\n      end\n    end)\n\n    :ok\n  end\n\n  test \"generates release files\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Release.run([\"--ecto\"])\n\n      assert_file(\"lib/phoenix/release.ex\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Release do|\n        assert file =~ ~S|@app :phoenix|\n      end)\n\n      assert_file(\"rel/overlays/bin/migrate\", fn file ->\n        assert file =~ ~S|exec ./phoenix eval Phoenix.Release.migrate|\n      end)\n\n      assert_file(\"rel/overlays/bin/migrate.bat\", fn file ->\n        assert file =~ ~S|call \"%~dp0\\phoenix\" eval Phoenix.Release.migrate|\n      end)\n\n      assert_file(\"rel/overlays/bin/server\", fn file ->\n        assert file =~ ~S|PHX_SERVER=true exec ./phoenix start|\n      end)\n\n      assert_file(\"rel/overlays/bin/server.bat\", fn file ->\n        assert file =~ \"set PHX_SERVER=true\\ncall \\\"%~dp0\\\\phoenix\\\" start\"\n      end)\n\n      refute_file(\"Dockerfile\")\n      refute_file(\".dockerignore\")\n\n      refute_receive {:mix_shell, :info, [\"* creating Dockerfile\"]}\n      refute_receive {:mix_shell, :info, [\"* creating .dockerignore\"]}\n      assert_receive {:mix_shell, :info, [\"* creating lib/phoenix/release.ex\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/migrate\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/migrate.bat\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/server\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/server.bat\"]}\n      assert_receive {:mix_shell, :info, [\"\\nYour application is ready to be deployed\" <> _]}\n    end)\n  end\n\n  test \"generates release files without ecto\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Release.run([])\n\n      assert_file(\"rel/overlays/bin/server\", fn file ->\n        assert file =~ ~S|PHX_SERVER=true exec ./phoenix start|\n      end)\n\n      refute_file(\"lib/phoenix/release.ex\")\n      refute_file(\"rel/overlays/bin/migrate\")\n      refute_file(\"rel/overlays/bin/migrate.bat\")\n      refute_file(\"Dockerfile\")\n      refute_file(\".dockerignore\")\n\n      refute_receive {:mix_shell, :info, [\"* creating Dockerfile\"]}\n      refute_receive {:mix_shell, :info, [\"* creating .dockerignore\"]}\n      refute_receive {:mix_shell, :info, [\"* creating lib/phoenix/release.ex\"]}\n      refute_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/migrate\"]}\n      refute_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/migrate.bat\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/server\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/server.bat\"]}\n      assert_receive {:mix_shell, :info, [\"\\nYour application is ready to be deployed\" <> _]}\n    end)\n  end\n\n  test \"generates release and docker files\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Release.run([\"--docker\", \"--ecto\"])\n\n      assert_file(\"lib/phoenix/release.ex\", fn file ->\n        assert file =~ ~S|defmodule Phoenix.Release do|\n        assert file =~ ~S|@app :phoenix|\n      end)\n\n      assert_file(\"Dockerfile\", fn file ->\n        assert file =~\n                 ~S|COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/phoenix ./|\n\n        assert file =~ ~S|CMD [\"/app/bin/server\"]|\n      end)\n\n      assert_file(\"rel/overlays/bin/migrate\", fn file ->\n        assert file =~ ~S|exec ./phoenix eval Phoenix.Release.migrate|\n      end)\n\n      assert_file(\"rel/overlays/bin/migrate.bat\", fn file ->\n        assert file =~ ~S|call \"%~dp0\\phoenix\" eval Phoenix.Release.migrate|\n      end)\n\n      assert_file(\"rel/overlays/bin/server\", fn file ->\n        assert file =~ ~S|PHX_SERVER=true exec ./phoenix start|\n      end)\n\n      assert_file(\"rel/overlays/bin/server.bat\", fn file ->\n        assert file =~ \"set PHX_SERVER=true\\ncall \\\"%~dp0\\\\phoenix\\\" start\"\n      end)\n\n      assert_file(\".dockerignore\")\n\n      assert_receive {:mix_shell, :info, [\"* creating Dockerfile\"]}\n      assert_receive {:mix_shell, :info, [\"* creating .dockerignore\"]}\n      assert_receive {:mix_shell, :info, [\"* creating lib/phoenix/release.ex\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/migrate\"]}\n      assert_receive {:mix_shell, :info, [\"* creating rel/overlays/bin/server\"]}\n      assert_receive {:mix_shell, :info, [\"\\nYour application is ready to be deployed\" <> _]}\n    end)\n  end\n\n  test \"generates release and docker files with assets dir\", config do\n    in_tmp_project(config.test, fn ->\n      File.mkdir_p!(\"assets\")\n      Gen.Release.run([\"--docker\"])\n\n      assert_file(\"Dockerfile\", fn file ->\n        assert file =~ ~S|RUN mix assets.setup|\n        assert file =~ ~S|COPY assets assets|\n        assert file =~ ~S|RUN mix assets.deploy|\n      end)\n    end)\n  end\n\n  test \"generates release and docker files without assets dir\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Release.run([\"--docker\"])\n\n      assert_file(\"Dockerfile\", fn file ->\n        refute file =~ ~S|RUN mix assets.setup|\n        refute file =~ ~S|COPY assets assets|\n        refute file =~ ~S|RUN mix assets.deploy|\n      end)\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.schema_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule Phoenix.DupSchema do\nend\n\ndefmodule Mix.Tasks.Phx.Gen.SchemaTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n  alias Mix.Phoenix.Schema\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"build\" do\n    in_tmp_project(\"build\", fn ->\n      schema = Gen.Schema.build(~w(Blog.Post posts title:string tags:map), [])\n\n      assert %Schema{\n               alias: Post,\n               module: Phoenix.Blog.Post,\n               repo: Phoenix.Repo,\n               migration?: true,\n               migration_defaults: %{title: \"\"},\n               plural: \"posts\",\n               singular: \"post\",\n               human_plural: \"Posts\",\n               human_singular: \"Post\",\n               attrs: [title: :string, tags: :map],\n               types: [title: :string, tags: :map],\n               optionals: [:tags],\n               route_helper: \"post\",\n               defaults: %{title: \"\", tags: \"\"},\n               scope: nil\n             } = schema\n\n      assert String.ends_with?(schema.file, \"lib/phoenix/blog/post.ex\")\n    end)\n  end\n\n  test \"build with default scope\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :id,\n            schema_table: :users\n          ]\n        ],\n        fn ->\n          schema = Gen.Schema.build(~w(Blog.Post posts title:string), [])\n\n          assert %Schema{\n                   scope: %Mix.Phoenix.Scope{\n                     name: :user,\n                     default: true,\n                     module: MyApp.Accounts.Scope,\n                     assign_key: :current_scope,\n                     access_path: [:user, :id],\n                     schema_key: :user_id,\n                     schema_type: :id,\n                     schema_table: :users\n                   }\n                 } = schema\n        end\n      )\n    end)\n  end\n\n  test \"build with specific scope\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: false,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :id,\n            schema_table: :users\n          ],\n          org: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :org_id],\n            schema_key: :org_id,\n            schema_type: :id,\n            schema_table: :organizations\n          ]\n        ],\n        fn ->\n          schema = Gen.Schema.build(~w(Blog.Post posts title:string --scope org), [])\n\n          assert %Schema{\n                   scope: %Mix.Phoenix.Scope{\n                     name: :org,\n                     default: true,\n                     module: MyApp.Accounts.Scope,\n                     assign_key: :current_scope,\n                     access_path: [:user, :org_id],\n                     schema_key: :org_id,\n                     schema_type: :id,\n                     schema_table: :organizations\n                   }\n                 } = schema\n        end\n      )\n    end)\n  end\n\n  test \"build with nested web namespace\", config do\n    in_tmp_project(config.test, fn ->\n      schema = Gen.Schema.build(~w(Blog.Post posts title:string --web API.V1), [])\n\n      assert %Schema{\n               alias: Post,\n               module: Phoenix.Blog.Post,\n               repo: Phoenix.Repo,\n               migration?: true,\n               migration_defaults: %{title: \"\"},\n               plural: \"posts\",\n               singular: \"post\",\n               human_plural: \"Posts\",\n               human_singular: \"Post\",\n               attrs: [title: :string],\n               types: [title: :string],\n               route_helper: \"api_v1_post\",\n               defaults: %{title: \"\"},\n               scope: nil\n             } = schema\n\n      assert String.ends_with?(schema.file, \"lib/phoenix/blog/post.ex\")\n    end)\n  end\n\n  test \"table name missing from references\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, ~r/expect the table to be given to user_id:references/, fn ->\n        Gen.Schema.run(~w(Blog.Post posts user_id:references))\n      end\n    end)\n  end\n\n  test \"type missing from array\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error,\n                   ~r/expect the type of the array to be given to settings:array/,\n                   fn ->\n                     Gen.Schema.run(~w(Blog.Post posts settings:array))\n                   end\n    end)\n  end\n\n  test \"plural can't contain a colon\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Schema.run(~w(Blog Post title:string))\n    end\n  end\n\n  test \"plural can't have uppercased characters or camelized format\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Schema.run(~w(Blog Post Posts title:string))\n    end\n\n    assert_raise Mix.Error, fn ->\n      Gen.Schema.run(~w(Blog Post BlogPosts title:string))\n    end\n  end\n\n  test \"table name omitted\", config do\n    in_tmp_project(config.test, fn ->\n      assert_raise Mix.Error, fn ->\n        Gen.Schema.run(~w(Blog.Post))\n      end\n    end)\n  end\n\n  test \"generates schema\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post blog_posts title:string))\n      assert_file(\"lib/phoenix/blog/post.ex\")\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_blog_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:blog_posts) do\"\n      end)\n    end)\n  end\n\n  test \"allows a custom repo\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post blog_posts title:string --repo MyApp.CustomRepo))\n\n      assert [migration] = Path.wildcard(\"priv/custom_repo/migrations/*_create_blog_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule MyApp.CustomRepo.Migrations.CreateBlogPosts do\"\n      end)\n    end)\n  end\n\n  test \"allows a custom migration dir\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post blog_posts title:string --migration-dir priv/custom_dir))\n\n      assert [migration] = Path.wildcard(\"priv/custom_dir/*_create_blog_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule Phoenix.Repo.Migrations.CreateBlogPosts do\"\n      end)\n    end)\n  end\n\n  test \"custom migration_dir takes precedence over custom repo name\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post blog_posts title:string \\\n        --repo MyApp.CustomRepo --migration-dir priv/custom_dir))\n\n      assert [migration] = Path.wildcard(\"priv/custom_dir/*_create_blog_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule MyApp.CustomRepo.Migrations.CreateBlogPosts do\"\n      end)\n    end)\n  end\n\n  test \"does not add maps to the required list\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post blog_posts title:string tags:map published_at:naive_datetime))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"cast(attrs, [:title, :tags, :published_at]\"\n        assert file =~ \"validate_required([:title, :published_at]\"\n      end)\n    end)\n  end\n\n  test \"generates nested schema\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Admin.User users name:string))\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_users.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule Phoenix.Repo.Migrations.CreateUsers do\"\n        assert file =~ \"create table(:users) do\"\n      end)\n\n      assert_file(\"lib/phoenix/blog/admin/user.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.Blog.Admin.User do\"\n        assert file =~ \"schema \\\"users\\\" do\"\n      end)\n    end)\n  end\n\n  test \"generates custom table name\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts --table cms_posts))\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_cms_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:cms_posts) do\"\n      end)\n    end)\n  end\n\n  test \"generates unique indices\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts title:unique secret:redact unique_int:integer:unique))\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule Phoenix.Repo.Migrations.CreatePosts do\"\n        assert file =~ \"create table(:posts) do\"\n        assert file =~ \"add :title, :string\"\n        assert file =~ \"add :unique_int, :integer\"\n        assert file =~ \"add :secret, :string\"\n        assert file =~ \"create unique_index(:posts, [:title])\"\n        assert file =~ \"create unique_index(:posts, [:unique_int])\"\n      end)\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.Blog.Post do\"\n        assert file =~ \"schema \\\"posts\\\" do\"\n        assert file =~ \"field :title, :string\"\n        assert file =~ \"field :unique_int, :integer\"\n        assert file =~ \"field :secret, :string, redact: true\"\n      end)\n    end)\n  end\n\n  test \"generates references and belongs_to associations\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts title user_id:references:users))\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"add :user_id, references(:users, on_delete: :nothing)\"\n        assert file =~ \"create index(:posts, [:user_id])\"\n      end)\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"field :user_id, :id\"\n      end)\n    end)\n  end\n\n  test \"generates references with unique indexes\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(\n        ~w(Blog.Post posts title user_id:references:users unique_post_id:references:posts:unique)\n      )\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"defmodule Phoenix.Repo.Migrations.CreatePosts do\"\n        assert file =~ \"create table(:posts) do\"\n        assert file =~ \"add :user_id, references(:users, on_delete: :nothing)\"\n        assert file =~ \"add :unique_post_id, references(:posts, on_delete: :nothing)\"\n        assert file =~ \"create index(:posts, [:user_id])\"\n        assert file =~ \"create unique_index(:posts, [:unique_post_id])\"\n      end)\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"defmodule Phoenix.Blog.Post do\"\n        assert file =~ \"field :user_id, :id\"\n        assert file =~ \"field :unique_post_id, :id\"\n      end)\n    end)\n  end\n\n  test \"generates schema with proper datetime types\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(\n        ~w(Blog.Comment comments title:string drafted_at:datetime published_at:naive_datetime edited_at:utc_datetime locked_at:naive_datetime_usec)\n      )\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :drafted_at, :naive_datetime\"\n        assert file =~ \"field :published_at, :naive_datetime\"\n        assert file =~ \"field :locked_at, :naive_datetime_usec\"\n        assert file =~ \"field :edited_at, :utc_datetime\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_comments.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:comments)\"\n        assert file =~ \"add :drafted_at, :naive_datetime\"\n        assert file =~ \"add :published_at, :naive_datetime\"\n        assert file =~ \"add :edited_at, :utc_datetime\"\n      end)\n    end)\n  end\n\n  test \"generates schema with enum\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(\n        ~w(Blog.Comment comments title:string status:enum:unpublished:published:deleted)\n      )\n\n      assert_file(\"lib/phoenix/blog/comment.ex\", fn file ->\n        assert file =~ \"field :status, Ecto.Enum, values: [:unpublished, :published, :deleted]\"\n      end)\n\n      assert [path] = Path.wildcard(\"priv/repo/migrations/*_create_comments.exs\")\n\n      assert_file(path, fn file ->\n        assert file =~ \"create table(:comments)\"\n        assert file =~ \"add :status, :string\"\n      end)\n    end)\n  end\n\n  test \"generates migration with binary_id\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts title user_id:references:users --binary-id))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"field :user_id, :binary_id\"\n      end)\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:posts, primary_key: false) do\"\n        assert file =~ \"add :id, :binary_id, primary_key: true\"\n        assert file =~ \"add :user_id, references(:users, on_delete: :nothing, type: :binary_id)\"\n      end)\n    end)\n  end\n\n  test \"generates migration with custom primary key\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(\n        ~w(Blog.Post posts title user_id:references:users --binary-id --primary-key post_id)\n      )\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"@derive {Phoenix.Param, key: :post_id}\"\n        assert file =~ \"@primary_key {:post_id, :binary_id, autogenerate: true}\"\n        assert file =~ \"field :user_id, :binary_id\"\n      end)\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:posts, primary_key: false) do\"\n        assert file =~ \"add :post_id, :binary_id, primary_key: true\"\n        assert file =~ \"add :user_id, references(:users, on_delete: :nothing, type: :binary_id)\"\n      end)\n    end)\n  end\n\n  test \"generates schema and migration with prefix\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts title --prefix cms))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"@schema_prefix :cms\"\n      end)\n\n      assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n      assert_file(migration, fn file ->\n        assert file =~ \"create table(:posts, prefix: :cms) do\"\n      end)\n    end)\n  end\n\n  test \"skips migration with --no-migration option\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts --no-migration))\n      assert [] = Path.wildcard(\"priv/repo/migrations/*\")\n    end)\n  end\n\n  test \"uses defaults from :generators configuration\" do\n    in_tmp_project(\"uses defaults from generators configuration (migration)\", fn ->\n      with_generator_env([migration: false], fn ->\n        Gen.Schema.run(~w(Blog.Post posts))\n\n        assert [] = Path.wildcard(\"priv/repo/migrations/*\")\n      end)\n    end)\n\n    in_tmp_project(\"uses defaults from generators configuration (binary_id)\", fn ->\n      with_generator_env([binary_id: true], fn ->\n        Gen.Schema.run(~w(Blog.Post posts))\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ \"create table(:posts, primary_key: false) do\"\n          assert file =~ \"add :id, :binary_id, primary_key: true\"\n        end)\n      end)\n    end)\n\n    in_tmp_project(\"uses defaults from generators configuration (:utc_datetime)\", fn ->\n      with_generator_env([timestamp_type: :utc_datetime], fn ->\n        Gen.Schema.run(~w(Blog.Post posts))\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ \"timestamps(type: :utc_datetime)\"\n        end)\n\n        assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n          assert file =~ \"timestamps(type: :utc_datetime)\"\n        end)\n      end)\n    end)\n  end\n\n  test \"generates migrations with a custom migration module\", config do\n    in_tmp_project(config.test, fn ->\n      try do\n        Application.put_env(:ecto_sql, :migration_module, MyCustomApp.MigrationModule)\n\n        Gen.Schema.run(~w(Blog.Post posts))\n\n        assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_posts.exs\")\n\n        assert_file(migration, fn file ->\n          assert file =~ \"use MyCustomApp.MigrationModule\"\n          assert file =~ \"create table(:posts) do\"\n        end)\n      after\n        Application.delete_env(:ecto_sql, :migration_module)\n      end\n    end)\n  end\n\n  test \"generates schema without extra line break\", config do\n    in_tmp_project(config.test, fn ->\n      Gen.Schema.run(~w(Blog.Post posts title))\n\n      assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n        assert file =~ \"import Ecto.Changeset\\n\\n  schema\"\n      end)\n    end)\n  end\n\n  describe \"inside umbrella\" do\n    test \"raises with false context_app\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: false)\n\n        assert_raise Mix.Error, ~r/no context_app configured/, fn ->\n          Gen.Schema.run(~w(Blog.Post blog_posts title:string))\n        end\n      end)\n    end\n\n    test \"with context_app set to nil\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: nil)\n\n        Gen.Schema.run(~w(Blog.Post blog_posts title:string))\n\n        assert_file(\"lib/phoenix/blog/post.ex\")\n        assert [_] = Path.wildcard(\"priv/repo/migrations/*_create_blog_posts.exs\")\n      end)\n    end\n\n    test \"with context_app\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n\n        Gen.Schema.run(~w(Blog.Post blog_posts title:string))\n\n        assert_file(\"another_app/lib/another_app/blog/post.ex\")\n        assert [_] = Path.wildcard(\"another_app/priv/repo/migrations/*_create_blog_posts.exs\")\n      end)\n    end\n\n    test \"generates scoped schema\", config do\n      in_tmp_umbrella_project(config.test, fn ->\n        Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n\n        with_scope_env(\n          :another_app,\n          [\n            org: [\n              default: true,\n              module: MyApp.Accounts.Scope,\n              assign_key: :current_scope,\n              access_path: [:user, :org_id],\n              schema_key: :org_id,\n              schema_type: :id,\n              schema_table: :organizations\n            ]\n          ],\n          fn ->\n            Gen.Schema.run(~w(Blog.Post blog_posts title:string --scope org --binary-id))\n\n            assert_file(\"another_app/lib/another_app/blog/post.ex\", fn file ->\n              assert file =~ \"@primary_key {:id, :binary_id, autogenerate: true}\"\n              assert file =~ \"field :org_id, :id\"\n            end)\n\n            assert [migration] =\n                     Path.wildcard(\"another_app/priv/repo/migrations/*_create_blog_posts.exs\")\n\n            assert_file(migration, fn file ->\n              assert file =~ \"create table(:blog_posts, primary_key: false) do\"\n              assert file =~ \"add :id, :binary_id, primary_key: true\"\n\n              assert file =~\n                       \"add :org_id, references(:organizations, type: :id, on_delete: :delete_all)\"\n\n              assert file =~ \"create index(:blog_posts, [:org_id])\"\n            end)\n          end\n        )\n      end)\n    end\n  end\n\n  test \"generates scoped schema\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          org: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :org_id],\n            schema_key: :org_id,\n            schema_type: :id,\n            schema_table: :organizations\n          ]\n        ],\n        fn ->\n          Gen.Schema.run(~w(Blog.Post blog_posts title:string --scope org --binary-id))\n\n          assert_file(\"lib/phoenix/blog/post.ex\", fn file ->\n            assert file =~ \"@primary_key {:id, :binary_id, autogenerate: true}\"\n            assert file =~ \"field :org_id, :id\"\n          end)\n\n          assert [migration] = Path.wildcard(\"priv/repo/migrations/*_create_blog_posts.exs\")\n\n          assert_file(migration, fn file ->\n            assert file =~ \"create table(:blog_posts, primary_key: false) do\"\n            assert file =~ \"add :id, :binary_id, primary_key: true\"\n\n            assert file =~\n                     \"add :org_id, references(:organizations, type: :id, on_delete: :delete_all)\"\n\n            assert file =~ \"create index(:blog_posts, [:org_id])\"\n          end)\n        end\n      )\n    end)\n  end\n\n  test \"raises when there are multiple default scopes\" do\n    with_scope_env(\n      :phoenix,\n      [\n        org: [\n          default: true,\n          module: MyApp.Accounts.Scope,\n          assign_key: :current_scope,\n          access_path: [:user, :org_id],\n          schema_key: :org_id,\n          schema_type: :id,\n          schema_table: :organizations\n        ],\n        user: [\n          default: true,\n          module: MyApp.Accounts.Scope,\n          assign_key: :current_scope,\n          access_path: [:user, :id],\n          schema_key: :user_id,\n          schema_type: :id,\n          schema_table: :users\n        ]\n      ],\n      fn ->\n        assert_raise Mix.Error, ~r\"There can only be one default scope\", fn ->\n          Gen.Schema.run(~w(Blog.Post blog_posts title:string))\n        end\n      end\n    )\n  end\n\n  test \"raises when a reference conflicts with the scope\", config do\n    in_tmp_project(config.test, fn ->\n      with_scope_env(\n        :phoenix,\n        [\n          user: [\n            default: true,\n            module: MyApp.Accounts.Scope,\n            assign_key: :current_scope,\n            access_path: [:user, :id],\n            schema_key: :user_id,\n            schema_type: :id,\n            schema_table: :users\n          ]\n        ],\n        fn ->\n          assert_raise Mix.Error,\n                       ~r\"Reference :user_id has the same name as the scope schema key, either skip the reference or pass it with the --no-scope flag.\",\n                       fn ->\n                         Gen.Schema.run(\n                           ~w(Blog.Post blog_posts title:string user_id:references:users)\n                         )\n                       end\n        end\n      )\n    end)\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.secret_test.exs",
    "content": "Code.require_file \"../../../installer/test/mix_helper.exs\", __DIR__\n\ndefmodule Mix.Tasks.Phx.Gen.SecretTest do\n  use ExUnit.Case\n  import Mix.Tasks.Phx.Gen.Secret\n\n  test \"generates a secret\" do\n    run []\n    assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 64\n    assert String.printable?(secret)\n  end\n\n  test \"generates a secret with custom length\" do\n    run [\"32\"]\n    assert_receive {:mix_shell, :info, [secret]} when byte_size(secret) == 32\n    assert String.printable?(secret)\n  end\n\n  test \"raises on invalid args\" do\n    message = \"mix phx.gen.secret expects a length as integer or no argument at all\"\n    assert_raise Mix.Error, message, fn -> run [\"bad\"] end\n    assert_raise Mix.Error, message, fn -> run [\"32bad\"] end\n    assert_raise Mix.Error, message, fn -> run [\"32\", \"bad\"] end\n  end\n\n  test \"raises when length is too short\" do\n    message = \"The secret should be at least 32 characters long\"\n    assert_raise Mix.Error, message, fn -> run [\"0\"] end\n    assert_raise Mix.Error, message, fn -> run [\"31\"] end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.gen.socket_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule PhoenixWeb.DupSocket do\nend\n\ndefmodule Mix.Tasks.Phx.Gen.SocketTest do\n  use ExUnit.Case\n  import MixHelper\n  alias Mix.Tasks.Phx.Gen\n\n  setup do\n    Mix.Task.clear()\n    :ok\n  end\n\n  test \"generates socket\" do\n    in_tmp_project(\"generates socket\", fn ->\n      Gen.Socket.run([\"User\"])\n\n      assert_file(\"lib/phoenix_web/channels/user_socket.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.UserSocket do|\n\n        assert file =~ ~S|# Uncomment the following line to define a \"room:*\" topic|\n        assert file =~ ~S|# pointing to the `PhoenixWeb.RoomChannel`:|\n        assert file =~ ~S|# channel \"room:*\", PhoenixWeb.RoomChannel|\n\n        assert file =~ ~S|def connect(_params, socket, _connect_info) do|\n        assert file =~ ~S|def id(_socket), do: nil|\n      end)\n\n      assert_file(\"assets/js/user_socket.js\", fn file ->\n        assert file =~ ~S|// NOTE: The contents of this file will only be executed if|\n        assert file =~ ~S|// you uncomment its entry in \"assets/js/app.js\".|\n\n        assert file =~ ~S|// And connect to the path in \"lib/phoenix_web/endpoint.ex\".|\n        assert file =~ ~S|let socket = new Socket(\"/socket\", {authToken: window.userToken})|\n\n        assert file =~ ~S|let channel = socket.channel(\"room:42\", {})|\n        assert file =~ ~S|channel.join()|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nAdd the socket handler to your `lib/phoenix_web/endpoint.ex`\" <> _]}\n  end\n\n  test \"generates socket with channel declaration\" do\n    in_tmp_project(\"generates socket with channel declaration\", fn ->\n      Gen.Socket.run(~w(User --from-channel Chat))\n\n      assert_file(\"lib/phoenix_web/channels/user_socket.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.UserSocket do|\n\n        refute file =~ ~S|# Uncomment the following line to define a \"room:*\" topic|\n        assert file =~ ~S|channel \"chat:*\", PhoenixWeb.ChatChannel|\n      end)\n    end)\n  end\n\n  test \"in an umbrella with a context_app, generates the files\" do\n    in_tmp_umbrella_project(\"generates channels\", fn ->\n      Application.put_env(:phoenix, :generators, context_app: {:another_app, \"another_app\"})\n      Gen.Socket.run([\"Room\"])\n\n      assert_file(\"lib/phoenix/channels/room_socket.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.RoomSocket do|\n      end)\n\n      assert_file(\"assets/js/room_socket.js\", fn file ->\n        assert file =~ ~S|// NOTE: The contents of this file will only be executed if|\n        assert file =~ ~S|// you uncomment its entry in \"assets/js/app.js\".|\n\n        assert file =~ ~S|// Bring in Phoenix channels client library:|\n        assert file =~ ~S|import {Socket} from \"phoenix\"|\n\n        assert file =~ ~S|// And connect to the path in \"lib/phoenix/endpoint.ex\".|\n\n        assert file =~\n                 ~S|Read the [`Using Token Authentication`](https://hexdocs.pm/phoenix/channels.html#using-token-authentication)|\n\n        assert file =~ ~S|let channel = socket.channel(\"room:42\", {})|\n        assert file =~ ~S|channel.join()|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nAdd the socket handler to your `lib/phoenix/endpoint.ex`\" <> _]}\n  end\n\n  test \"generates nested socket\" do\n    in_tmp_project(\"generates nested socket\", fn ->\n      Gen.Socket.run([\"Admin.User\"])\n\n      assert_file(\"lib/phoenix_web/channels/admin/user_socket.ex\", fn file ->\n        assert file =~ ~S|defmodule PhoenixWeb.Admin.UserSocket do|\n      end)\n\n      assert_file(\"assets/js/admin/user_socket.js\", fn file ->\n        assert file =~ ~S|// NOTE: The contents of this file will only be executed if|\n        assert file =~ ~S|// you uncomment its entry in \"assets/js/app.js\".|\n\n        assert file =~ ~S|// Bring in Phoenix channels client library:|\n        assert file =~ ~S|import {Socket} from \"phoenix\"|\n\n        assert file =~ ~S|// And connect to the path in \"lib/phoenix_web/endpoint.ex\".|\n        assert file =~ ~S|let socket = new Socket(\"/socket\", {authToken: window.userToken})|\n\n        assert file =~\n                 ~S|Read the [`Using Token Authentication`](https://hexdocs.pm/phoenix/channels.html#using-token-authentication)|\n\n        assert file =~ ~S|let channel = socket.channel(\"room:42\", {})|\n        assert file =~ ~S|channel.join()|\n      end)\n    end)\n\n    assert_received {:mix_shell, :info,\n                     [\"\\nAdd the socket handler to your `lib/phoenix_web/endpoint.ex`\" <> _]}\n  end\n\n  test \"passing no args raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Socket.run([])\n    end\n  end\n\n  test \"passing invalid name raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Socket.run([\"room\"])\n    end\n  end\n\n  test \"passing extra args raises error\" do\n    assert_raise Mix.Error, fn ->\n      Gen.Socket.run([\"Admin.User\", \"new_message\"])\n    end\n  end\n\n  test \"name is already defined\" do\n    assert_raise Mix.Error, ~r/DupSocket is already taken/, fn ->\n      Gen.Socket.run([\"Dup\"])\n    end\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx.routes_test.exs",
    "content": "Code.require_file(\"../../../installer/test/mix_helper.exs\", __DIR__)\n\ndefmodule PageController do\n  def init(opts), do: opts\n  def call(conn, _opts), do: conn\n\n  defmodule Live do\n    def init(opts), do: opts\n  end\nend\n\ndefmodule PhoenixTestWeb.Router do\n  use Phoenix.Router\n  get \"/\", PageController, :index, as: :page\nend\n\ndefmodule PhoenixTestOld.Router do\n  use Phoenix.Router\n  get \"/old\", PageController, :index, as: :page\nend\n\ndefmodule PhoenixTestLiveWeb.Router do\n  use Phoenix.Router\n  get \"/\", PageController, :index, metadata: %{mfa: {PageController.Live, :init, 1}}\nend\n\ndefmodule PhoenixTestWeb.ForwardedRouter do\n  use Phoenix.Router\n\n  forward \"/\", PhoenixTestWeb.PlugRouterWithVerifiedRoutes\nend\n\ndefmodule PhoenixTestWeb.PlugRouterWithVerifiedRoutes do\n  use Plug.Router\n\n  @behaviour Phoenix.VerifiedRoutes\n\n  get \"/foo\" do\n    send_resp(conn, 200, \"ok\")\n  end\n\n  @impl Phoenix.VerifiedRoutes\n  def formatted_routes(_plug_opts) do\n    [\n      %{verb: \"GET\", path: \"/foo\", label: \"Hello\"}\n    ]\n  end\n\n  @impl Phoenix.VerifiedRoutes\n  def verified_route?(_plug_opts, path) do\n    path == [\"foo\"]\n  end\nend\n\ndefmodule Mix.Tasks.Phx.RoutesTest do\n  use ExUnit.Case, async: true\n\n  test \"format routes for specific router\" do\n    Mix.Tasks.Phx.Routes.run([\"PhoenixTestWeb.Router\", \"--no-compile\"])\n    assert_received {:mix_shell, :info, [routes]}\n    assert routes =~ \"page_path  GET  /  PageController :index\"\n  end\n\n  test \"format routes for forwarded router that implements verified routes\" do\n    Mix.Tasks.Phx.Routes.run([\"PhoenixTestWeb.ForwardedRouter\", \"--no-compile\"])\n    assert_received {:mix_shell, :info, [routes]}\n    assert routes =~ \"GET  /foo  Hello\"\n  end\n\n  test \"prints error when explicit router cannot be found\" do\n    assert_raise Mix.Error,\n                 \"the provided router, Foo.UnknownBar.CantFindBaz, does not exist\",\n                 fn ->\n                   Mix.Tasks.Phx.Routes.run([\"Foo.UnknownBar.CantFindBaz\", \"--no-compile\"])\n                 end\n  end\n\n  test \"prints error when implicit router cannot be found\" do\n    assert_raise Mix.Error, ~r/no router found at FooWeb.Router or Foo.Router/, fn ->\n      Mix.Tasks.Phx.Routes.run([\"--no-compile\"], Foo)\n    end\n  end\n\n  test \"implicit router detection for web namespace\" do\n    Mix.Tasks.Phx.Routes.run([\"--no-compile\"], PhoenixTest)\n    assert_received {:mix_shell, :info, [routes]}\n    assert routes =~ \"page_path  GET  /  PageController :index\"\n  end\n\n  test \"implicit router detection fallback for old namespace\" do\n    Mix.Tasks.Phx.Routes.run([\"--no-compile\"], PhoenixTestOld)\n    assert_received {:mix_shell, :info, [routes]}\n    assert routes =~ \"page_path  GET  /old  PageController :index\"\n  end\n\n  test \"overrides module name for route with :mfa metadata\" do\n    Mix.Tasks.Phx.Routes.run([\"PhoenixTestLiveWeb.Router\", \"--no-compile\"])\n    assert_received {:mix_shell, :info, [routes]}\n    assert routes =~ \"page_path  GET  /  PageController.Live :index\"\n  end\nend\n"
  },
  {
    "path": "test/mix/tasks/phx_test.exs",
    "content": "defmodule Mix.Tasks.Phx.Test do\n  use ExUnit.Case\n\n  test \"provide a list of available phx mix tasks\" do\n    Mix.Tasks.Phx.run []\n    assert_received {:mix_shell, :info, [\"mix phx.digest\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.digest.clean\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.channel\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.cert\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.context\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.embedded\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.html\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.json\" <> _]}\n    assert_received {:mix_shell, :info, [\"mix phx.gen.live\" <> _]}\n  end\n\n  test \"expects no arguments\" do\n    assert_raise Mix.Error, fn ->\n      Mix.Tasks.Phx.run [\"invalid\"]\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/channel_test.exs",
    "content": "defmodule Phoenix.Channel.ChannelTest do\n  use ExUnit.Case, async: true\n\n  @pubsub __MODULE__.PubSub\n  import Phoenix.Channel\n\n  setup_all do\n    start_supervised! {Phoenix.PubSub, name: @pubsub, pool_size: 1}\n    :ok\n  end\n\n  test \"broadcasts from self\" do\n    Phoenix.PubSub.subscribe(@pubsub, \"sometopic\")\n\n    socket = %Phoenix.Socket{\n      pubsub_server: @pubsub,\n      topic: \"sometopic\",\n      channel_pid: self(),\n      joined: true\n    }\n\n    broadcast_from(socket, \"event1\", %{key: :val})\n\n    refute_received %Phoenix.Socket.Broadcast{\n      event: \"event1\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast_from!(socket, \"event2\", %{key: :val})\n\n    refute_received %Phoenix.Socket.Broadcast{\n      event: \"event2\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast(socket, \"event3\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event3\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast!(socket, \"event4\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event4\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"broadcasts from other\" do\n    Phoenix.PubSub.subscribe(@pubsub, \"sometopic\")\n\n    socket = %Phoenix.Socket{\n      pubsub_server: @pubsub,\n      topic: \"sometopic\",\n      channel_pid: spawn_link(fn -> :ok end),\n      joined: true\n    }\n\n    broadcast_from(socket, \"event1\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event1\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast_from!(socket, \"event2\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event2\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast(socket, \"event3\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event3\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    broadcast!(socket, \"event4\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event4\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"pushing to transport\" do\n    socket = %Phoenix.Socket{\n      serializer: Phoenix.ChannelTest.NoopSerializer,\n      topic: \"sometopic\",\n      transport_pid: self(),\n      joined: true\n    }\n\n    push(socket, \"event1\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Message{\n      event: \"event1\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"replying to transport\" do\n    socket = %Phoenix.Socket{\n      serializer: Phoenix.ChannelTest.NoopSerializer,\n      ref: \"123\",\n      topic: \"sometopic\",\n      transport_pid: self(),\n      joined: true\n    }\n\n    ref = socket_ref(socket)\n    reply(ref, {:ok, %{key: :val}})\n\n    assert_receive %Phoenix.Socket.Reply{\n      payload: %{key: :val},\n      ref: \"123\",\n      status: :ok,\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"replying just status to transport\" do\n    socket = %Phoenix.Socket{\n      serializer: Phoenix.ChannelTest.NoopSerializer,\n      ref: \"123\",\n      topic: \"sometopic\",\n      transport_pid: self(),\n      joined: true\n    }\n\n    ref = socket_ref(socket)\n    reply(ref, :ok)\n\n    assert_receive %Phoenix.Socket.Reply{\n      payload: %{},\n      ref: \"123\",\n      status: :ok,\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"socket_ref raises ArgumentError when socket is not joined or has no ref\" do\n    assert_raise ArgumentError, ~r\"join\", fn ->\n      socket_ref(%Phoenix.Socket{joined: false})\n    end\n\n    assert_raise ArgumentError, ~r\"ref\", fn ->\n      socket_ref(%Phoenix.Socket{joined: true})\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/code_reloader_test.exs",
    "content": "defmodule Phoenix.CodeReloaderTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  defmodule Endpoint do\n    def config(:reloadable_compilers), do: [:unknown_compiler, :elixir]\n    def config(:reloadable_apps), do: nil\n  end\n\n  def reload(_, _) do\n    {:error, \"oops \\e[31merror\"}\n  end\n\n  @tag :capture_log\n  test \"syncs with code server\" do\n    assert Phoenix.CodeReloader.sync() == :ok\n\n    # Suspend so we can monitor the process until we get a reply.\n    # There is an inherent race condition here in that the process\n    # may die before we request but the code should work in both\n    # cases, so we are fine.\n    :sys.suspend(Phoenix.CodeReloader.Server)\n    ref = Process.monitor(Phoenix.CodeReloader.Server)\n\n    Task.start_link(fn ->\n      Phoenix.CodeReloader.Server\n      |> Process.whereis()\n      |> Process.exit(:kill)\n    end)\n\n    assert Phoenix.CodeReloader.sync() == :ok\n    assert_receive {:DOWN, ^ref, _, _, _}\n    wait_until_is_up(Phoenix.CodeReloader.Server)\n  end\n\n  test \"reloads on every request\" do\n    pid = Process.whereis(Phoenix.CodeReloader.Server)\n    :erlang.trace(pid, true, [:receive])\n\n    opts = Phoenix.CodeReloader.init([])\n\n    conn =\n      conn(:get, \"/\")\n      |> Plug.Conn.put_private(:phoenix_endpoint, Endpoint)\n      |> Phoenix.CodeReloader.call(opts)\n\n    assert conn.state == :unset\n\n    assert_receive {:trace, ^pid, :receive, {_, _, {:reload!, Endpoint, _}}}\n  end\n\n  test \"renders compilation error on failure\" do\n    opts = Phoenix.CodeReloader.init(reloader: &__MODULE__.reload/2)\n\n    conn =\n      conn(:get, \"/\")\n      |> Plug.Conn.put_private(:phoenix_endpoint, Endpoint)\n      |> Phoenix.CodeReloader.call(opts)\n\n    assert conn.state == :sent\n    assert conn.status == 500\n    assert conn.resp_body =~ \"oops error\"\n    assert conn.resp_body =~ \"CompileError\"\n    assert conn.resp_body =~ \"Compilation error\"\n  end\n\n  defp wait_until_is_up(process) do\n    if Process.whereis(process) do\n      :ok\n    else\n      Process.sleep(10)\n      wait_until_is_up(process)\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/config_test.exs",
    "content": "defmodule Phoenix.ConfigTest do\n  use ExUnit.Case, async: true\n  import Phoenix.Config\n\n  @defaults [static: [at: \"/\"]]\n  @config [parsers: false, custom: true, otp_app: :phoenix_config]\n  @all @config ++ @defaults\n\n  test \"reads configuration from env\", meta do\n    Application.put_env(:config_app, meta.test, @config)\n    config = from_env(:config_app, meta.test, [static: true])\n    assert config[:parsers] == false\n    assert config[:custom]  == true\n    assert config[:static]  == true\n  end\n\n  test \"starts an ets table as part of the module\", meta do\n    {:ok, _pid} = start_link({meta.test, @all, @defaults, []})\n    assert :ets.info(meta.test, :name) == meta.test\n    assert :ets.lookup(meta.test, :parsers) == [parsers: false]\n    assert :ets.lookup(meta.test, :static)  == [static: [at: \"/\"]]\n    assert :ets.lookup(meta.test, :custom)  == [custom: true]\n  end\n\n  test \"raises with warning about compile time when table not started\" do\n    assert_raise RuntimeError,\n                 \"could not find ets table for endpoint Fooz. Make sure your endpoint is started and note you cannot access endpoint functions at compile-time\",\n                 fn -> cache(Fooz, :foo, fn _ -> {:nocache, :bar} end) end\n  end\n\n  test \"can change configuration\", meta do\n    {:ok, pid} = start_link({meta.test, @all, @defaults, []})\n    ref = Process.monitor(pid)\n\n    # Nothing changed\n    config_change(meta.test, [], [])\n    assert :ets.lookup(meta.test, :parsers) == [parsers: false]\n    assert :ets.lookup(meta.test, :static)  == [static: [at: \"/\"]]\n    assert :ets.lookup(meta.test, :custom)  == [custom: true]\n\n    # Something changed\n    config_change(meta.test, [{meta.test, parsers: true}], [])\n    assert :ets.lookup(meta.test, :parsers) == [parsers: true]\n    assert :ets.lookup(meta.test, :static)  == [static: [at: \"/\"]]\n    assert :ets.lookup(meta.test, :custom)  == []\n\n    # Module removed\n    config_change(meta.test, [], [meta.test])\n\n    assert_receive {:DOWN, ^ref, :process, ^pid, :normal}\n    assert :ets.info(meta.test, :name) == :undefined\n  end\n\n  test \"can cache\", meta do\n    {:ok, _pid} = start_link({meta.test, @all, @defaults, []})\n\n    assert cache(meta.test, :__hello__, fn _ -> {:nocache, 1} end) == 1\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 2} end) == 2\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 3} end) == 2\n    assert cache(meta.test, :__hello__, fn _ -> {:nocache, 3} end) == 2\n\n    # Cache is reloaded on config_change\n    config_change(meta.test, [{meta.test, []}], [])\n    assert cache(meta.test, :__hello__, fn _ -> {:nocache, 4} end) == 4\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 5} end) == 5\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 6} end) == 5\n\n    # Cache is cleaned on clear_cache\n    clear_cache(meta.test)\n    assert cache(meta.test, :__hello__, fn _ -> {:nocache, 7} end) == 7\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 8} end) == 8\n    assert cache(meta.test, :__hello__, fn _ -> {:cache, 9} end) == 8\n  end\nend\n"
  },
  {
    "path": "test/phoenix/controller/controller_test.exs",
    "content": "defmodule Phoenix.Controller.ControllerTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  import ExUnit.CaptureIO\n  import Phoenix.Controller\n  alias Plug.Conn\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  defp get_resp_content_type(conn) do\n    [header] = get_resp_header(conn, \"content-type\")\n    header |> String.split(\";\") |> Enum.fetch!(0)\n  end\n\n  test \"action_name/1\" do\n    conn = put_private(%Conn{}, :phoenix_action, :show)\n    assert action_name(conn) == :show\n  end\n\n  test \"controller_module/1\" do\n    conn = put_private(%Conn{}, :phoenix_controller, Hello)\n    assert controller_module(conn) == Hello\n  end\n\n  test \"router_module/1\" do\n    conn = put_private(%Conn{}, :phoenix_router, Hello)\n    assert router_module(conn) == Hello\n  end\n\n  test \"endpoint_module/1\" do\n    conn = put_private(%Conn{}, :phoenix_endpoint, Hello)\n    assert endpoint_module(conn) == Hello\n  end\n\n  test \"view_template/1\" do\n    conn = put_private(%Conn{}, :phoenix_template, \"hello.html\")\n    assert view_template(conn) == \"hello.html\"\n    assert view_template(%Conn{}) == nil\n  end\n\n  test \"status_message_from_template/1\" do\n    assert status_message_from_template(\"404.html\") == \"Not Found\"\n    assert status_message_from_template(\"whatever.html\") == \"Internal Server Error\"\n  end\n\n  test \"put_layout/2 and layout/1\" do\n    conn = conn(:get, \"/\")\n    assert layout(conn) == false\n\n    conn = put_layout(conn, {AppView, \"app.html\"})\n    assert layout(conn) == {AppView, \"app.html\"}\n\n    assert capture_io(:stderr, fn ->\n             conn = put_layout(conn, \"print.html\")\n             assert layout(conn) == {AppView, \"print.html\"}\n           end) =~\n             \"specifying put_layout(conn, template) or put_new_layout(conn, template) is deprecated\"\n\n    assert capture_io(:stderr, fn ->\n             conn = put_layout(conn, :print)\n             assert layout(conn) == {AppView, :print}\n           end) =~\n             \"specifying put_layout(conn, template) or put_new_layout(conn, template) is deprecated\"\n\n    conn = put_layout(conn, false)\n    assert layout(conn) == false\n\n    assert_raise RuntimeError, fn ->\n      put_layout(conn, \"print\")\n    end\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_layout(sent_conn(), {AppView, :print})\n    end\n  end\n\n  test \"put_layout/2 and layout/1 with formats\" do\n    conn = conn(:get, \"/\") |> put_format(\"html\")\n    assert layout(conn) == false\n\n    conn = put_layout(conn, html: {AppView, :app})\n    assert layout(conn) == {AppView, :app}\n\n    assert capture_io(:stderr, fn ->\n             conn = put_layout(conn, html: :print)\n             assert layout(conn) == {AppView, :print}\n           end) =~\n             \"specifying a layout without module is deprecated, use html: {AppView, :print} instead\"\n\n    conn = put_layout(conn, html: {AppView, :app}, print: {AppView, :print})\n\n    conn = put_format(conn, \"html\")\n    assert layout(conn) == {AppView, :app}\n\n    conn = put_format(conn, \"print\")\n    assert layout(conn) == {AppView, :print}\n\n    message = \"\"\"\n    the response was already sent.\n\n        Status code: 200\n        Request path: /\n        Method: GET\n        Layout: {AppView, :print}\n    \"\"\"\n\n    assert_raise Plug.Conn.AlreadySentError, message, fn ->\n      put_layout(sent_conn(), {AppView, :print})\n    end\n  end\n\n  test \"put_root_layout/2 and root_layout/1\" do\n    conn = conn(:get, \"/\")\n    assert root_layout(conn) == false\n\n    conn = put_root_layout(conn, {AppView, \"root.html\"})\n    assert root_layout(conn) == {AppView, \"root.html\"}\n\n    assert capture_io(:stderr, fn ->\n             conn = put_root_layout(conn, \"bare.html\")\n             assert root_layout(conn) == {AppView, \"bare.html\"}\n           end) =~\n             \"specifying put_layout(conn, template) or put_new_layout(conn, template) is deprecated\"\n\n    assert capture_io(:stderr, fn ->\n             conn = put_root_layout(conn, :print)\n             assert root_layout(conn) == {AppView, :print}\n           end) =~\n             \"specifying put_layout(conn, template) or put_new_layout(conn, template) is deprecated\"\n\n    conn = put_root_layout(conn, false)\n    assert root_layout(conn) == false\n\n    assert_raise RuntimeError, fn ->\n      put_root_layout(conn, \"print\")\n    end\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_layout(sent_conn(), {AppView, :print})\n    end\n  end\n\n  test \"put_root_layout/2 and root_layout/1 with formats\" do\n    conn = conn(:get, \"/\") |> put_format(\"html\")\n    assert root_layout(conn) == false\n\n    conn = put_root_layout(conn, html: {AppView, :app})\n    assert root_layout(conn) == {AppView, :app}\n\n    assert capture_io(:stderr, fn ->\n             conn = put_root_layout(conn, html: :print)\n             assert root_layout(conn) == {AppView, :print}\n           end) =~\n             \"specifying a layout without module is deprecated, use html: {AppView, :print} instead\"\n\n    conn = put_root_layout(conn, html: {AppView, :app}, print: {AppView, :print})\n\n    conn = put_format(conn, \"html\")\n    assert root_layout(conn) == {AppView, :app}\n\n    conn = put_format(conn, \"print\")\n    assert root_layout(conn) == {AppView, :print}\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_root_layout(sent_conn(), {AppView, :print})\n    end\n  end\n\n  test \"put_new_layout/2\" do\n    conn = put_new_layout(conn(:get, \"/\"), false)\n    assert layout(conn) == false\n    conn = put_new_layout(conn, {AppView, \"app.html\"})\n    assert layout(conn) == false\n\n    conn = put_new_layout(conn(:get, \"/\"), {AppView, \"app.html\"})\n    assert layout(conn) == {AppView, \"app.html\"}\n    conn = put_new_layout(conn, false)\n    assert layout(conn) == {AppView, \"app.html\"}\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_new_layout(sent_conn(), {AppView, \"app.html\"})\n    end\n  end\n\n  test \"put_new_layout/2 with formats\" do\n    conn = put_new_layout(conn(:get, \"/\"), html: false, json: false)\n    conn = put_format(conn, \"html\")\n    assert layout(conn) == false\n    conn = put_new_layout(conn, html: {AppView, :app})\n    assert layout(conn) == false\n\n    conn = put_new_layout(conn(:get, \"/\"), html: {AppView, :app}, json: false)\n    conn = put_format(conn, \"html\")\n    assert layout(conn) == {AppView, :app}\n    conn = put_new_layout(conn, false)\n    assert layout(conn) == false\n\n    conn = put_format(conn, \"json\")\n    assert layout(conn) == false\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_new_layout(sent_conn(), {AppView, :app})\n    end\n  end\n\n  test \"put_view/2 and put_new_view/2\" do\n    conn = put_new_view(conn(:get, \"/\"), Hello)\n    assert view_module(conn) == Hello\n    conn = put_new_view(conn, World)\n    assert view_module(conn) == Hello\n    conn = put_view(conn, World)\n    assert view_module(conn) == World\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_new_view(sent_conn(), Hello)\n    end\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_view(sent_conn(), Hello)\n    end\n  end\n\n  test \"put_view/2 and put_new_view/2 with formats\" do\n    conn =\n      conn(:get, \"/\")\n      |> put_format(\"print\")\n      |> put_new_view(html: Hello, json: HelloJSON)\n\n    assert view_module(conn, \"html\") == Hello\n\n    assert_raise RuntimeError, ~r/no view was found for the format: \"print\"/, fn ->\n      view_module(conn)\n    end\n\n    conn =\n      conn(:get, \"/\")\n      |> put_format(\"html\")\n      |> put_new_view(html: Hello, json: HelloJSON)\n\n    conn = put_format(conn, \"html\")\n    assert view_module(conn) == Hello\n\n    conn = put_new_view(conn, html: World)\n    assert view_module(conn) == Hello\n    conn = put_view(conn, html: World)\n    assert view_module(conn) == World\n\n    conn = put_format(conn, \"json\")\n    assert view_module(conn) == HelloJSON\n    assert view_module(conn, \"json\") == HelloJSON\n\n    conn = put_format(conn, \"json\")\n    conn = put_new_view(conn, Hello)\n    assert view_module(conn) == Hello\n    assert view_module(conn, \"json\") == Hello\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_new_view(sent_conn(), html: Hello)\n    end\n\n    assert_raise Plug.Conn.AlreadySentError, fn ->\n      put_view(sent_conn(), html: Hello)\n    end\n  end\n\n  describe \"json/2\" do\n    test \"encodes content to json\" do\n      conn = json(conn(:get, \"/\"), %{foo: :bar})\n      assert conn.resp_body == \"{\\\"foo\\\":\\\"bar\\\"}\"\n      assert get_resp_content_type(conn) == \"application/json\"\n      refute conn.halted\n    end\n\n    test \"allows status injection on connection\" do\n      conn = conn(:get, \"/\") |> put_status(400)\n      conn = json(conn, %{foo: :bar})\n      assert conn.resp_body == \"{\\\"foo\\\":\\\"bar\\\"}\"\n      assert conn.status == 400\n    end\n\n    test \"allows content-type injection on connection\" do\n      conn = conn(:get, \"/\") |> put_resp_content_type(\"application/vnd.api+json\")\n      conn = json(conn, %{foo: :bar})\n      assert conn.resp_body == \"{\\\"foo\\\":\\\"bar\\\"}\"\n\n      assert Conn.get_resp_header(conn, \"content-type\") ==\n               [\"application/vnd.api+json; charset=utf-8\"]\n    end\n\n    test \"with allow_jsonp/2 returns json when no callback param is present\" do\n      conn =\n        conn(:get, \"/\")\n        |> fetch_query_params()\n        |> allow_jsonp()\n        |> json(%{foo: \"bar\"})\n\n      assert conn.resp_body == \"{\\\"foo\\\":\\\"bar\\\"}\"\n      assert get_resp_content_type(conn) == \"application/json\"\n      refute conn.halted\n    end\n\n    test \"with allow_jsonp/2 returns json when callback name is left empty\" do\n      conn =\n        conn(:get, \"/?callback=\")\n        |> fetch_query_params()\n        |> allow_jsonp()\n        |> json(%{foo: \"bar\"})\n\n      assert conn.resp_body == \"{\\\"foo\\\":\\\"bar\\\"}\"\n      assert get_resp_content_type(conn) == \"application/json\"\n      refute conn.halted\n    end\n\n    test \"with allow_jsonp/2 returns javascript when callback param is present\" do\n      conn =\n        conn(:get, \"/?callback=cb\")\n        |> fetch_query_params\n        |> allow_jsonp\n        |> json(%{foo: \"bar\"})\n\n      assert conn.resp_body == \"/**/ typeof cb === 'function' && cb({\\\"foo\\\":\\\"bar\\\"});\"\n      assert get_resp_content_type(conn) == \"application/javascript\"\n      refute conn.halted\n    end\n\n    test \"with allow_jsonp/2 allows to override the callback param\" do\n      conn =\n        conn(:get, \"/?cb=cb\")\n        |> fetch_query_params\n        |> allow_jsonp(callback: \"cb\")\n        |> json(%{foo: \"bar\"})\n\n      assert conn.resp_body == \"/**/ typeof cb === 'function' && cb({\\\"foo\\\":\\\"bar\\\"});\"\n      assert get_resp_content_type(conn) == \"application/javascript\"\n      refute conn.halted\n    end\n\n    test \"with allow_jsonp/2 raises ArgumentError when callback contains invalid characters\" do\n      conn = conn(:get, \"/?cb=_c*b!()[0]\") |> fetch_query_params()\n\n      assert_raise ArgumentError, \"the JSONP callback name contains invalid characters\", fn ->\n        allow_jsonp(conn, callback: \"cb\")\n      end\n    end\n\n    test \"with allow_jsonp/2 escapes invalid javascript characters\" do\n      conn =\n        conn(:get, \"/?cb=cb\")\n        |> fetch_query_params\n        |> allow_jsonp(callback: \"cb\")\n        |> json(%{foo: <<0x2028::utf8, 0x2029::utf8>>})\n\n      assert conn.resp_body ==\n               \"/**/ typeof cb === 'function' && cb({\\\"foo\\\":\\\"\\\\u2028\\\\u2029\\\"});\"\n\n      assert get_resp_content_type(conn) == \"application/javascript\"\n      refute conn.halted\n    end\n  end\n\n  describe \"text/2\" do\n    test \"sends the content as text\" do\n      conn = text(conn(:get, \"/\"), \"foobar\")\n      assert conn.resp_body == \"foobar\"\n      assert get_resp_content_type(conn) == \"text/plain\"\n      refute conn.halted\n\n      conn = text(conn(:get, \"/\"), :foobar)\n      assert conn.resp_body == \"foobar\"\n      assert get_resp_content_type(conn) == \"text/plain\"\n      refute conn.halted\n    end\n\n    test \"allows status injection on connection\" do\n      conn = conn(:get, \"/\") |> put_status(400)\n      conn = text(conn, :foobar)\n      assert conn.resp_body == \"foobar\"\n      assert conn.status == 400\n    end\n  end\n\n  describe \"html/2\" do\n    test \"sends the content as html\" do\n      conn = html(conn(:get, \"/\"), \"foobar\")\n      assert conn.resp_body == \"foobar\"\n      assert get_resp_content_type(conn) == \"text/html\"\n      refute conn.halted\n    end\n\n    test \"allows status injection on connection\" do\n      conn = conn(:get, \"/\") |> put_status(400)\n      conn = html(conn, \"foobar\")\n      assert conn.resp_body == \"foobar\"\n      assert conn.status == 400\n    end\n  end\n\n  describe \"redirect/2\" do\n    test \"with :to\" do\n      conn = redirect(conn(:get, \"/\"), to: \"/foobar\")\n      assert conn.resp_body =~ \"/foobar\"\n      assert get_resp_content_type(conn) == \"text/html\"\n      assert get_resp_header(conn, \"location\") == [\"/foobar\"]\n      refute conn.halted\n\n      conn = redirect(conn(:get, \"/\"), to: \"/<foobar>\")\n      assert conn.resp_body =~ \"/&lt;foobar&gt;\"\n\n      assert_raise ArgumentError, ~r/the :to option in redirect expects a path/, fn ->\n        redirect(conn(:get, \"/\"), to: \"http://example.com\")\n      end\n\n      assert_raise ArgumentError, ~r/the :to option in redirect expects a path/, fn ->\n        redirect(conn(:get, \"/\"), to: \"//example.com\")\n      end\n\n      assert_raise ArgumentError, ~r/unsafe/, fn ->\n        redirect(conn(:get, \"/\"), to: \"/\\\\example.com\")\n      end\n\n      assert_raise ArgumentError, ~r/expects a path/, fn ->\n        redirect(conn(:get, \"/\"), to: \"//\\\\example.com\")\n      end\n\n      assert_raise ArgumentError, ~r/unsafe/, fn ->\n        redirect(conn(:get, \"/\"), to: \"/%09/example.com\")\n      end\n\n      assert_raise ArgumentError, ~r/unsafe/, fn ->\n        redirect(conn(:get, \"/\"), to: \"/\\t/example.com\")\n      end\n    end\n\n    test \"with :external\" do\n      conn = redirect(conn(:get, \"/\"), external: \"http://example.com\")\n      assert conn.resp_body =~ \"http://example.com\"\n      assert get_resp_header(conn, \"location\") == [\"http://example.com\"]\n      refute conn.halted\n    end\n\n    test \"with put_status/2 uses previously set status or defaults to 302\" do\n      conn = conn(:get, \"/\") |> redirect(to: \"/\")\n      assert conn.status == 302\n      conn = conn(:get, \"/\") |> put_status(301) |> redirect(to: \"/\")\n      assert conn.status == 301\n    end\n  end\n\n  defp with_accept(header) do\n    conn(:get, \"/\", [])\n    |> put_req_header(\"accept\", header)\n  end\n\n  describe \"accepts/2\" do\n    test \"uses params[\\\"_format\\\"] when available\" do\n      conn = accepts(conn(:get, \"/\", _format: \"json\"), ~w(json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == \"json\"\n\n      exception =\n        assert_raise Phoenix.NotAcceptableError, ~r/unknown format \"json\"/, fn ->\n          accepts(conn(:get, \"/\", _format: \"json\"), ~w(html))\n        end\n\n      assert Plug.Exception.status(exception) == 406\n      assert exception.accepts == [\"html\"]\n    end\n\n    test \"uses first accepts on empty or catch-all header\" do\n      conn = accepts(conn(:get, \"/\", []), ~w(json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"*/*\"), ~w(json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n    end\n\n    test \"uses first matching accepts on empty subtype\" do\n      conn = accepts(with_accept(\"text/*\"), ~w(json text css))\n      assert get_format(conn) == \"text\"\n      assert conn.params[\"_format\"] == nil\n    end\n\n    test \"on non-empty */*\" do\n      # Fallbacks to HTML due to browsers behavior\n      conn = accepts(with_accept(\"application/json, */*\"), ~w(html json))\n      assert get_format(conn) == \"html\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"*/*, application/json\"), ~w(html json))\n      assert get_format(conn) == \"html\"\n      assert conn.params[\"_format\"] == nil\n\n      # No HTML is treated normally\n      conn = accepts(with_accept(\"*/*, text/plain, application/json\"), ~w(json text))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"text/plain, application/json, */*\"), ~w(json text))\n      assert get_format(conn) == \"text\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"text/*, application/*, */*\"), ~w(json text))\n      assert get_format(conn) == \"text\"\n      assert conn.params[\"_format\"] == nil\n    end\n\n    test \"ignores invalid media types\" do\n      conn = accepts(with_accept(\"foo/bar, bar baz, application/json\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"foo/*, */bar, text/*\"), ~w(json html))\n      assert get_format(conn) == \"html\"\n      assert conn.params[\"_format\"] == nil\n    end\n\n    test \"considers q params\" do\n      conn = accepts(with_accept(\"text/html; q=0.7, application/json\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"application/json, text/html; q=0.7\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"application/json; q=1.0, text/html; q=0.7\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"application/json; q=0.8, text/html; q=0.7\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"text/html; q=0.7, application/json; q=0.8\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"text/*; q=0.7, application/json\"), ~w(html json))\n      assert get_format(conn) == \"json\"\n      assert conn.params[\"_format\"] == nil\n\n      conn = accepts(with_accept(\"application/json; q=0.7, text/*; q=0.8\"), ~w(json html))\n      assert get_format(conn) == \"html\"\n      assert conn.params[\"_format\"] == nil\n\n      exception =\n        assert_raise Phoenix.NotAcceptableError, ~r/no supported media type in accept/, fn ->\n          accepts(with_accept(\"text/html; q=0.7, application/json; q=0.8\"), ~w(xml))\n        end\n\n      assert Plug.Exception.status(exception) == 406\n      assert exception.accepts == [\"xml\"]\n    end\n  end\n\n  describe \"send_download/3\" do\n    @hello_txt Path.expand(\"../../fixtures/hello.txt\", __DIR__)\n\n    test \"sends file for download\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt})\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"attachment; filename=\\\"hello.txt\\\"\"]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"text/plain\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download with custom :filename\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, filename: \"hello world.json\")\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\n                 \"attachment; filename=\\\"hello%20world.json\\\"; filename*=utf-8''hello%20world.json\"\n               ]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download for filename with unreserved characters\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, filename: \"hello, world.json\")\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\n                 \"attachment; filename=\\\"hello%2C%20world.json\\\"; filename*=utf-8''hello%2C%20world.json\"\n               ]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file supports UTF-8\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, filename: \"测 试.txt\")\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\n                 \"attachment; filename=\\\"%E6%B5%8B%20%E8%AF%95.txt\\\"; filename*=utf-8''%E6%B5%8B%20%E8%AF%95.txt\"\n               ]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"text/plain\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download with custom :filename and :encode false\" do\n      conn =\n        send_download(conn(:get, \"/\"), {:file, @hello_txt},\n          filename: \"dev's hello world.json\",\n          encode: false\n        )\n\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"attachment; filename=\\\"dev's hello world.json\\\"\"]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download with custom :content_type and :charset\" do\n      conn =\n        send_download(conn(:get, \"/\"), {:file, @hello_txt},\n          content_type: \"application/json\",\n          charset: \"utf8\"\n        )\n\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"attachment; filename=\\\"hello.txt\\\"\"]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json; charset=utf8\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download with custom :disposition\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, disposition: :inline)\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"inline; filename=\\\"hello.txt\\\"\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends file for download with custom :offset\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, offset: 2)\n      assert conn.status == 200\n\n      assert conn.resp_body ==\n               \"rld\"\n    end\n\n    test \"sends file for download with custom :length\" do\n      conn = send_download(conn(:get, \"/\"), {:file, @hello_txt}, length: 2)\n      assert conn.status == 200\n\n      assert conn.resp_body ==\n               \"wo\"\n    end\n\n    test \"sends binary for download with :filename\" do\n      conn = send_download(conn(:get, \"/\"), {:binary, \"world\"}, filename: \"hello.json\")\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"attachment; filename=\\\"hello.json\\\"\"]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends binary as download with custom :content_type and :charset\" do\n      conn =\n        send_download(conn(:get, \"/\"), {:binary, \"world\"},\n          filename: \"hello.txt\",\n          content_type: \"application/json\",\n          charset: \"utf8\"\n        )\n\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"attachment; filename=\\\"hello.txt\\\"\"]\n\n      assert get_resp_header(conn, \"content-type\") ==\n               [\"application/json; charset=utf8\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"sends binary for download with custom :disposition\" do\n      conn =\n        send_download(conn(:get, \"/\"), {:binary, \"world\"},\n          filename: \"hello.txt\",\n          disposition: :inline\n        )\n\n      assert conn.status == 200\n\n      assert get_resp_header(conn, \"content-disposition\") ==\n               [\"inline; filename=\\\"hello.txt\\\"\"]\n\n      assert conn.resp_body ==\n               \"world\"\n    end\n\n    test \"raises ArgumentError for :disposition other than :attachment or :inline\" do\n      assert_raise(\n        ArgumentError,\n        ~r\"expected :disposition to be :attachment or :inline, got: :foo\",\n        fn ->\n          send_download(conn(:get, \"/\"), {:file, @hello_txt}, disposition: :foo)\n        end\n      )\n\n      assert_raise(\n        ArgumentError,\n        ~r\"expected :disposition to be :attachment or :inline, got: :foo\",\n        fn ->\n          send_download(conn(:get, \"/\"), {:binary, \"world\"},\n            filename: \"hello.txt\",\n            disposition: :foo\n          )\n        end\n      )\n    end\n  end\n\n  describe \"scrub_params/2\" do\n    test \"raises Phoenix.MissingParamError for missing key\" do\n      assert_raise(\n        Phoenix.MissingParamError,\n        ~r\"expected key \\\"foo\\\" to be present in params\",\n        fn ->\n          conn(:get, \"/\") |> fetch_query_params |> scrub_params(\"foo\")\n        end\n      )\n\n      assert_raise(\n        Phoenix.MissingParamError,\n        ~r\"expected key \\\"foo\\\" to be present in params\",\n        fn ->\n          conn(:get, \"/?foo=\") |> fetch_query_params |> scrub_params(\"foo\")\n        end\n      )\n    end\n\n    test \"keeps populated keys intact\" do\n      conn =\n        conn(:get, \"/?foo=bar\")\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"foo\"] == \"bar\"\n    end\n\n    test \"nils out all empty values for the passed in key if it is a list\" do\n      conn =\n        conn(:get, \"/?foo[]=&foo[]=++&foo[]=bar\")\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"foo\"] == [nil, nil, \"bar\"]\n    end\n\n    test \"nils out all empty keys in value for the passed in key if it is a map\" do\n      conn =\n        conn(:get, \"/?foo[bar]=++&foo[baz]=&foo[bat]=ok\")\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"foo\"] == %{\"bar\" => nil, \"baz\" => nil, \"bat\" => \"ok\"}\n    end\n\n    test \"nils out all empty keys in value for the passed in key if it is a nested map\" do\n      conn =\n        conn(:get, \"/?foo[bar][baz]=\")\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"foo\"] == %{\"bar\" => %{\"baz\" => nil}}\n    end\n\n    test \"ignores the keys that don't match the passed in key\" do\n      conn =\n        conn(:get, \"/?foo=bar&baz=\")\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"baz\"] == \"\"\n    end\n\n    test \"keeps structs intact\" do\n      conn =\n        conn(:post, \"/\", %{\"foo\" => %{\"bar\" => %Plug.Upload{}}})\n        |> fetch_query_params\n        |> scrub_params(\"foo\")\n\n      assert conn.params[\"foo\"][\"bar\"] == %Plug.Upload{}\n    end\n  end\n\n  test \"protect_from_forgery/2 sets token\" do\n    conn(:get, \"/\")\n    |> init_test_session(%{})\n    |> protect_from_forgery([])\n\n    assert is_binary(get_csrf_token())\n    assert is_binary(delete_csrf_token())\n  end\n\n  describe \"put_secure_browser_headers/2\" do\n    test \"sets headers\" do\n      conn = conn(:get, \"/\") |> put_secure_browser_headers()\n      assert get_resp_header(conn, \"x-content-type-options\") == [\"nosniff\"]\n      assert get_resp_header(conn, \"x-permitted-cross-domain-policies\") == [\"none\"]\n\n      assert get_resp_header(conn, \"content-security-policy\") ==\n               [\"base-uri 'self'; frame-ancestors 'self';\"]\n    end\n\n    test \"only if not set headers\" do\n      custom_headers = %{\"content-security-policy\" => \"custom\", \"foo\" => \"bar\"}\n      conn = conn(:get, \"/\") |> merge_resp_headers(custom_headers) |> put_secure_browser_headers()\n      assert get_resp_header(conn, \"x-content-type-options\") == [\"nosniff\"]\n      assert get_resp_header(conn, \"x-permitted-cross-domain-policies\") == [\"none\"]\n      assert get_resp_header(conn, \"content-security-policy\") == [\"custom\"]\n      assert get_resp_header(conn, \"foo\") == [\"bar\"]\n    end\n\n    test \"can be overridden\" do\n      custom_headers = %{\"content-security-policy\" => \"custom\", \"foo\" => \"bar\"}\n      conn = conn(:get, \"/\") |> put_secure_browser_headers(custom_headers)\n      assert get_resp_header(conn, \"x-content-type-options\") == [\"nosniff\"]\n      assert get_resp_header(conn, \"x-permitted-cross-domain-policies\") == [\"none\"]\n      assert get_resp_header(conn, \"content-security-policy\") == [\"custom\"]\n      assert get_resp_header(conn, \"foo\") == [\"bar\"]\n    end\n  end\n\n  describe \"__using__\" do\n    defp new_layout(module, opts), do: Phoenix.Controller.__plugs__(module, opts) |> elem(0)\n    defp new_view(module, opts), do: Phoenix.Controller.__plugs__(module, opts) |> elem(1)\n\n    test \"deprecated when lacking formats\" do\n      assert capture_io(:stderr, fn ->\n               assert Phoenix.Controller.__plugs__(UserController, []) ==\n                        {{LayoutView, :app}, UserView}\n             end) =~\n               \"use UserController must receive the :formats option with the formats you intend to render\"\n\n      assert capture_io(:stderr, fn ->\n               assert Phoenix.Controller.__plugs__(MyApp.UserController, []) ==\n                        {{MyApp.LayoutView, :app}, MyApp.UserView}\n             end) =~\n               \"use MyApp.UserController must receive the :formats option with the formats you intend to render\"\n\n      assert capture_io(:stderr, fn ->\n               assert Phoenix.Controller.__plugs__(MyApp.Admin.UserController, []) ==\n                        {{MyApp.LayoutView, :app}, MyApp.Admin.UserView}\n             end) =~\n               \"use MyApp.Admin.UserController must receive the :formats option with the formats you intend to render\"\n    end\n\n    test \"deprecated namespace\" do\n      assert capture_io(:stderr, fn ->\n               assert new_layout(MyApp.Admin.UserController, namespace: MyApp.Admin, formats: []) ==\n                        {MyApp.Admin.LayoutView, :app}\n             end) =~ \"the :namespace option given to MyApp.Admin.UserController is deprecated\"\n    end\n\n    test \"returns view modules based on format\" do\n      assert new_view(MyApp.Admin.UserController, formats: [:html, :json]) ==\n               [html: MyApp.Admin.UserHTML, json: MyApp.Admin.UserJSON]\n\n      assert new_view(MyApp.Admin.UserController, formats: [:html, json: \"View\"]) ==\n               [html: MyApp.Admin.UserHTML, json: MyApp.Admin.UserView]\n    end\n\n    test \"returns the layout module based on controller module\" do\n      assert new_layout(MyApp.Admin.UserController, formats: []) == []\n\n      assert new_layout(MyApp.Admin.UserController,\n               layouts: [html: MyApp.LayoutHTML],\n               formats: []\n             ) ==\n               [html: {MyApp.LayoutHTML, :app}]\n\n      assert new_layout(MyApp.Admin.UserController,\n               layouts: [html: {MyApp.LayoutHTML, :application}],\n               formats: []\n             ) ==\n               [html: {MyApp.LayoutHTML, :application}]\n    end\n  end\n\n  defp sent_conn do\n    conn(:get, \"/\") |> send_resp(:ok, \"\")\n  end\n\n  describe \"path and url generation\" do\n    def url(), do: \"https://www.example.com\"\n\n    def build_conn_for_path(path) do\n      conn(:get, path)\n      |> fetch_query_params()\n      |> put_private(:phoenix_endpoint, __MODULE__)\n      |> put_private(:phoenix_router, __MODULE__)\n    end\n\n    test \"current_path/1 uses the conn's query params\" do\n      conn = build_conn_for_path(\"/\")\n      assert current_path(conn) == \"/\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_path(conn) == \"/foo?one=1&two=2\"\n\n      conn = build_conn_for_path(\"/foo//bar/\")\n      assert current_path(conn) == \"/foo/bar\"\n    end\n\n    test \"current_path/2 allows custom query params\" do\n      conn = build_conn_for_path(\"/\")\n      assert current_path(conn, %{}) == \"/\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_path(conn, %{}) == \"/foo\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_path(conn, %{three: 3}) == \"/foo?three=3\"\n    end\n\n    test \"current_path/2 allows custom nested query params\" do\n      conn = build_conn_for_path(\"/\")\n      assert current_path(conn, foo: [bar: [:baz], baz: :qux]) == \"/?foo[bar][]=baz&foo[baz]=qux\"\n    end\n\n    test \"current_url/1 with root path includes trailing slash\" do\n      conn = build_conn_for_path(\"/\")\n      assert current_url(conn) == \"https://www.example.com/\"\n    end\n\n    test \"current_url/1 users conn's endpoint and query params\" do\n      conn = build_conn_for_path(\"/?foo=bar\")\n      assert current_url(conn) == \"https://www.example.com/?foo=bar\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_url(conn) == \"https://www.example.com/foo?one=1&two=2\"\n    end\n\n    test \"current_url/2 allows custom query params\" do\n      conn = build_conn_for_path(\"/\")\n      assert current_url(conn, %{}) == \"https://www.example.com/\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_url(conn, %{}) == \"https://www.example.com/foo\"\n\n      conn = build_conn_for_path(\"/foo?one=1&two=2\")\n      assert current_url(conn, %{three: 3}) == \"https://www.example.com/foo?three=3\"\n    end\n  end\n\n  describe \"assign/2\" do\n    test \"merges assigns\" do\n      conn = conn(:get, \"/\")\n\n      refute conn.assigns[:foo]\n\n      conn = assign(conn, %{foo: :bar})\n      assert conn.assigns.foo == :bar\n\n      conn = assign(conn, bar: :baz)\n      assert conn.assigns.foo == :bar\n      assert conn.assigns.bar == :baz\n\n      conn = assign(conn, fn %{foo: :bar} -> [baz: :quux] end)\n      assert conn.assigns.foo == :bar\n      assert conn.assigns.bar == :baz\n      assert conn.assigns.baz == :quux\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/controller/flash_test.exs",
    "content": "defmodule Phoenix.Controller.FlashTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  import Phoenix.Controller\n\n  alias Phoenix.Flash\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  @session Plug.Session.init(\n             store: :cookie,\n             key: \"_app\",\n             encryption_salt: \"yadayada\",\n             signing_salt: \"yadayada\"\n           )\n\n  def with_session(conn) do\n    conn\n    |> Map.put(:secret_key_base, String.duplicate(\"abcdefgh\", 8))\n    |> Plug.Session.call(@session)\n    |> Plug.Conn.fetch_session()\n  end\n\n  test \"does not fetch flash twice\" do\n    expected_flash = %{\"foo\" => \"bar\"}\n\n    conn =\n      conn(:get, \"/\")\n      |> with_session()\n      |> put_session(\"phoenix_flash\", expected_flash)\n      |> fetch_flash()\n      |> put_session(\"phoenix_flash\", %{\"foo\" => \"baz\"})\n      |> fetch_flash()\n\n    assert conn.assigns.flash == expected_flash\n    assert conn.assigns.flash == expected_flash\n  end\n\n  test \"flash is persisted when status is a redirect\" do\n    for status <- 300..308 do\n      conn =\n        conn(:get, \"/\")\n        |> with_session\n        |> fetch_flash()\n        |> put_flash(:notice, \"elixir\")\n        |> send_resp(status, \"ok\")\n\n      assert Flash.get(conn.assigns.flash, :notice) == \"elixir\"\n      assert get_resp_header(conn, \"set-cookie\") != []\n      conn = conn(:get, \"/\") |> recycle_cookies(conn) |> with_session |> fetch_flash()\n      assert Flash.get(conn.assigns.flash, :notice) == \"elixir\"\n    end\n  end\n\n  test \"flash is not persisted when status is not redirect\" do\n    for status <- [299, 309, 200, 404] do\n      conn =\n        conn(:get, \"/\")\n        |> with_session\n        |> fetch_flash()\n        |> put_flash(:notice, \"elixir\")\n        |> send_resp(status, \"ok\")\n\n      assert Flash.get(conn.assigns.flash, :notice) == \"elixir\"\n      assert get_resp_header(conn, \"set-cookie\") != []\n      conn = conn(:get, \"/\") |> recycle_cookies(conn) |> with_session |> fetch_flash()\n      assert Flash.get(conn.assigns.flash, :notice) == nil\n    end\n  end\n\n  test \"flash does not write to session when it is empty and no session exists\" do\n    conn =\n      conn(:get, \"/\")\n      |> with_session()\n      |> fetch_flash()\n      |> clear_flash()\n      |> send_resp(302, \"ok\")\n\n    assert get_resp_header(conn, \"set-cookie\") == []\n  end\n\n  test \"flash writes to session when it is empty and a previous session exists\" do\n    persisted_flash_conn =\n      conn(:get, \"/\")\n      |> with_session()\n      |> fetch_flash()\n      |> put_flash(:info, \"existing\")\n      |> send_resp(302, \"ok\")\n\n    conn =\n      conn(:get, \"/\")\n      |> Plug.Test.recycle_cookies(persisted_flash_conn)\n      |> with_session()\n      |> fetch_flash()\n      |> clear_flash()\n      |> send_resp(200, \"ok\")\n\n    assert [\"_app=\" <> _] = get_resp_header(conn, \"set-cookie\")\n  end\n\n  test \"flash assigns contains the map of messages\" do\n    conn = conn(:get, \"/\") |> with_session |> fetch_flash([]) |> put_flash(:notice, \"hi\")\n    assert conn.assigns.flash == %{\"notice\" => \"hi\"}\n  end\n\n  test \"Flash.get/2 returns the message by key\" do\n    conn = conn(:get, \"/\") |> with_session |> fetch_flash([]) |> put_flash(:notice, \"hi\")\n    assert Flash.get(conn.assigns.flash, :notice) == \"hi\"\n    assert Flash.get(conn.assigns.flash, \"notice\") == \"hi\"\n  end\n\n  test \"Flash.get/2 returns nil for missing key\" do\n    conn = conn(:get, \"/\") |> with_session |> fetch_flash([])\n    assert Flash.get(conn.assigns.flash, :notice) == nil\n    assert Flash.get(conn.assigns.flash, \"notice\") == nil\n  end\n\n  test \"put_flash/3 raises ArgumentError when flash not previously fetched\" do\n    assert_raise ArgumentError, fn ->\n      conn(:get, \"/\") |> with_session |> put_flash(:error, \"boom!\")\n    end\n  end\n\n  test \"put_flash/3 adds the key/message pair to the flash and updates assigns\" do\n    conn =\n      conn(:get, \"/\")\n      |> with_session\n      |> fetch_flash([])\n\n    assert conn.assigns.flash == %{}\n\n    conn =\n      conn\n      |> put_flash(:error, \"oh noes!\")\n      |> put_flash(:notice, \"false alarm!\")\n\n    assert conn.assigns.flash == %{\"error\" => \"oh noes!\", \"notice\" => \"false alarm!\"}\n    assert Flash.get(conn.assigns.flash, :error) == \"oh noes!\"\n    assert Flash.get(conn.assigns.flash, \"error\") == \"oh noes!\"\n    assert Flash.get(conn.assigns.flash, :notice) == \"false alarm!\"\n    assert Flash.get(conn.assigns.flash, \"notice\") == \"false alarm!\"\n  end\n\n  test \"clear_flash/1 clears the flash messages\" do\n    conn =\n      conn(:get, \"/\")\n      |> with_session\n      |> fetch_flash([])\n      |> put_flash(:error, \"oh noes!\")\n      |> put_flash(:notice, \"false alarm!\")\n\n    refute conn.assigns.flash == %{}\n    conn = clear_flash(conn)\n    assert conn.assigns.flash == %{}\n  end\n\n  test \"merge_flash/2 adds kv-pairs to the flash\" do\n    conn =\n      conn(:get, \"/\")\n      |> with_session\n      |> fetch_flash([])\n      |> merge_flash(error: \"oh noes!\", notice: \"false alarm!\")\n\n    assert Flash.get(conn.assigns.flash, :error) == \"oh noes!\"\n    assert Flash.get(conn.assigns.flash, \"error\") == \"oh noes!\"\n    assert Flash.get(conn.assigns.flash, :notice) == \"false alarm!\"\n    assert Flash.get(conn.assigns.flash, \"notice\") == \"false alarm!\"\n  end\n\n  test \"fetch_flash/2 raises ArgumentError when session not previously fetched\" do\n    assert_raise ArgumentError, fn ->\n      conn(:get, \"/\") |> fetch_flash([])\n    end\n  end\n\n  describe \"Flash\" do\n    test \"get/2\" do\n      assert Flash.get(%{}, :info) == nil\n      assert Flash.get(%{\"info\" => \"hi\"}, :info) == \"hi\"\n      assert Flash.get(%{\"info\" => \"hi\", \"error\" => \"ohno\"}, :error) == \"ohno\"\n    end\n\n    test \"invalid access\" do\n      assert_raise ArgumentError, ~r/expected a map of flash data, but got a %Plug.Conn{}/, fn ->\n        Flash.get(%Plug.Conn{}, :info)\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/controller/pipeline_test.exs",
    "content": "defmodule Phoenix.Controller.PipelineTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  import Phoenix.Controller\n\n  defmodule MyController do\n    use Phoenix.Controller, formats: []\n\n    @secret_actions [:secret]\n\n    plug :prepend, :before1 when action in [:show, :create, :secret]\n    plug :prepend, :before2\n    plug :do_halt when action in @secret_actions\n\n    # Delete the attribute to verify attributes in guards are expanded at call time\n    Module.delete_attribute(__MODULE__, :secret_actions)\n\n    def show(conn, _) do\n      prepend(conn, :action)\n    end\n\n    def no_fallback(_conn, _) do\n      :not_a_conn\n    end\n\n    def create(conn, _) do\n      prepend(conn, :action)\n    end\n\n    def secret(conn, _) do\n      prepend(conn, :secret_action)\n    end\n\n    def no_match(_conn, %{\"no\" => \"match\"}) do\n      raise \"shouldn't have matched\"\n    end\n\n    def non_top_level_function_clause_error(conn, params) do\n      send_resp(conn, :ok, trigger_func_clause_error(params))\n    end\n\n    defp trigger_func_clause_error(%{\"no\" => \"match\"}), do: \"shouldn't ever match\"\n\n    defp do_halt(conn, _), do: halt(conn)\n\n    defp prepend(conn, val) do\n      update_in(conn.private.stack, &[val | &1])\n    end\n  end\n\n  defmodule NoViewsController do\n    use Phoenix.Controller, formats: []\n\n    def show(conn, _), do: conn\n  end\n\n  defmodule FallbackFunctionController do\n    use Phoenix.Controller, formats: []\n\n    action_fallback :function_plug\n\n    plug :put_assign\n\n    def fallback(_conn, _), do: :not_a_conn\n\n    def bad_fallback(_conn, _), do: :bad_fallback\n\n    defp function_plug(%Plug.Conn{} = conn, :not_a_conn) do\n      Plug.Conn.send_resp(conn, 200, \"function fallback\")\n    end\n\n    defp function_plug(%Plug.Conn{}, :bad_fallback), do: :bad_function_fallback\n\n    defp put_assign(conn, _), do: assign(conn, :value_before_action, :a_value)\n  end\n\n  defmodule ActionController do\n    use Phoenix.Controller, formats: []\n\n    action_fallback Phoenix.Controller.PipelineTest\n\n    plug :put_assign\n\n    def action(conn, _) do\n      apply(__MODULE__, conn.private.phoenix_action, [conn, conn.body_params, conn.query_params])\n    end\n\n    def show(conn, _, _), do: text(conn, \"show\")\n\n    def no_match(_conn, _, %{\"no\" => \"match\"}) do\n      raise \"Shouldn't have matched\"\n    end\n\n    def fallback(_conn, _, _) do\n      :not_a_conn\n    end\n\n    def bad_fallback(_conn, _, _) do\n      :bad_fallback\n    end\n\n    defp put_assign(conn, _), do: assign(conn, :value_before_action, :a_value)\n  end\n\n  def init(opts), do: opts\n  def call(conn, :not_a_conn), do: Plug.Conn.send_resp(conn, 200, \"fallback\")\n  def call(_conn, :bad_fallback), do: :bad_fallback\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"invokes the plug stack\" do\n    conn =\n      stack_conn()\n      |> MyController.call(:show)\n\n    assert conn.private.stack == [:action, :before2, :before1]\n  end\n\n  test \"invokes the plug stack with guards\" do\n    conn =\n      stack_conn()\n      |> MyController.call(:create)\n\n    assert conn.private.stack == [:action, :before2, :before1]\n  end\n\n  test \"halts prevent action from running\" do\n    conn =\n      stack_conn()\n      |> MyController.call(:secret)\n\n    assert conn.private.stack == [:before2, :before1]\n  end\n\n  test \"does not override previous views/layouts\" do\n    conn =\n      stack_conn()\n      |> put_view(Hello)\n      |> put_layout(false)\n      |> MyController.call(:create)\n\n    assert view_module(conn) == Hello\n    assert layout(conn) == false\n  end\n\n  test \"does not set default view/layout\" do\n    conn =\n      stack_conn()\n      |> NoViewsController.call(:show)\n      |> put_new_view(Hello)\n\n    assert view_module(conn) == Hello\n    assert layout(conn) == false\n  end\n\n  test \"transforms top-level function clause errors into Phoenix.ActionClauseError\" do\n    assert_raise Phoenix.ActionClauseError, fn ->\n      MyController.call(stack_conn(), :no_match)\n    end\n  end\n\n  test \"wraps function clause errors lower in action stack in Plug.Conn.WrapperError\" do\n    assert_raise Plug.Conn.WrapperError, fn ->\n      MyController.call(stack_conn(), :non_top_level_function_clause_error)\n    end\n  end\n\n  test \"action/2 is overridable and still wraps function clause transforms\" do\n    conn = ActionController.call(stack_conn(), :show)\n    assert conn.status == 200\n    assert conn.resp_body == \"show\"\n\n    assert_raise Phoenix.ActionClauseError, fn ->\n      ActionController.call(stack_conn(), :no_match)\n    end\n  end\n\n  describe \"action_fallback\" do\n    test \"module fallback delegates to plug for bad return values when not configured\" do\n      assert_raise RuntimeError, ~r/expected action\\/2 to return a Plug.Conn/, fn ->\n        MyController.call(stack_conn(), :no_fallback)\n      end\n    end\n\n    test \"module fallback invokes module plug when configured\" do\n      conn = ActionController.call(stack_conn(), :fallback)\n      assert conn.status == 200\n      assert conn.assigns.value_before_action == :a_value\n      assert conn.resp_body == \"fallback\"\n    end\n\n    test \"module fallback with bad return delegates to plug\" do\n      assert_raise RuntimeError, ~r/expected action\\/2 to return a Plug.Conn/, fn ->\n        ActionController.call(stack_conn(), :bad_fallback)\n      end\n    end\n\n    test \"function fallback invokes module plug when configured\" do\n      conn = FallbackFunctionController.call(stack_conn(), :fallback)\n      assert conn.status == 200\n      assert conn.assigns.value_before_action == :a_value\n      assert conn.resp_body == \"function fallback\"\n    end\n\n    test \"function fallback with bad return delegates to plug\" do\n      assert_raise RuntimeError, ~r/expected action\\/2 to return a Plug.Conn/, fn ->\n        FallbackFunctionController.call(stack_conn(), :bad_fallback)\n      end\n    end\n\n    test \"raises when calling from import instead of use\", config do\n      assert_raise RuntimeError, ~r/can only be called when using Phoenix.Controller/, fn ->\n        defmodule config.test do\n          import Phoenix.Controller\n          action_fallback Boom\n        end\n      end\n    end\n\n    test \"raises when calling more than once\", config do\n      assert_raise RuntimeError, ~r/can only be called a single time/, fn ->\n        defmodule config.test do\n          use Phoenix.Controller, formats: []\n          action_fallback Ok\n          action_fallback Boom\n        end\n      end\n    end\n  end\n\n  defp stack_conn() do\n    conn(:get, \"/\")\n    |> fetch_query_params()\n    |> put_private(:stack, [])\n  end\nend\n"
  },
  {
    "path": "test/phoenix/controller/render_test.exs",
    "content": "Code.require_file(\"../../fixtures/views.exs\", __DIR__)\n\ndefmodule Phoenix.Controller.RenderTest do\n  use ExUnit.Case, async: true\n\n  use RouterHelper\n  import Phoenix.Controller\n\n  defp conn() do\n    conn(:get, \"/\") |> fetch_query_params() |> put_view(MyApp.UserView)\n  end\n\n  defp layout_conn() do\n    conn() |> put_layout({MyApp.LayoutView, :app})\n  end\n\n  defp html_response?(conn) do\n    assert get_resp_header(conn, \"content-type\") == [\"text/html; charset=utf-8\"]\n  end\n\n  test \"renders string template\" do\n    conn = render(conn(), \"index.html\", title: \"Hello\")\n    assert conn.resp_body == \"Hello\\n\"\n    assert html_response?(conn)\n    refute conn.halted\n    assert view_template(conn) == \"index.html\"\n  end\n\n  test \"renders atom template\" do\n    conn = put_format(conn(), \"html\")\n    conn = render(conn, :index, title: \"Hello\")\n    assert conn.resp_body == \"Hello\\n\"\n    assert html_response?(conn)\n    refute conn.halted\n    assert view_template(conn) == \"index.html\"\n  end\n\n  test \"renders string template with put layout\" do\n    conn = render(layout_conn(), \"index.html\", title: \"Hello\")\n    assert conn.resp_body =~ ~r\"<title>Hello</title>\"\n    assert html_response?(conn)\n  end\n\n  test \"renders string template with put_root_layout\" do\n    conn =\n      conn()\n      |> put_layout({MyApp.LayoutView, \"app.html\"})\n      |> put_root_layout({MyApp.LayoutView, \"root.html\"})\n      |> render(\"index.html\", title: \"Hello\")\n\n    assert conn.resp_body ==\n             \"ROOTSTART[Hello]<html>\\n  <title>Hello</title>\\n  Hello\\n\\n</html>\\nROOTEND\\n\"\n\n    assert html_response?(conn)\n  end\n\n  test \"renders atom template with put layout\" do\n    conn = put_format(layout_conn(), \"html\")\n    conn = render(conn, :index, title: \"Hello\")\n    assert conn.resp_body =~ ~r\"<title>Hello</title>\"\n    assert html_response?(conn)\n  end\n\n  test \"renders atom template with put_root_layout\" do\n    conn =\n      conn()\n      |> put_layout({MyApp.LayoutView, \"app.html\"})\n      |> put_root_layout({MyApp.LayoutView, :root})\n      |> render(\"index.html\", title: \"Hello\")\n\n    assert conn.resp_body ==\n             \"ROOTSTART[Hello]<html>\\n  <title>Hello</title>\\n  Hello\\n\\n</html>\\nROOTEND\\n\"\n\n    assert html_response?(conn)\n  end\n\n  test \"renders template with overriding layout option\" do\n    conn = render(layout_conn(), \"index.html\", title: \"Hello\", layout: false)\n    assert conn.resp_body == \"Hello\\n\"\n    assert html_response?(conn)\n  end\n\n  test \"renders template with atom layout option\" do\n    conn = render(conn(), \"index.html\", title: \"Hello\", layout: {MyApp.LayoutView, :app})\n    assert conn.resp_body =~ ~r\"<title>Hello</title>\"\n    assert html_response?(conn)\n  end\n\n  test \"renders template with string layout option\" do\n    conn = render(conn(), \"index.html\", title: \"Hello\", layout: {MyApp.LayoutView, \"app.html\"})\n    assert conn.resp_body =~ ~r\"<title>Hello</title>\"\n    assert html_response?(conn)\n  end\n\n  test \"render with layout sets view_module/template for layout and inner view\" do\n    conn = render(conn(), \"inner.html\", title: \"Hello\", layout: {MyApp.LayoutView, :app})\n\n    assert conn.resp_body ==\n             \"<html>\\n  <title>Hello</title>\\n  View module is Elixir.MyApp.UserView and view template is inner.html\\n\\n</html>\\n\"\n  end\n\n  test \"render without layout sets inner view_module/template assigns\" do\n    conn = render(conn(), \"inner.html\", [])\n\n    assert conn.resp_body ==\n             \"View module is Elixir.MyApp.UserView and view template is inner.html\\n\"\n  end\n\n  test \"renders with conn status code\" do\n    conn = %{conn() | status: 404}\n    conn = render(conn, \"index.html\", title: \"Hello\", layout: {MyApp.LayoutView, \"app.html\"})\n    assert conn.status == 404\n  end\n\n  test \"merges render assigns\" do\n    conn = render(conn(), \"index.html\", title: \"Hello\")\n    assert conn.resp_body == \"Hello\\n\"\n    assert conn.assigns.title == \"Hello\"\n  end\n\n  test \"uses connection assigns\" do\n    conn = conn() |> assign(:title, \"Hello\") |> render(\"index.html\")\n    assert conn.resp_body == \"Hello\\n\"\n    assert html_response?(conn)\n  end\n\n  test \"uses custom status\" do\n    conn = render(conn(), \"index.html\", title: \"Hello\")\n    assert conn.status == 200\n\n    conn = render(put_status(conn(), :created), \"index.html\", title: \"Hello\")\n    assert conn.status == 201\n  end\n\n  test \"uses action name\" do\n    conn = put_format(conn(), \"html\")\n    conn = put_in(conn.private[:phoenix_action], :index)\n    conn = render(conn, title: \"Hello\")\n    assert conn.resp_body == \"Hello\\n\"\n  end\n\n  test \"render/2 renders with View and Template with atom for template\" do\n    conn = put_format(conn(), \"json\")\n    conn = put_in(conn.private[:phoenix_action], :show)\n    conn = put_view(conn, MyApp.UserView)\n    conn = render(conn, :show)\n    assert conn.resp_body == ~s({\"foo\":\"bar\"})\n  end\n\n  test \"render/2 renders with View and Template\" do\n    conn = put_format(conn(), \"json\")\n    conn = put_in(conn.private[:phoenix_action], :show)\n    conn = put_view(conn, MyApp.UserView)\n    conn = render(conn, \"show.json\")\n    assert conn.resp_body == ~s({\"foo\":\"bar\"})\n  end\n\n  test \"errors when rendering without format\" do\n    assert_raise RuntimeError, ~r/cannot render template :index because conn.params/, fn ->\n      render(conn(), :index)\n    end\n\n    assert_raise RuntimeError, ~r/cannot render template \"index\" without format/, fn ->\n      render(conn(), \"index\")\n    end\n  end\n\n  test \"errors when rendering without view\" do\n    assert_raise RuntimeError, ~r/no view was found for the format: \"html\"/, fn ->\n      render(conn() |> put_view(nil), \"index.html\")\n    end\n  end\n\n  describe \"telemetry\" do\n    @render_start_event [:phoenix, :controller, :render, :start]\n    @render_stop_event [:phoenix, :controller, :render, :stop]\n    @render_exception_event [:phoenix, :controller, :render, :exception]\n\n    @render_events [\n      @render_start_event,\n      @render_stop_event,\n      @render_exception_event\n    ]\n\n    setup context do\n      :telemetry.attach_many(context.test, @render_events, &__MODULE__.message_pid/4, self())\n      on_exit(fn -> :telemetry.detach(context.test) end)\n    end\n\n    def message_pid(event, measures, metadata, test_pid) do\n      send(test_pid, {:telemetry_event, event, {measures, metadata}})\n    end\n\n    test \"phoenix.controller.render.start and .stop are emitted on success\" do\n      render(conn(), \"index.html\", title: \"Hello\")\n\n      assert_received {:telemetry_event, [:phoenix, :controller, :render, :start],\n                       {_, %{format: \"html\", template: \"index\", view: MyApp.UserView}}}\n\n      assert_received {:telemetry_event, [:phoenix, :controller, :render, :stop],\n                       {_, %{format: \"html\", template: \"index\", view: MyApp.UserView}}}\n\n      refute_received {:telemetry_event, [:phoenix, :controller, :render, :exception], _}\n    end\n\n    test \"phoenix.controller.render.exception is emitted on failure\" do\n      :ok =\n        try do\n          render(conn(), \"index.html\")\n        rescue\n          ArgumentError ->\n            :ok\n        end\n\n      assert_received {:telemetry_event, [:phoenix, :controller, :render, :start],\n                       {_, %{format: \"html\", template: \"index\", view: MyApp.UserView}}}\n\n      refute_received {:telemetry_event, [:phoenix, :controller, :render, :stop], _}\n\n      assert_received {:telemetry_event, [:phoenix, :controller, :render, :exception],\n                       {_,\n                        %{\n                          format: \"html\",\n                          template: \"index\",\n                          view: MyApp.UserView,\n                          kind: :error,\n                          reason: %ArgumentError{}\n                        }}}\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/debug_test.exs",
    "content": "defmodule Phoenix.DebugTest do\n  use ExUnit.Case, async: true\n\n  alias Phoenix.Debug\n\n  # we cannot easily test the Phoenix.Debug functions with the regular\n  # Phoenix.ChannelTest functions, because they use the test process\n  # itself as the transport process\n  defmodule FakeSocket do\n    use GenServer\n\n    def init(channel_pid) do\n      Process.put(:\"$process_label\", {Phoenix.Socket, __MODULE__, nil})\n      {:ok, channel_pid}\n    end\n\n    def handle_info({:debug_channels, ref, reply_to}, state) do\n      send(\n        reply_to,\n        {:debug_channels, ref, [%{pid: state, status: :joined, topic: \"room:lobby\"}]}\n      )\n\n      {:noreply, state}\n    end\n  end\n\n  defmodule FakeChannel do\n    use GenServer\n\n    def init(state) do\n      Process.put(:\"$process_label\", {Phoenix.Channel, __MODULE__.Channel, \"room:lobby\"})\n      {:ok, state}\n    end\n\n    def handle_call(:socket, _from, state) do\n      {:reply, %Phoenix.Socket{}, state}\n    end\n  end\n\n  setup do\n    {:ok, channel_pid} = GenServer.start_link(FakeChannel, nil)\n    {:ok, socket_pid} = GenServer.start_link(FakeSocket, channel_pid)\n\n    %{socket_pid: socket_pid, channel_pid: channel_pid}\n  end\n\n  describe \"list_sockets/0\" do\n    test \"returns a list of all currently connected channel socket processes\", %{\n      socket_pid: socket_pid\n    } do\n      sockets = Debug.list_sockets()\n      assert is_list(sockets)\n      assert Enum.any?(sockets, fn s -> s.pid == socket_pid end)\n      assert Enum.find(sockets, fn s -> s.module == __MODULE__.FakeSocket end)\n    end\n  end\n\n  describe \"socket_process?/1\" do\n    test \"returns true if the given pid is a channel socket process\", %{socket_pid: socket_pid} do\n      assert Debug.socket_process?(socket_pid)\n    end\n\n    test \"returns false for a non-socket process\" do\n      refute Debug.socket_process?(self())\n    end\n  end\n\n  describe \"channel_process?/1\" do\n    test \"returns true if the given pid is a channel process\", %{channel_pid: channel_pid} do\n      assert Debug.channel_process?(channel_pid)\n    end\n\n    test \"returns false for a non-channel process\" do\n      refute Debug.channel_process?(self())\n    end\n  end\n\n  describe \"list_channels/1\" do\n    test \"returns a list of all channels for a given socket pid\", %{\n      socket_pid: socket_pid,\n      channel_pid: channel_pid\n    } do\n      {:ok, channels} = Debug.list_channels(socket_pid)\n      assert is_list(channels)\n      assert Enum.any?(channels, fn ch -> ch.pid == channel_pid and ch.topic == \"room:lobby\" end)\n    end\n\n    test \"returns error for non-socket pid\" do\n      assert {:error, :not_alive} = Debug.list_channels(self())\n    end\n  end\n\n  describe \"socket/1\" do\n    test \"returns the socket struct for a channel process\", %{channel_pid: channel_pid} do\n      assert {:ok, %Phoenix.Socket{}} = Debug.socket(channel_pid)\n    end\n\n    test \"returns error for non-channel process\" do\n      assert {:error, :not_alive_or_not_a_channel} = Debug.socket(self())\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/digester/gzip_test.exs",
    "content": "defmodule Phoenix.Digester.GzipTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Digester.Gzip\n\n  test \"compress_file/2 compresses file\" do\n    file_path = \"test/fixtures/digest/priv/static/css/app.css\"\n    content = File.read!(file_path)\n\n    {:ok, compressed} = Gzip.compress_file(file_path, content)\n\n    assert is_binary(compressed)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/digester_test.exs",
    "content": "defmodule Phoenix.DigesterTest do\n  use ExUnit.Case, async: true\n\n  @output_path Path.join(\"tmp\", \"phoenix_digest\")\n  @fake_now 32_132_173\n  @hash_regex ~S\"[a-fA-F\\d]{32}\"\n\n  defmodule DigestTestCompressor do\n    @behaviour Phoenix.Digester.Compressor\n    def compress_file(_file_path, _content), do: :error\n    def file_extensions, do: [\".digest_test\"]\n  end\n\n  setup do\n    File.rm_rf!(@output_path)\n    on_exit(fn -> File.rm_rf!(@output_path) end)\n    :ok\n  end\n\n  describe \"compile\" do\n    test \"fails when the given paths are invalid\" do\n      assert {:error, :invalid_path} = Phoenix.Digester.compile(\"nonexistent path\", \"/ ?? /path\", true)\n    end\n\n    test \"digests and compress files\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n      output_files = assets_files(@output_path)\n\n      assert \"phoenix.png\" in output_files\n      refute \"phoenix.png.gz\" in output_files\n      assert \"app.js\" in output_files\n      assert \"app.js.gz\" in output_files\n      assert \"app.js.map\" in output_files\n      assert \"app.js.map.gz\" in output_files\n      assert \"css/app.css\" in output_files\n      assert \"css/app.css.gz\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      assert \"cache_manifest.json\" in output_files\n      assert Enum.any?(output_files, &String.match?(&1, ~r/(phoenix-#{@hash_regex}\\.png)/))\n      refute Enum.any?(output_files, &String.match?(&1, ~r/(phoenix-#{@hash_regex}\\.png\\.gz)/))\n\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n      assert json[\"latest\"][\"phoenix.png\"] =~ ~r\"phoenix-#{@hash_regex}.png\"\n      assert json[\"version\"] == 1\n    end\n\n    test \"includes existing digests in new cache manifest\" do\n      source_path = \"test/fixtures/digest/priv/static/\"\n      input_path = \"tmp/digest/static\"\n\n      File.rm_rf!(input_path)\n      :ok = File.mkdir_p!(@output_path)\n      :ok = File.mkdir_p!(input_path)\n\n      {:ok, _} = File.cp_r(source_path, input_path)\n\n      :ok =\n        File.cp(\n          Path.join(source_path, \"foo.css\"),\n          Path.join(@output_path, \"foo-d978852bea6530fcd197b5445ed008fd.css\")\n        )\n\n      :ok =\n        File.cp(\n          \"test/fixtures/digest/compile/cache_manifest.json\",\n          Path.join(@output_path, \"cache_manifest.json\")\n        )\n\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n\n      # Keep old entries\n      assert json[\"digests\"][\"foo-d978852bea6530fcd197b5445ed008fd.css\"][\"logical_path\"] ==\n               \"foo.css\"\n\n      # Update mtime\n      assert_in_delta json[\"digests\"][\"foo-d978852bea6530fcd197b5445ed008fd.css\"][\"mtime\"],\n                      now(),\n                      2\n\n      # Add new entries\n      key = Enum.find(Map.keys(json[\"digests\"]), &(&1 =~ ~r\"phoenix-#{@hash_regex}.png\"))\n      assert json[\"version\"] == 1\n      assert is_integer(json[\"digests\"][key][\"mtime\"])\n      assert json[\"digests\"][key][\"logical_path\"] == \"phoenix.png\"\n      assert json[\"digests\"][key][\"size\"] == 13900\n      assert json[\"digests\"][key][\"digest\"] =~ ~r\"#{@hash_regex}\"\n\n      assert json[\"digests\"][key][\"sha512\"] ==\n               \"93pY5dBa8nHHi0Zfj75O/vXCBXb+UvEVCyU7Yd3pzOJ7o1wkYBWbvs3pVXhBChEmo8MDANT11vsggo2+bnYqoQ==\"\n    after\n      File.rm_rf!(\"tmp/digest\")\n    end\n\n    test \"excludes compiled files\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n      output_files = assets_files(@output_path)\n\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n      refute json[\"latest\"][\"precompressed.js.gz\"]\n\n      assert :ok = Phoenix.Digester.compile(@output_path, @output_path, true)\n      assert output_files == assets_files(@output_path)\n    end\n\n    test \"old versions maintain their mtime\" do\n      source_path = \"test/fixtures/digest/priv/static/\"\n      input_path = \"tmp/digest/static\"\n      File.rm_rf!(input_path)\n      :ok = File.mkdir_p!(@output_path)\n      :ok = File.mkdir_p!(input_path)\n\n      :ok =\n        File.cp(\n          Path.join(source_path, \"foo.css\"),\n          Path.join(@output_path, \"foo-d978852bea6530fcd197b5445ed008fd.css\")\n        )\n\n      :ok =\n        File.cp(\n          \"test/fixtures/digest/compile/cache_manifest.json\",\n          Path.join(@output_path, \"cache_manifest.json\")\n        )\n\n      File.write!(Path.join(input_path, \"foo.css\"), \".foo { background-color: blue }\")\n\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n\n      assert json[\"digests\"][\"foo-d978852bea6530fcd197b5445ed008fd.css\"][\"mtime\"] == 32_132_171\n\n      assert_in_delta json[\"digests\"][\"foo-1198fd3c7ecf0e8f4a33a6e4fc5ae168.css\"][\"mtime\"],\n                      now(),\n                      2\n    after\n      File.rm_rf!(\"tmp/digest\")\n    end\n\n    test \"excludes files that no longer exist from cache manifest\" do\n      input_path = \"tmp/digest/static\"\n      File.rm_rf!(input_path)\n      :ok = File.mkdir_p!(input_path)\n\n      :ok =\n        File.cp(\n          \"test/fixtures/digest/compile/cache_manifest.json\",\n          Path.join(input_path, \"cache_manifest.json\")\n        )\n\n      assert :ok = Phoenix.Digester.compile(input_path, input_path, true)\n\n      json = Path.join(input_path, \"cache_manifest.json\") |> json_read!()\n      assert json[\"digests\"] == %{}\n    after\n      File.rm_rf!(\"tmp/digest\")\n    end\n\n    test \"digests and compress nested files\" do\n      input_path = \"test/fixtures/digest/priv/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      output_files = assets_files(@output_path)\n\n      assert \"static/phoenix.png\" in output_files\n      refute \"static/phoenix.png.gz\" in output_files\n      assert \"cache_manifest.json\" in output_files\n      assert Enum.any?(output_files, &String.match?(&1, ~r/(phoenix-#{@hash_regex}\\.png)/))\n      refute Enum.any?(output_files, &String.match?(&1, ~r/(phoenix-#{@hash_regex}\\.png\\.gz)/))\n\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n      assert json[\"latest\"][\"static/phoenix.png\"] =~ ~r\"static/phoenix-#{@hash_regex}\\.png\"\n    end\n\n    test \"keeps old version in cache manifest when digesting twice\" do\n      input_path = Path.join(\"tmp\", \"phoenix_digest_twice\")\n      input_file = Path.join(input_path, \"file.js\")\n\n      try do\n        File.rm_rf!(input_path)\n        File.mkdir_p!(input_path)\n        File.mkdir_p!(@output_path)\n\n        File.write!(input_file, \"console.log('test');\")\n        assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n        json1 = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n        assert Enum.count(json1[\"digests\"]) == 1\n\n        File.write!(input_file, \"console.log('test2');\")\n        assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n        json2 = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n        assert Enum.count(json2[\"digests\"]) == 2\n      after\n        File.rm_rf!(input_path)\n      end\n    end\n\n    test \"doesn't duplicate files when digesting and compressing twice\" do\n      input_path = Path.join(\"tmp\", \"phoenix_digest_twice\")\n      input_file = Path.join(input_path, \"file.js\")\n\n      try do\n        File.rm_rf!(input_path)\n        File.mkdir_p!(input_path)\n        File.write!(input_file, \"console.log('test');\")\n\n        assert :ok = Phoenix.Digester.compile(input_path, input_path, true)\n        assert :ok = Phoenix.Digester.compile(input_path, input_path, true)\n\n        output_files = assets_files(input_path)\n        refute \"file.js.gz.gz\" in output_files\n        refute \"cache_manifest.json.gz\" in output_files\n        refute Enum.any?(output_files, &(&1 =~ ~r/file-#{@hash_regex}.[\\w|\\d]*.[-#{@hash_regex}/))\n      after\n        File.rm_rf!(input_path)\n      end\n    end\n\n    test \"digests only absolute and relative asset paths found within stylesheets with vsn\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      digested_css_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.css\"))\n\n      digested_css =\n        Path.join(@output_path, digested_css_filename)\n        |> File.read!()\n\n      refute digested_css =~ ~r\"/phoenix\\.png\"\n      refute digested_css =~ ~r\"\\.\\./images/relative\\.png\"\n      assert digested_css =~ ~r\"/phoenix-#{@hash_regex}\\.png\\?vsn=d\"\n      assert digested_css =~ ~r\"\\.\\./images/relative-#{@hash_regex}\\.png\\?vsn=d\"\n\n      refute digested_css =~ ~r\"http://www.phoenixframework.org/absolute-#{@hash_regex}.png\"\n      assert digested_css =~ ~r\"http://www.phoenixframework.org/absolute.png\"\n    end\n\n    test \"digests only absolute and relative asset paths found within stylesheets without vsn\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, false)\n\n      digested_css_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.css\"))\n\n      digested_css =\n        Path.join(@output_path, digested_css_filename)\n        |> File.read!()\n\n      refute digested_css =~ ~r\"/phoenix\\.png\"\n      refute digested_css =~ ~r\"\\.\\./images/relative\\.png\"\n      assert digested_css =~ ~r\"/phoenix-#{@hash_regex}\\.png\"\n      assert digested_css =~ ~r\"\\.\\./images/relative-#{@hash_regex}\\.png\"\n\n      refute digested_css =~ ~r\"http://www.phoenixframework.org/absolute-#{@hash_regex}.png\"\n      assert digested_css =~ ~r\"http://www.phoenixframework.org/absolute.png\"\n    end\n\n    test \"sha512 matches content of digested file\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      digested_css_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.css\"))\n\n      digested_css =\n        Path.join(@output_path, digested_css_filename)\n        |> File.read!()\n\n      cache_manifest_file =\n        @output_path\n        |> assets_files()\n        |> Enum.find(&(&1 =~ ~r\"cache_manifest.json\"))\n\n      json = Path.join(@output_path, cache_manifest_file) |> json_read!()\n      integrity = Base.encode64(:crypto.hash(:sha512, digested_css))\n      assert json[\"digests\"][digested_css_filename][\"sha512\"] == integrity\n    end\n\n    test \"digests sourceMappingURL asset paths found within javascript source files\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      digested_js_map_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.js.map\"))\n\n      digested_js_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.js\"))\n\n      digested_js = Path.join(@output_path, digested_js_filename) |> File.read!()\n      refute digested_js =~ \"app.js.map\"\n      assert digested_js =~ \"#{digested_js_map_filename}\"\n\n      js = Path.join(@output_path, \"app.js\") |> File.read!()\n      assert js =~ \"app.js.map\"\n      refute js =~ \"#{digested_js_map_filename}\"\n    end\n\n    test \"digests file url paths found within javascript mapping files\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      digested_js_map_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.js.map\"))\n\n      digested_js_filename =\n        assets_files(@output_path)\n        |> Enum.find(&(&1 =~ ~r\"app-#{@hash_regex}.js\"))\n\n      digested_js_map =\n        Path.join(@output_path, digested_js_map_filename)\n        |> File.read!()\n\n      refute digested_js_map =~ ~r\"\\\"file\\\":\\\"app.js\\\"\"\n      assert digested_js_map =~ ~r\"#{digested_js_filename}\"\n    end\n\n    test \"does not digest assets within undigested files\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      undigested_css =\n        Path.join(@output_path, \"css/app.css\")\n        |> File.read!()\n\n      assert undigested_css =~ ~r\"/phoenix\\.png\"\n      assert undigested_css =~ ~r\"\\.\\./images/relative\\.png\"\n      refute undigested_css =~ ~r\"/phoenix-#{@hash_regex}\\.png\"\n      refute undigested_css =~ ~r\"\\.\\./images/relative-#{@hash_regex}\\.png\"\n    end\n\n    test \"digested sourcemaps and their asset share the same hash\" do\n      input_path = \"test/fixtures/digest/priv/static/\"\n      assert :ok = Phoenix.Digester.compile(input_path, @output_path, true)\n\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n\n      assert json[\"latest\"][\"app.js.map\"] == json[\"latest\"][\"app.js\"] <> \".map\"\n    end\n  end\n\n  describe \"clean\" do\n    test \"fails when the given path is invalid\" do\n      assert {:error, :invalid_path} = Phoenix.Digester.clean(\"nonexistent path\", 3600, 2)\n    end\n\n    test \"removes versions over the keep count\" do\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-2.css\")\n      File.touch(\"#{@output_path}/app-2.css.gz\")\n      File.touch(\"#{@output_path}/app-3.css\")\n      File.touch(\"#{@output_path}/app-3.css.gz\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/app.css\")\n\n      assert :ok = Phoenix.Digester.clean(@output_path, 3600, 1, @fake_now)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      assert \"app-3.css\" in output_files\n      assert \"app-3.css.gz\" in output_files\n      assert \"app-2.css\" in output_files\n      assert \"app-2.css.gz\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      refute \"app-1.css\" in output_files\n      refute \"app-1.css.gz\" in output_files\n    end\n\n    test \"removes files older than specified number of seconds\" do\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-2.css\")\n      File.touch(\"#{@output_path}/app-2.css.gz\")\n      File.touch(\"#{@output_path}/app-3.css\")\n      File.touch(\"#{@output_path}/app-3.css.gz\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/app.css\")\n\n      assert :ok = Phoenix.Digester.clean(@output_path, 1, 10, @fake_now)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      assert \"app-2.css\" in output_files\n      assert \"app-2.css.gz\" in output_files\n      assert \"app-3.css\" in output_files\n      assert \"app-3.css.gz\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      refute \"app-1.css\" in output_files\n      refute \"app-1.css.gz\" in output_files\n    end\n\n    test \"cleaning doesn't delete the latest even if the mtime is wrong\" do\n      manifest_path = \"test/fixtures/digest/cleaner/latest_not_most_recent_cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-2.css\")\n      File.touch(\"#{@output_path}/app-2.css.gz\")\n      File.touch(\"#{@output_path}/app-3.css\")\n      File.touch(\"#{@output_path}/app-3.css.gz\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/app.css\")\n\n      assert :ok = Phoenix.Digester.clean(@output_path, 3600, 1, @fake_now)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      assert \"app-3.css\" in output_files\n      assert \"app-3.css.gz\" in output_files\n      assert \"app-2.css\" in output_files\n      assert \"app-2.css.gz\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      refute \"app-1.css\" in output_files\n      refute \"app-1.css.gz\" in output_files\n    end\n\n    test \"cleaning updates cache manifest to remove cleaned files\" do\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-2.css\")\n      File.touch(\"#{@output_path}/app-2.css.gz\")\n      File.touch(\"#{@output_path}/app-3.css\")\n      File.touch(\"#{@output_path}/app-3.css.gz\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/app.css\")\n\n      assert :ok = Phoenix.Digester.clean(@output_path, 3600, 1, @fake_now)\n      json = Path.join(@output_path, \"cache_manifest.json\") |> json_read!()\n      refute json[\"digests\"][\"app-1.css\"]\n    end\n\n    test \"removes compressed versions from all static compressors\" do\n      add_digest_test_compressor()\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-1.css.digest_test\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/manifest.json.digest_test\")\n\n      assert :ok = Phoenix.Digester.clean(@output_path, 1, 10, @fake_now)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      assert \"manifest.json.digest_test\" in output_files\n      refute \"app-1.css\" in output_files\n      refute \"app-1.css.gz\" in output_files\n      refute \"app-1.css.digest_test\" in output_files\n    end\n  end\n\n  describe \"clean_all\" do\n    test \"fails when the given path is invalid\" do\n      assert {:error, :invalid_path} = Phoenix.Digester.clean_all(\"nonexistent path\")\n    end\n\n    test \"no-op when the given path does not contain cache_manifest.json\" do\n      output_path = \"test/fixtures/digest/priv/static/css\"\n      assert assets_files(output_path) == [\"app.css\"]\n      assert :ok = Phoenix.Digester.clean_all(output_path)\n      assert assets_files(output_path) == [\"app.css\"]\n    end\n\n    test \"removes all compressed/compiled files including latest and manifest\" do\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app.css.gz\")\n      File.touch(\"#{@output_path}/app-1.css\")\n      File.touch(\"#{@output_path}/app-1.css.gz\")\n      File.touch(\"#{@output_path}/app-2.css\")\n      File.touch(\"#{@output_path}/app-2.css.gz\")\n      File.touch(\"#{@output_path}/app-3.css\")\n      File.touch(\"#{@output_path}/app-3.css.gz\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/app.css\")\n\n      assert :ok = Phoenix.Digester.clean_all(@output_path)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      refute \"app.css.gz\" in output_files\n      refute \"app-3.css\" in output_files\n      refute \"app-3.css.gz\" in output_files\n      refute \"app-2.css\" in output_files\n      refute \"app-2.css.gz\" in output_files\n      refute \"app-1.css\" in output_files\n      refute \"app-1.css.gz\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      refute \"cache_manifest.json\" in output_files\n    end\n\n    test \"removes compressed versions from all static compressors\" do\n      add_digest_test_compressor()\n      manifest_path = \"test/fixtures/digest/cleaner/cache_manifest.json\"\n      File.mkdir_p!(@output_path)\n      File.cp(manifest_path, \"#{@output_path}/cache_manifest.json\")\n      File.touch(\"#{@output_path}/app.css\")\n      File.touch(\"#{@output_path}/app.css.gz\")\n      File.touch(\"#{@output_path}/app.css.digest_test\")\n      File.touch(\"#{@output_path}/manifest.json\")\n      File.touch(\"#{@output_path}/manifest.json.gz\")\n      File.touch(\"#{@output_path}/manifest.json.digest_test\")\n\n      assert :ok = Phoenix.Digester.clean_all(@output_path)\n      output_files = assets_files(@output_path)\n\n      assert \"app.css\" in output_files\n      refute \"app.css.gz\" in output_files\n      refute \"app.css.digest_test\" in output_files\n      assert \"manifest.json\" in output_files\n      assert \"manifest.json.gz\" in output_files\n      assert \"manifest.json.digest_test\" in output_files\n      refute \"cache_manifest.json\" in output_files\n    end\n  end\n\n  defp assets_files(path) do\n    path\n    |> Path.join(\"**/*\")\n    |> Path.wildcard()\n    |> Enum.filter(&(!File.dir?(&1)))\n    |> Enum.map(&Path.relative_to(&1, path))\n  end\n\n  defp now do\n    :calendar.datetime_to_gregorian_seconds(:calendar.universal_time())\n  end\n\n  defp json_read!(path) do\n    path\n    |> File.read!()\n    |> Phoenix.json_library().decode!()\n  end\n\n  defp add_digest_test_compressor() do\n    compressors = Application.fetch_env!(:phoenix, :static_compressors)\n    Application.put_env(:phoenix, :static_compressors, [DigestTestCompressor | compressors])\n    on_exit(fn -> Application.put_env(:phoenix, :static_compressors, compressors) end)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/endpoint/endpoint_test.exs",
    "content": "System.put_env(\"ENDPOINT_TEST_HOST\", \"example.com\")\nSystem.put_env(\"ENDPOINT_TEST_PORT\", \"80\")\nSystem.put_env(\"ENDPOINT_TEST_ASSET_HOST\", \"assets.example.com\")\nSystem.put_env(\"ENDPOINT_TEST_ASSET_PORT\", \"443\")\n\ndefmodule Phoenix.Endpoint.EndpointTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  @config [\n    url: [host: {:system, \"ENDPOINT_TEST_HOST\"}, path: \"/api\"],\n    static_url: [host: \"static.example.com\"],\n    server: false,\n    http: [port: 80],\n    https: [port: 443],\n    force_ssl: [subdomains: true],\n    cache_manifest_skip_vsn: false,\n    cache_static_manifest: \"../../../../test/fixtures/digest/compile/cache_manifest.json\",\n    pubsub_server: :endpoint_pub\n  ]\n\n  Application.put_env(:phoenix, __MODULE__.Endpoint, @config)\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    # Assert endpoint variables\n    assert @otp_app == :phoenix\n    assert code_reloading? == false\n    assert debug_errors? == false\n  end\n\n  defmodule NoConfigEndpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n  end\n\n  defmodule SystemTupleEndpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n  end\n\n  defmodule TelemetryEventEndpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n  end\n\n  setup_all do\n    ExUnit.CaptureLog.capture_log(fn -> start_supervised!(Endpoint) end)\n    start_supervised!({Phoenix.PubSub, name: :endpoint_pub})\n    on_exit(fn -> Application.delete_env(:phoenix, :serve_endpoints) end)\n    :ok\n  end\n\n  test \"defines child_spec/1\" do\n    assert Endpoint.child_spec([]) == %{\n             id: Endpoint,\n             start: {Endpoint, :start_link, [[]]},\n             type: :supervisor\n           }\n  end\n\n  test \"warns if there is no configuration for an endpoint\" do\n    assert ExUnit.CaptureLog.capture_log(fn ->\n             NoConfigEndpoint.start_link()\n           end) =~ \"no configuration\"\n  end\n\n  test \"has reloadable configuration\" do\n    endpoint_id = Endpoint.config(:endpoint_id)\n    assert Endpoint.config(:url) == [host: {:system, \"ENDPOINT_TEST_HOST\"}, path: \"/api\"]\n    assert Endpoint.config(:static_url) == [host: \"static.example.com\"]\n    assert Endpoint.url() == \"https://example.com\"\n    assert Endpoint.path(\"/\") == \"/api/\"\n    assert Endpoint.static_url() == \"https://static.example.com\"\n    assert Endpoint.struct_url() == %URI{scheme: \"https\", host: \"example.com\", port: 443}\n\n    config =\n      @config\n      |> put_in([:url, :port], 1234)\n      |> put_in([:static_url, :port], 456)\n\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n    assert Endpoint.config(:endpoint_id) == endpoint_id\n\n    assert Enum.sort(Endpoint.config(:url)) ==\n             [host: {:system, \"ENDPOINT_TEST_HOST\"}, path: \"/api\", port: 1234]\n\n    assert Enum.sort(Endpoint.config(:static_url)) ==\n             [host: \"static.example.com\", port: 456]\n\n    assert Endpoint.url() == \"https://example.com:1234\"\n    assert Endpoint.path(\"/\") == \"/api/\"\n    assert Endpoint.static_url() == \"https://static.example.com:456\"\n    assert Endpoint.struct_url() == %URI{scheme: \"https\", host: \"example.com\", port: 1234}\n  end\n\n  test \"{:system, env_var} tuples are deprecated\" do\n    Application.put_env(:phoenix, __MODULE__.SystemTupleEndpoint,\n      url: [\n        host: {:system, \"ENDPOINT_TEST_HOST\"},\n        port: {:system, \"ENDPOINT_TEST_PORT\"}\n      ],\n      static_url: [\n        host: {:system, \"ENDPOINT_TEST_ASSET_HOST\"},\n        port: {:system, \"ENDPOINT_TEST_ASSET_PORT\"}\n      ]\n    )\n\n    log =\n      ExUnit.CaptureLog.capture_log(fn ->\n        start_supervised!(SystemTupleEndpoint)\n      end)\n\n    assert log =~ ~S|host: System.get_env(\"ENDPOINT_TEST_HOST\")|\n    assert log =~ ~S|port: System.get_env(\"ENDPOINT_TEST_PORT\")|\n    assert log =~ ~S|host: System.get_env(\"ENDPOINT_TEST_ASSET_HOST\")|\n    assert log =~ ~S|port: System.get_env(\"ENDPOINT_TEST_ASSET_PORT\")|\n  end\n\n  test \"start_link/2 should emit an Endpoint init event\" do\n    # Set up the test telemetry event\n    :telemetry.attach(\n      [:test, :endpoint, :init, :handler],\n      [:phoenix, :endpoint, :init],\n      &__MODULE__.validate_init_event/4,\n      nil\n    )\n\n    Application.put_env(:phoenix, __MODULE__.TelemetryEventEndpoint, server: false)\n    start_supervised!(TelemetryEventEndpoint)\n  after\n    :telemetry.detach([:test, :endpoint, :init, :handler])\n  end\n\n  def validate_init_event(event, measurements, metadata, _config) do\n    assert event == [:phoenix, :endpoint, :init]\n    assert Process.whereis(TelemetryEventEndpoint) == metadata.pid\n    assert metadata.module == TelemetryEventEndpoint\n    assert metadata.otp_app == :phoenix\n    assert metadata.config == [server: false]\n    assert Map.has_key?(measurements, :system_time)\n  end\n\n  test \"sets script name when using path\" do\n    conn = conn(:get, \"https://example.com/\")\n    assert Endpoint.call(conn, []).script_name == ~w\"api\"\n\n    conn = put_in(conn.script_name, ~w(foo))\n    assert Endpoint.call(conn, []).script_name == ~w\"api\"\n  end\n\n  @tag :capture_log\n  test \"redirects http requests to https on force_ssl\" do\n    conn = Endpoint.call(conn(:get, \"/\"), [])\n    assert get_resp_header(conn, \"location\") == [\"https://example.com/\"]\n    assert conn.halted\n  end\n\n  test \"sends hsts on https requests on force_ssl\" do\n    conn = Endpoint.call(conn(:get, \"https://example.com/\"), [])\n\n    assert get_resp_header(conn, \"strict-transport-security\") ==\n             [\"max-age=31536000; includeSubDomains\"]\n  end\n\n  test \"warms up caches on load and config change\" do\n    assert Endpoint.config_change([{Endpoint, @config}], []) == :ok\n\n    assert Endpoint.config(:cache_static_manifest_latest) ==\n             %{\"foo.css\" => \"foo-d978852bea6530fcd197b5445ed008fd.css\"}\n\n    assert Endpoint.static_path(\"/foo.css\") == \"/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d\"\n\n    # Trigger a config change and the cache should be warmed up again\n    config =\n      put_in(\n        @config[:cache_static_manifest],\n        \"../../../../test/fixtures/digest/compile/cache_manifest_upgrade.json\"\n      )\n\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n    assert Endpoint.config(:cache_static_manifest_latest) == %{\"foo.css\" => \"foo-ghijkl.css\"}\n    assert Endpoint.static_path(\"/foo.css\") == \"/foo-ghijkl.css?vsn=d\"\n  end\n\n  test \"uses correct path accordingly to vsn setting\" do\n    config = put_in(@config[:cache_manifest_skip_vsn], false)\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n    assert Endpoint.static_path(\"/foo.css\") == \"/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d\"\n\n    config = put_in(@config[:cache_manifest_skip_vsn], true)\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n    assert Endpoint.static_path(\"/foo.css\") == \"/foo-d978852bea6530fcd197b5445ed008fd.css\"\n  end\n\n  test \"uses correct path for resources with fragment identifier\" do\n    config = put_in(@config[:cache_manifest_skip_vsn], false)\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n\n    assert Endpoint.static_path(\"/foo.css#info\") ==\n             \"/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d#info\"\n\n    # assert that even multiple presences of a number sign are treated as a fragment\n    assert Endpoint.static_path(\"/foo.css#info#me\") ==\n             \"/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d#info#me\"\n  end\n\n  @tag :capture_log\n  test \"uses url configuration for static path\" do\n    Application.put_env(:phoenix, __MODULE__.UrlEndpoint, url: [path: \"/api\"])\n\n    defmodule UrlEndpoint do\n      use Phoenix.Endpoint, otp_app: :phoenix\n    end\n\n    UrlEndpoint.start_link()\n    assert UrlEndpoint.path(\"/phoenix.png\") =~ \"/api/phoenix.png\"\n    assert UrlEndpoint.static_path(\"/phoenix.png\") =~ \"/api/phoenix.png\"\n  after\n    :code.purge(__MODULE__.UrlEndpoint)\n    :code.delete(__MODULE__.UrlEndpoint)\n  end\n\n  @tag :capture_log\n  test \"uses static_url configuration for static path\" do\n    Application.put_env(:phoenix, __MODULE__.StaticEndpoint, static_url: [path: \"/static\"])\n\n    defmodule StaticEndpoint do\n      use Phoenix.Endpoint, otp_app: :phoenix\n    end\n\n    StaticEndpoint.start_link()\n    assert StaticEndpoint.path(\"/phoenix.png\") =~ \"/phoenix.png\"\n    assert StaticEndpoint.static_path(\"/phoenix.png\") =~ \"/static/phoenix.png\"\n  after\n    :code.purge(__MODULE__.StaticEndpoint)\n    :code.delete(__MODULE__.StaticEndpoint)\n  end\n\n  @tag :capture_log\n  test \"can find the running address and port for an endpoint\" do\n    Application.put_env(:phoenix, __MODULE__.AddressEndpoint,\n      http: [ip: {127, 0, 0, 1}, port: 0],\n      server: true\n    )\n\n    defmodule AddressEndpoint do\n      use Phoenix.Endpoint, otp_app: :phoenix\n    end\n\n    AddressEndpoint.start_link()\n    assert {:ok, {{127, 0, 0, 1}, port}} = AddressEndpoint.server_info(:http)\n    assert is_integer(port)\n  after\n    :code.purge(__MODULE__.AddressEndpoint)\n    :code.delete(__MODULE__.AddressEndpoint)\n  end\n\n  test \"injects pubsub broadcast with configured server\" do\n    Endpoint.subscribe(\"sometopic\")\n    some = spawn(fn -> :ok end)\n\n    Endpoint.broadcast_from(some, \"sometopic\", \"event1\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event1\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    Endpoint.broadcast_from!(some, \"sometopic\", \"event2\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event2\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    Endpoint.broadcast(\"sometopic\", \"event3\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event3\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    Endpoint.broadcast!(\"sometopic\", \"event4\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event4\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    Endpoint.local_broadcast_from(some, \"sometopic\", \"event1\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event1\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n\n    Endpoint.local_broadcast(\"sometopic\", \"event3\", %{key: :val})\n\n    assert_receive %Phoenix.Socket.Broadcast{\n      event: \"event3\",\n      payload: %{key: :val},\n      topic: \"sometopic\"\n    }\n  end\n\n  test \"loads cache manifest from specified application\" do\n    config =\n      put_in(\n        @config[:cache_static_manifest],\n        {:phoenix, \"../../../../test/fixtures/digest/compile/cache_manifest.json\"}\n      )\n\n    assert Endpoint.config_change([{Endpoint, config}], []) == :ok\n    assert Endpoint.static_path(\"/foo.css\") == \"/foo-d978852bea6530fcd197b5445ed008fd.css?vsn=d\"\n  end\n\n  test \"server?/2 returns true for explicitly true server\", config do\n    endpoint = Module.concat(__MODULE__, config.test)\n    Application.put_env(:phoenix, endpoint, server: true)\n    assert Phoenix.Endpoint.server?(:phoenix, endpoint)\n  end\n\n  test \"server?/2 returns false for explicitly false server\", config do\n    Application.put_env(:phoenix, :serve_endpoints, true)\n    endpoint = Module.concat(__MODULE__, config.test)\n    Application.put_env(:phoenix, endpoint, server: false)\n    refute Phoenix.Endpoint.server?(:phoenix, endpoint)\n  end\n\n  test \"server?/2 returns true for global serve_endpoints as true\", config do\n    Application.put_env(:phoenix, :serve_endpoints, true)\n    endpoint = Module.concat(__MODULE__, config.test)\n    Application.put_env(:phoenix, endpoint, [])\n    assert Phoenix.Endpoint.server?(:phoenix, endpoint)\n  end\n\n  test \"server?/2 returns false for no global serve_endpoints config\", config do\n    Application.delete_env(:phoenix, :serve_endpoints)\n    endpoint = Module.concat(__MODULE__, config.test)\n    Application.put_env(:phoenix, endpoint, [])\n    refute Phoenix.Endpoint.server?(:phoenix, endpoint)\n  end\n\n  test \"static_path/1 validates paths are local/safe\" do\n    safe_path = \"/some_safe_path\"\n    assert Endpoint.static_path(safe_path) == safe_path\n\n    assert_raise ArgumentError, ~r/unsafe characters/, fn ->\n      Endpoint.static_path(\"/\\\\unsafe_path\")\n    end\n\n    assert_raise ArgumentError, ~r/expected a path starting with a single/, fn ->\n      Endpoint.static_path(\"//invalid_path\")\n    end\n  end\n\n  test \"static_integrity/1 validates paths are local/safe\" do\n    safe_path = \"/some_safe_path\"\n    assert is_nil(Endpoint.static_integrity(safe_path))\n\n    assert_raise ArgumentError, ~r/unsafe characters/, fn ->\n      Endpoint.static_integrity(\"/\\\\unsafe_path\")\n    end\n\n    assert_raise ArgumentError, ~r/expected a path starting with a single/, fn ->\n      Endpoint.static_integrity(\"//invalid_path\")\n    end\n  end\n\n  test \"validates websocket and longpoll socket options\" do\n    assert_raise ArgumentError, ~r/unknown keys \\[:invalid\\]/, fn ->\n      defmodule MyInvalidSocketEndpoint1 do\n        use Phoenix.Endpoint, otp_app: :phoenix\n\n        socket \"/ws\", UserSocket, websocket: [path: \"/ws\", check_origin: false, invalid: true]\n      end\n    end\n\n    assert_raise ArgumentError, ~r/unknown keys \\[:drainer\\]/, fn ->\n      defmodule MyInvalidSocketEndpoint2 do\n        use Phoenix.Endpoint, otp_app: :phoenix\n\n        socket \"/ws\", UserSocket, longpoll: [path: \"/ws\", check_origin: false, drainer: []]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/endpoint/render_errors_test.exs",
    "content": "defmodule Phoenix.Endpoint.RenderErrorsTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  Application.put_env(:phoenix, __MODULE__.Endpoint, [])\n\n  import ExUnit.CaptureLog\n  view = __MODULE__\n\n  def render(\"app.html\", assigns) do\n    \"Layout: \" <> assigns.inner_content\n  end\n\n  def render(\"404.html\", %{\n        kind: kind,\n        reason: _reason,\n        stack: _stack,\n        status: 404,\n        conn: conn,\n        __changed__: nil\n      }) do\n    \"Got 404 from #{kind} with #{conn.method}\"\n  end\n\n  def render(\"404.json\", %{\n        kind: kind,\n        reason: _reason,\n        stack: _stack,\n        status: 404,\n        conn: conn,\n        __changed__: nil\n      }) do\n    %{error: \"Got 404 from #{kind} with #{conn.method}\"}\n  end\n\n  def render(\"415.html\", %{\n        kind: kind,\n        reason: _reason,\n        stack: _stack,\n        status: 415,\n        conn: conn,\n        __changed__: nil\n      }) do\n    \"Got 415 from #{kind} with #{conn.method}\"\n  end\n\n  def render(\"500.html\", %{\n        kind: kind,\n        reason: _reason,\n        stack: _stack,\n        status: 500,\n        conn: conn,\n        __changed__: nil\n      }) do\n    \"Got 500 from #{kind} with #{conn.method}\"\n  end\n\n  def render(\"500.text\", _) do\n    \"500 in TEXT\"\n  end\n\n  defmodule Router do\n    use Plug.Router\n    use Phoenix.Endpoint.RenderErrors, view: view, accepts: ~w(html json)\n\n    plug :match\n    plug :dispatch\n\n    get \"/boom\" do\n      resp(conn, 200, \"oops\")\n      raise \"oops\"\n    end\n\n    get \"/send_and_boom\" do\n      send_resp(conn, 200, \"oops\")\n      raise \"oops\"\n    end\n\n    get \"/send_and_wrapped\" do\n      stack =\n        try do\n          raise \"oops\"\n        rescue\n          _ -> __STACKTRACE__\n        end\n\n      # Those are always ignored and must be explicitly opted-in.\n      conn =\n        conn\n        |> Phoenix.Controller.put_layout({Unknown, \"layout\"})\n        |> Phoenix.Controller.put_root_layout({Unknown, \"root\"})\n\n      reason = ArgumentError.exception(\"oops\")\n      raise Plug.Conn.WrapperError, conn: conn, kind: :error, stack: stack, reason: reason\n    end\n\n    match _ do\n      raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__\n    end\n  end\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"call/2 is overridden\" do\n    assert_raise RuntimeError, \"oops\", fn ->\n      call(Router, :get, \"/boom\")\n    end\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  test \"call/2 is overridden but is a no-op when response is already sent\" do\n    assert_raise RuntimeError, \"oops\", fn ->\n      call(Router, :get, \"/send_and_boom\")\n    end\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  test \"call/2 is overridden with no route match as HTML and does not reraise\" do\n    call(Router, :get, \"/unknown\")\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  test \"call/2 is overridden with no route match as JSON and does not reraise\" do\n    call(Router, :get, \"/unknown?_format=json\")\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  @tag :capture_log\n  test \"call/2 is overridden with no route match while malformed format and does not reraise\" do\n    call(Router, :get, \"/unknown?_format=unknown\")\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  test \"call/2 is overridden and unwraps wrapped errors\" do\n    assert_raise ArgumentError, \"oops\", fn ->\n      conn(:get, \"/send_and_wrapped\") |> Router.call([])\n    end\n\n    assert_received {:plug_conn, :sent}\n  end\n\n  test \"logs converted errors if response has not yet been sent\" do\n    Logger.delete_process_level(self())\n    conn = put_endpoint(conn(:get, \"/\"))\n\n    assert capture_log(fn ->\n             assert_render(500, conn, [], fn -> throw(:hello) end)\n           end) =~ \"Converted throw :hello to 500\"\n\n    assert capture_log(fn ->\n             assert_render(500, conn, [], fn -> raise \"boom\" end)\n           end) =~ \"Converted error RuntimeError to 500\"\n\n    assert capture_log(fn ->\n             assert_render(500, conn, [], fn -> exit(:timeout) end)\n           end) =~ \"Converted exit :timeout to 500\"\n  end\n\n  test \"does not log converted errors if response already sent\" do\n    conn = put_endpoint(conn(:get, \"/\"))\n\n    try do\n      try do\n        Plug.Conn.send_resp(conn, 200, \"hello\")\n        throw(:hello)\n      catch\n        kind, reason ->\n          stack = __STACKTRACE__\n          opts = [view: __MODULE__, accepts: ~w(html)]\n          Phoenix.Endpoint.RenderErrors.__catch__(conn, kind, reason, stack, opts)\n      else\n        _ -> flunk(\"function should have failed\")\n      end\n    catch\n      :throw, :hello -> :ok\n    end\n  end\n\n  defp put_endpoint(conn) do\n    Plug.Conn.put_private(conn, :phoenix_endpoint, Endpoint)\n  end\n\n  defp assert_render(status, conn, opts, func) do\n    opts =\n      if opts[:formats] do\n        opts\n      else\n        opts\n        |> Keyword.put_new(:view, __MODULE__)\n        |> Keyword.put_new(:accepts, ~w(html))\n      end\n\n    {^status, _, body} =\n      Phoenix.ConnTest.assert_error_sent(status, fn ->\n        try do\n          func.()\n        catch\n          kind, reason ->\n            stack = __STACKTRACE__\n            Phoenix.Endpoint.RenderErrors.__catch__(conn, kind, reason, stack, opts)\n        else\n          _ -> flunk(\"function should have failed\")\n        end\n      end)\n\n    body\n  end\n\n  test \"exception page for throws\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"Got 500 from throw with GET\"\n  end\n\n  test \"exception page for errors\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [], fn ->\n        :erlang.error(:badarg)\n      end)\n\n    assert body == \"Got 500 from error with GET\"\n  end\n\n  test \"exception page for exceptions\" do\n    body =\n      assert_render(415, conn(:get, \"/\"), [], fn ->\n        raise Plug.Parsers.UnsupportedMediaTypeError, media_type: \"foo/bar\"\n      end)\n\n    assert body == \"Got 415 from error with GET\"\n  end\n\n  test \"exception page for exits\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [], fn ->\n        exit({:timedout, {GenServer, :call, [:foo, :bar]}})\n      end)\n\n    assert body == \"Got 500 from exit with GET\"\n  end\n\n  test \"exception page ignores params _format\" do\n    conn = conn(:get, \"/\", _format: \"text\")\n\n    body =\n      assert_render(500, conn, [accepts: [\"html\", \"text\"]], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"500 in TEXT\"\n  end\n\n  test \"exception page uses stored _format\" do\n    conn = conn(:get, \"/\") |> put_private(:phoenix_format, \"text\")\n    body = assert_render(500, conn, [accepts: [\"html\", \"text\"]], fn -> throw(:hello) end)\n    assert body == \"500 in TEXT\"\n  end\n\n  test \"exception page with custom format\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [accepts: ~w(text)], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"500 in TEXT\"\n  end\n\n  test \"exception page with layout\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [layout: {__MODULE__, :app}], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"Layout: Got 500 from throw with GET\"\n  end\n\n  test \"exception page with root layout\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [root_layout: {__MODULE__, :app}], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"Layout: Got 500 from throw with GET\"\n  end\n\n  test \"exception page with formats\" do\n    body =\n      assert_render(500, conn(:get, \"/\"), [formats: [text: __MODULE__]], fn ->\n        throw(:hello)\n      end)\n\n    assert body == \"500 in TEXT\"\n  end\n\n  test \"exception page is shown even with invalid format\" do\n    conn = conn(:get, \"/\") |> put_req_header(\"accept\", \"unknown/unknown\")\n    body = assert_render(500, conn, [], fn -> throw(:hello) end)\n    assert body == \"Got 500 from throw with GET\"\n  end\n\n  test \"exception page is shown even with invalid query parameters\" do\n    body = assert_render(500, conn(:get, \"/?q=%{\"), [], fn -> throw(:hello) end)\n\n    assert body == \"Got 500 from throw with GET\"\n  end\n\n  test \"captures warning when format is not supported\" do\n    Logger.delete_process_level(self())\n\n    assert capture_log(fn ->\n             conn = conn(:get, \"/\") |> put_req_header(\"accept\", \"unknown/unknown\")\n             assert_render(500, conn, [], fn -> throw(:hello) end)\n           end) =~ \"Could not render errors due to no supported media type in accept header\"\n  end\n\n  test \"captures warning when format does not match error view\" do\n    Logger.delete_process_level(self())\n\n    assert capture_log(fn ->\n             conn = conn(:get, \"/?_format=unknown\")\n             assert_render(500, conn, [], fn -> throw(:hello) end)\n           end) =~ \"Could not render errors due to unknown format \\\"unknown\\\"\"\n  end\n\n  test \"exception page for NoRouteError with plug_status 404 renders and does not reraise\" do\n    conn = call(Router, :get, \"/unknown\")\n    assert Phoenix.ConnTest.response(conn, 404) =~ \"Got 404 from error with GET\"\n  end\nend\n"
  },
  {
    "path": "test/phoenix/endpoint/supervisor_test.exs",
    "content": "defmodule Phoenix.Endpoint.SupervisorTest do\n  use ExUnit.Case, async: false\n  alias Phoenix.Endpoint.Supervisor\n\n  defmodule HTTPSEndpoint do\n    def config(:otp_app), do: :phoenix\n    def config(:https), do: [port: 443]\n    def config(:http), do: false\n    def config(:url), do: [host: \"example.com\"]\n    def config(_), do: nil\n  end\n\n  defmodule HTTPEndpoint do\n    def config(:otp_app), do: :phoenix\n    def config(:https), do: false\n    def config(:http), do: [port: 80]\n    def config(:url), do: [host: \"example.com\"]\n    def config(_), do: nil\n  end\n\n  defmodule HTTPEnvVarEndpoint do\n    def config(:otp_app), do: :phoenix\n    def config(:https), do: false\n    def config(:http), do: [port: {:system, \"PHOENIX_PORT\"}]\n    def config(:url), do: [host: {:system, \"PHOENIX_HOST\"}]\n    def config(_), do: nil\n  end\n\n  defmodule URLEndpoint do\n    def config(:https), do: false\n    def config(:http), do: false\n    def config(:url), do: [host: \"example.com\", port: 678, scheme: \"random\"]\n    def config(_), do: nil\n  end\n\n  defmodule StaticURLEndpoint do\n    def config(:https), do: false\n    def config(:http), do: []\n    def config(:url), do: []\n    def config(:static_url), do: [host: \"static.example.com\"]\n    def config(_), do: nil\n  end\n\n  defmodule ServerEndpoint do\n    def __sockets__(), do: []\n  end\n\n  setup_all do\n    Application.put_env(:phoenix, SupervisorApp.Endpoint, custom: true)\n    System.put_env(\"PHOENIX_PORT\", \"8080\")\n    System.put_env(\"PHOENIX_HOST\", \"example.org\")\n\n    [HTTPSEndpoint, HTTPEndpoint, HTTPEnvVarEndpoint, URLEndpoint, StaticURLEndpoint]\n    |> Enum.each(&Supervisor.warmup/1)\n\n    :ok\n  end\n\n  defp persistent!(endpoint), do: :persistent_term.get({Phoenix.Endpoint, endpoint})\n\n  test \"generates the static url based on the static host configuration\" do\n    assert persistent!(StaticURLEndpoint).static_url == \"http://static.example.com\"\n  end\n\n  test \"static url fallbacks to url when there is no configuration for static_url\" do\n    assert persistent!(URLEndpoint).static_url == \"random://example.com:678\"\n  end\n\n  test \"generates url\" do\n    assert persistent!(URLEndpoint).url == \"random://example.com:678\"\n    assert persistent!(HTTPEndpoint).url == \"http://example.com\"\n    assert persistent!(HTTPSEndpoint).url == \"https://example.com\"\n    assert persistent!(HTTPEnvVarEndpoint).url == \"http://example.org:8080\"\n  end\n\n  test \"static_path/2 returns file's path with lookup cache\" do\n    assert {:nocache, {\"/phoenix.png\", nil}} =\n             Supervisor.static_lookup(HTTPEndpoint, \"/phoenix.png\")\n\n    assert {:nocache, {\"/images/unknown.png\", nil}} =\n             Supervisor.static_lookup(HTTPEndpoint, \"/images/unknown.png\")\n  end\n\n  import ExUnit.CaptureLog\n\n  test \"logs info if :http or :https configuration is set but not :server when running in release\" do\n    # simulate running inside release\n    System.put_env(\"RELEASE_NAME\", \"phoenix-test\")\n    Application.put_env(:phoenix, ServerEndpoint, server: false, http: [], https: [])\n\n    assert capture_log(fn ->\n             {:ok, {_, _children}} = Supervisor.init({:phoenix, ServerEndpoint, []})\n           end) =~ \"Configuration :server\"\n\n    Application.put_env(:phoenix, ServerEndpoint, server: false, http: [])\n\n    assert capture_log(fn ->\n             {:ok, {_, _children}} = Supervisor.init({:phoenix, ServerEndpoint, []})\n           end) =~ \"Configuration :server\"\n\n    Application.put_env(:phoenix, ServerEndpoint, server: false, https: [])\n\n    assert capture_log(fn ->\n             {:ok, {_, _children}} = Supervisor.init({:phoenix, ServerEndpoint, []})\n           end) =~ \"Configuration :server\"\n\n    Application.put_env(:phoenix, ServerEndpoint, server: false)\n\n    refute capture_log(fn ->\n             {:ok, {_, _children}} = Supervisor.init({:phoenix, ServerEndpoint, []})\n           end) =~ \"Configuration :server\"\n\n    Application.put_env(:phoenix, ServerEndpoint, server: true)\n\n    refute capture_log(fn ->\n             {:ok, {_, _children}} = Supervisor.init({:phoenix, ServerEndpoint, []})\n           end) =~ \"Configuration :server\"\n\n    Application.delete_env(:phoenix, ServerEndpoint)\n  end\n\n  describe \"watchers\" do\n    defmodule WatchersEndpoint do\n      def __sockets__(), do: []\n    end\n\n    @watchers [esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]}]\n\n    test \"init/1 starts watcher children when `:server` config is true\" do\n      Application.put_env(:phoenix, WatchersEndpoint, server: true, watchers: @watchers)\n      {:ok, {_, children}} = Supervisor.init({:phoenix, WatchersEndpoint, []})\n\n      assert Enum.any?(children, fn\n               %{start: {Phoenix.Endpoint.Watcher, :start_link, _config}} -> true\n               _ -> false\n             end)\n    end\n\n    test \"init/1 doesn't start watchers when `:server` config is true and `:watchers` is false\" do\n      Application.put_env(:phoenix, WatchersEndpoint, server: true, watchers: false)\n      {:ok, {_, children}} = Supervisor.init({:phoenix, WatchersEndpoint, []})\n\n      refute Enum.any?(children, fn\n               %{start: {Phoenix.Endpoint.Watcher, :start_link, _config}} -> true\n               _ -> false\n             end)\n    end\n\n    test \"init/1 doesn't start watchers when `:server` config is false\" do\n      Application.put_env(:phoenix, WatchersEndpoint, server: false, watchers: @watchers)\n      {:ok, {_, children}} = Supervisor.init({:phoenix, WatchersEndpoint, []})\n\n      refute Enum.any?(children, fn\n               %{start: {Phoenix.Endpoint.Watcher, :start_link, _config}} -> true\n               _ -> false\n             end)\n    end\n\n    test \"init/1 starts watcher children when `:server` config is false and `:force_watchers` is true\" do\n      Application.put_env(:phoenix, WatchersEndpoint,\n        server: false,\n        force_watchers: true,\n        watchers: @watchers\n      )\n\n      {:ok, {_, children}} = Supervisor.init({:phoenix, WatchersEndpoint, []})\n\n      assert Enum.any?(children, fn\n               %{start: {Phoenix.Endpoint.Watcher, :start_link, _config}} -> true\n               _ -> false\n             end)\n    end\n  end\n\n  describe \"origin & CSRF checks config\" do\n    defmodule TestSocket do\n      @behaviour Phoenix.Socket.Transport\n      def child_spec(_), do: :ignore\n      def connect(_), do: {:ok, []}\n      def init(state), do: {:ok, state}\n      def handle_in(_, state), do: {:ok, state}\n      def handle_info(_, state), do: {:ok, state}\n      def terminate(_, _), do: :ok\n    end\n\n    defmodule SocketEndpoint do\n      use Phoenix.Endpoint, otp_app: :phoenix\n\n      socket \"/ws\", TestSocket, websocket: [check_csrf: false, check_origin: false]\n    end\n\n    Application.put_env(:phoenix, SocketEndpoint, [])\n\n    test \"fails when CSRF and origin checks both disabled in transport\" do\n      assert_raise ArgumentError, ~r/one of :check_origin and :check_csrf must be set/, fn ->\n        Supervisor.init({:phoenix, SocketEndpoint, []})\n      end\n    end\n\n    defmodule SocketEndpointOriginCheckDisabled do\n      use Phoenix.Endpoint, otp_app: :phoenix\n\n      socket \"/ws\", TestSocket, websocket: [check_csrf: false]\n    end\n\n    Application.put_env(:phoenix, SocketEndpointOriginCheckDisabled, check_origin: false)\n\n    test \"fails when origin is disabled in endpoint config and CSRF disabled in transport\" do\n      assert_raise ArgumentError, ~r/one of :check_origin and :check_csrf must be set/, fn ->\n        Supervisor.init({:phoenix, SocketEndpointOriginCheckDisabled, []})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/endpoint/watcher_test.exs",
    "content": "defmodule Phoenix.Endpoint.WatcherTest do\n  use ExUnit.Case, async: true\n\n  alias Phoenix.Endpoint.Watcher\n  import ExUnit.CaptureIO\n\n  test \"starts watching and writes to stdio with args\" do\n    assert capture_io(fn ->\n      {:ok, pid} = Watcher.start_link({\"echo\", [\"hello\", cd: File.cwd!()]})\n      ref = Process.monitor(pid)\n      assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000\n    end) == \"hello\\n\"\n  end\n\n  test \"starts watching and writes to stdio with fun\" do\n    assert capture_io(fn ->\n      {:ok, pid} = Watcher.start_link({\"echo\", {IO, :puts, [\"hello\"]}})\n      ref = Process.monitor(pid)\n      assert_receive {:DOWN, ^ref, :process, ^pid, :normal}, 1000\n    end) == \"hello\\n\"\n  end\nend\n"
  },
  {
    "path": "test/phoenix/integration/endpoint_test.exs",
    "content": "Code.require_file \"../../support/http_client.exs\", __DIR__\nCode.require_file \"../../support/endpoint_helper.exs\", __DIR__\n\ndefmodule Phoenix.Integration.EndpointTest do\n  use ExUnit.Case, async: false\n  import ExUnit.CaptureLog\n\n  import Phoenix.Integration.EndpointHelper\n\n  alias Phoenix.Integration.AdapterTest.ProdEndpoint\n  alias Phoenix.Integration.AdapterTest.DevEndpoint\n  alias Phoenix.Integration.AdapterTest.ProdInet6Endpoint\n\n  # Find available ports to use for this test\n  [dev, prod, prod_inet6] = get_unused_port_numbers(3)\n  @dev dev\n  @prod prod\n  @prod_inet6 prod_inet6\n\n  Application.put_env(:endpoint_int, ProdEndpoint,\n    http: [port: @prod], url: [host: \"example.com\"], server: true, drainer: false,\n    render_errors: [accepts: ~w(html json)])\n  Application.put_env(:endpoint_int, DevEndpoint,\n    http: [port: @dev], debug_errors: true, drainer: false)\n\n  Application.put_env(:endpoint_int, ProdInet6Endpoint,\n    http: [port: @prod_inet6, transport_options: [socket_opts: [:inet6]]],\n    url: [host: \"example.com\"],\n    server: true)\n\n  defmodule Router do\n    @moduledoc \"\"\"\n    Let's use a plug router to test this endpoint.\n    \"\"\"\n    use Plug.Router\n\n    plug :match\n    plug :dispatch\n\n    get \"/\" do\n      send_resp conn, 200, \"ok\"\n    end\n\n    get \"/router/oops\" do\n      _ = conn\n      raise \"oops\"\n    end\n\n    match _ do\n      raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__\n    end\n\n    def __routes__ do\n      []\n    end\n  end\n\n  defmodule Wrapper do\n    @moduledoc \"\"\"\n    A wrapper around the endpoint call to extract information.\n\n    This exists so we can verify that the exception handling\n    in the Phoenix endpoint is working as expected. In order\n    to do that, we need to wrap the endpoint.call/2 in a\n    before compile callback so it wraps the whole stack,\n    including render errors and debug errors functionality.\n    \"\"\"\n\n    defmacro __before_compile__(_) do\n      quote do\n        defoverridable [call: 2]\n\n        def call(conn, opts) do\n          # Assert we never have a lingering sent message in the inbox\n          refute_received {:plug_conn, :sent}\n\n          try do\n            super(conn, opts)\n          after\n            # When we pipe downstream, downstream will always render,\n            # either because the router is responding or because the\n            # endpoint error layer is kicking in.\n            assert_received {:plug_conn, :sent}\n            send self(), {:plug_conn, :sent}\n          end\n        end\n      end\n    end\n  end\n\n  for mod <- [ProdEndpoint, DevEndpoint, ProdInet6Endpoint] do\n    defmodule mod do\n      use Phoenix.Endpoint, otp_app: :endpoint_int\n      @before_compile Wrapper\n\n      plug :oops\n      plug Router\n\n      @doc \"\"\"\n      Verify errors from the plug stack too (before the router).\n      \"\"\"\n      def oops(conn, _opts) do\n        if conn.path_info == ~w(oops) do\n          raise \"oops\"\n        else\n          conn\n        end\n      end\n    end\n  end\n\n  alias Phoenix.Integration.HTTPClient\n\n  test \"starts drainer in supervision tree if configured\" do\n    capture_log fn ->\n      {:ok, _} = ProdInet6Endpoint.start_link()\n      assert List.keyfind(Supervisor.which_children(ProdInet6Endpoint), Plug.Cowboy.Drainer, 0)\n      Supervisor.stop(ProdInet6Endpoint)\n\n      {:ok, _} = ProdEndpoint.start_link()\n      refute List.keyfind(Supervisor.which_children(ProdEndpoint), Plug.Cowboy.Drainer, 0)\n      Supervisor.stop(ProdEndpoint)\n    end\n  end\n\n  test \"adapters starts on configured port and serves requests and stops for prod\" do\n    capture_log fn ->\n      # Has server: true\n      {:ok, _} = ProdEndpoint.start_link()\n\n      # Requests\n      {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}\", %{})\n      assert resp.status == 200\n      assert resp.body == \"ok\"\n\n      {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}/unknown\", %{})\n      assert resp.status == 404\n      assert resp.body == \"404.html from Phoenix.ErrorView\"\n\n      {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}/unknown?_format=json\", %{})\n      assert resp.status == 404\n      assert resp.body |> Phoenix.json_library().decode!() == %{\"error\" => \"Got 404 from error with GET\"}\n\n      capture_log(fn ->\n        {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}/oops\", %{})\n        assert resp.status == 500\n        assert resp.body == \"500.html from Phoenix.ErrorView\"\n\n        {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}/router/oops\", %{})\n        assert resp.status == 500\n        assert resp.body == \"500.html from Phoenix.ErrorView\"\n\n        Supervisor.stop(ProdEndpoint)\n      end)\n\n      {:error, _reason} = HTTPClient.request(:get, \"http://127.0.0.1:#{@prod}\", %{})\n    end\n  end\n\n  test \"adapters starts on configured port and serves requests and stops for dev\" do\n    # Toggle globally\n    serve_endpoints(true)\n    on_exit(fn -> serve_endpoints(false) end)\n\n    capture_log fn ->\n      # Has server: false\n      {:ok, _} = DevEndpoint.start_link()\n\n      # Requests\n      {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@dev}\", %{})\n      assert resp.status == 200\n      assert resp.body == \"ok\"\n\n      {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@dev}/unknown\", %{})\n      assert resp.status == 404\n      assert resp.body =~ \"NoRouteError at GET /unknown\"\n\n      capture_log(fn ->\n        {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@dev}/oops\", %{})\n        assert resp.status == 500\n        assert resp.body =~ \"RuntimeError at GET /oops\"\n\n        {:ok, resp} = HTTPClient.request(:get, \"http://127.0.0.1:#{@dev}/router/oops\", %{})\n        assert resp.status == 500\n        assert resp.body =~ \"RuntimeError at GET /router/oops\"\n\n        Supervisor.stop(DevEndpoint)\n      end)\n\n      {:error, _reason} = HTTPClient.request(:get, \"http://127.0.0.1:#{@dev}\", %{})\n    end\n  end\n\n  test \"adapters starts on configured port and inet6 for prod\" do\n    capture_log fn ->\n      # Has server: true\n      {:ok, _} = ProdInet6Endpoint.start_link()\n\n      Supervisor.stop(ProdInet6Endpoint)\n    end\n  end\n\n  defp serve_endpoints(bool) do\n    Application.put_env(:phoenix, :serve_endpoints, bool)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/integration/long_poll_channels_test.exs",
    "content": "Code.require_file(\"../../support/http_client.exs\", __DIR__)\n\ndefmodule Phoenix.Integration.LongPollChannelsTest do\n  # TODO: use parameterized tests once we require Elixir 1.18\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Integration.HTTPClient\n  alias Phoenix.Socket.{Broadcast, Message, V1, V2}\n  alias __MODULE__.Endpoint\n\n  @moduletag :capture_log\n  @port 5808\n  @pool_size 1\n\n  Application.put_env(:phoenix, Endpoint,\n    https: false,\n    http: [port: @port],\n    secret_key_base: String.duplicate(\"abcdefgh\", 8),\n    server: true,\n    drainer: false,\n    pubsub_server: __MODULE__\n  )\n\n  defmodule RoomChannel do\n    use Phoenix.Channel, log_join: :info, log_handle_in: :info\n\n    intercept [\"new_msg\", \"bin_ack\"]\n\n    def join(topic, message, socket) do\n      Process.register(self(), String.to_atom(topic))\n      send(self(), {:after_join, message})\n      {:ok, socket}\n    end\n\n    def handle_info({:after_join, message}, socket) do\n      broadcast(socket, \"user_entered\", %{user: message[\"user\"]})\n      push(socket, \"joined\", Map.merge(%{status: \"connected\"}, socket.assigns))\n      {:noreply, socket}\n    end\n\n    def handle_in(\"bin\", {:binary, _bin}, socket) do\n      broadcast!(socket, \"bin_ack\", %{})\n      {:noreply, socket}\n    end\n\n    def handle_in(\"new_msg\", message, socket) do\n      broadcast!(socket, \"new_msg\", message)\n      {:noreply, socket}\n    end\n\n    def handle_in(\"boom\", _message, _socket) do\n      raise \"boom\"\n    end\n\n    def handle_out(event, payload, socket) do\n      push(socket, event, Map.put(payload, \"transport\", inspect(socket.transport)))\n      {:noreply, socket}\n    end\n  end\n\n  defmodule UserSocketConnectInfo do\n    use Phoenix.Socket\n\n    channel \"room:*\", RoomChannel\n\n    def connect(params, socket, connect_info) do\n      unless params[\"logging\"] == \"enabled\", do: Logger.put_process_level(self(), :none)\n      address = Tuple.to_list(connect_info.peer_data.address) |> Enum.join(\".\")\n      trace_context_headers = Enum.into(connect_info.trace_context_headers, %{})\n      uri = Map.from_struct(connect_info.uri)\n      x_headers = Enum.into(connect_info.x_headers, %{})\n\n      connect_info =\n        connect_info\n        |> update_in([:peer_data], &Map.put(&1, :address, address))\n        |> Map.put(:trace_context_headers, trace_context_headers)\n        |> Map.put(:uri, uri)\n        |> Map.put(:x_headers, x_headers)\n\n      socket =\n        socket\n        |> assign(:user_id, params[\"user_id\"])\n        |> assign(:connect_info, connect_info)\n\n      {:ok, socket}\n    end\n\n    def id(socket) do\n      if id = socket.assigns.user_id, do: \"user_sockets:#{id}\"\n    end\n  end\n\n  defmodule UserSocket do\n    use Phoenix.Socket\n\n    channel \"room:*\", RoomChannel\n\n    def connect(%{\"reject\" => \"true\"}, _socket) do\n      :error\n    end\n\n    def connect(%{\"custom_error\" => \"true\"}, _socket) do\n      {:error, :custom}\n    end\n\n    def connect(params, socket) do\n      unless params[\"logging\"] == \"enabled\", do: Logger.put_process_level(self(), :none)\n      {:ok, assign(socket, :user_id, params[\"user_id\"])}\n    end\n\n    def id(socket) do\n      if id = socket.assigns.user_id, do: \"user_sockets:#{id}\"\n    end\n  end\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    socket \"/ws\", UserSocket,\n      longpoll: [\n        window_ms: 200,\n        pubsub_timeout_ms: 200,\n        check_origin: [\"//example.com\"]\n      ]\n\n    socket \"/ws/admin\", UserSocket,\n      longpoll: [\n        window_ms: 200,\n        pubsub_timeout_ms: 200,\n        check_origin: [\"//example.com\"]\n      ]\n\n    socket \"/ws/connect_info\", UserSocketConnectInfo,\n      longpoll: [\n        window_ms: 200,\n        pubsub_timeout_ms: 200,\n        check_origin: [\"//example.com\"],\n        connect_info: [:trace_context_headers, :x_headers, :peer_data, :uri]\n      ]\n  end\n\n  setup %{adapter: adapter} do\n    config = Application.get_env(:phoenix, Endpoint)\n    Application.put_env(:phoenix, Endpoint, Keyword.merge(config, adapter: adapter))\n    capture_log(fn -> start_supervised!(Endpoint) end)\n    start_supervised!({Phoenix.PubSub, name: __MODULE__, pool_size: @pool_size})\n    :ok\n  end\n\n  setup config do\n    for {_, pid, _, _} <- DynamicSupervisor.which_children(Phoenix.Transports.LongPoll.Supervisor) do\n      DynamicSupervisor.terminate_child(Phoenix.Transports.LongPoll.Supervisor, pid)\n    end\n\n    {:ok, topic: \"room:\" <> to_string(config.test)}\n  end\n\n  def assert_down(topic) do\n    ref = Process.monitor(Process.whereis(:\"#{topic}\"))\n    assert_receive {:DOWN, ^ref, :process, _pid, _}\n  end\n\n  @doc \"\"\"\n  Helper method to maintain token session state when making HTTP requests.\n\n  Returns a response with body decoded into JSON map.\n  \"\"\"\n  def poll(method, path, vsn, params, json \\\\ nil, headers \\\\ %{}) do\n    {serializer, json} = serializer(vsn, json)\n\n    headers =\n      if is_list(json) do\n        Map.merge(%{\"content-type\" => \"application/x-ndjson\"}, headers)\n      else\n        Map.merge(%{\"content-type\" => \"application/json\"}, headers)\n      end\n\n    body = encode(serializer, json)\n    query_string = params |> Map.put(\"vsn\", vsn) |> URI.encode_query()\n    url = \"http://127.0.0.1:#{@port}#{path}/longpoll?\" <> query_string\n    {:ok, resp} = HTTPClient.request(method, url, headers, body)\n    decode_body(serializer, resp)\n  end\n\n  defp serializer(\"2.\" <> _, json), do: {V2.JSONSerializer, json}\n  defp serializer(_, nil), do: {V1.JSONSerializer, nil}\n\n  defp serializer(_, batch) when is_list(batch) do\n    {V1.JSONSerializer, for(msg <- batch, do: Map.delete(msg, \"join_ref\"))}\n  end\n\n  defp serializer(_, %{} = json) do\n    {V1.JSONSerializer, json}\n  end\n\n  defp decode_body(serializer, %{} = resp) do\n    resp\n    |> update_in([:body], &Phoenix.json_library().decode!(&1))\n    |> update_in([:body, \"messages\"], fn messages ->\n      for msg <- messages || [] do\n        serializer.decode!(msg, opcode: :text)\n      end\n    end)\n  end\n\n  defp encode(_vsn, nil), do: \"\"\n\n  defp encode(V2.JSONSerializer = serializer, batch) when is_list(batch) do\n    batch\n    |> Enum.map(&encode(serializer, &1))\n    |> Enum.join(\"\\n\")\n  end\n\n  defp encode(V2.JSONSerializer, %{} = map) do\n    case map[\"payload\"] do\n      {:binary, data} ->\n        %{\"topic\" => topic, \"event\" => event, \"join_ref\" => join_ref, \"ref\" => ref} = map\n        ref = to_string(ref)\n        ref_size = byte_size(ref)\n        join_ref = to_string(join_ref)\n        join_ref_size = byte_size(join_ref)\n        topic_size = byte_size(topic)\n        event_size = byte_size(event)\n\n        Base.encode64(<<\n          0::size(8),\n          join_ref_size::size(8),\n          ref_size::size(8),\n          topic_size::size(8),\n          event_size::size(8),\n          join_ref::binary-size(join_ref_size),\n          ref::binary-size(ref_size),\n          topic::binary-size(topic_size),\n          event::binary-size(event_size),\n          data::binary\n        >>)\n\n      _ ->\n        Phoenix.json_library().encode!([\n          map[\"join_ref\"],\n          map[\"ref\"],\n          map[\"topic\"],\n          map[\"event\"],\n          map[\"payload\"]\n        ])\n    end\n  end\n\n  defp encode(V1.JSONSerializer, %{} = map), do: Phoenix.json_library().encode!(map)\n\n  @doc \"\"\"\n  Joins a long poll socket.\n\n  Returns the long polling session token.\n\n  If the mode is local, the session will point to a local\n  process. If the mode is pubsub, the session will use the\n  pubsub system.\n  \"\"\"\n  def join(\n        path,\n        topic,\n        vsn,\n        join_ref,\n        mode \\\\ :local,\n        payload \\\\ %{},\n        params \\\\ %{},\n        headers \\\\ %{}\n      )\n\n  def join(path, topic, vsn, join_ref, :local, payload, params, headers) do\n    resp = poll(:get, path, vsn, params, %{}, headers)\n    assert resp.body[\"token\"]\n    assert resp.body[\"status\"] == 410\n    assert resp.status == 200\n\n    session = resp.body |> Map.take([\"token\"]) |> Map.merge(params)\n\n    resp =\n      poll(\n        :post,\n        path,\n        vsn,\n        session,\n        %{\n          \"topic\" => topic,\n          \"event\" => \"phx_join\",\n          \"ref\" => \"1\",\n          \"join_ref\" => join_ref,\n          \"payload\" => payload\n        },\n        headers\n      )\n\n    assert resp.body[\"status\"] == 200\n    session\n  end\n\n  def join(path, topic, vsn, join_ref, :pubsub, payload, params, headers) do\n    session = join(path, topic, vsn, join_ref, :local, payload, params, headers)\n\n    {:ok, {:v1, _id, pid, topic}} =\n      Phoenix.Token.verify(Endpoint, Atom.to_string(__MODULE__), session[\"token\"])\n\n    %{\n      \"token\" =>\n        Phoenix.Token.sign(Endpoint, Atom.to_string(__MODULE__), {:v1, \"unknown\", pid, topic})\n    }\n  end\n\n  for %{adapter: adapter} <- [\n        %{adapter: Bandit.PhoenixAdapter},\n        %{adapter: Phoenix.Endpoint.Cowboy2Adapter}\n      ] do\n    describe \"adapter: #{inspect(adapter)}\" do\n      @describetag adapter: adapter\n\n      for mode <- [:local, :pubsub] do\n        @mode mode\n        @vsn \"1.0.0\"\n\n        test \"#{@mode}: joins and poll messages\" do\n          session = join(\"/ws\", \"room:lobby\", @vsn, \"1\", @mode)\n\n          # pull messages\n          resp = poll(:get, \"/ws\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          [phx_reply, user_entered, status_msg] = resp.body[\"messages\"]\n\n          assert phx_reply == %Message{\n                   event: \"phx_reply\",\n                   payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n                   ref: \"1\",\n                   topic: \"room:lobby\"\n                 }\n\n          assert %Message{\n                   event: \"joined\",\n                   payload: %{\"status\" => \"connected\", \"user_id\" => nil},\n                   ref: nil,\n                   join_ref: nil,\n                   topic: \"room:lobby\"\n                 } = status_msg\n\n          assert user_entered == %Message{\n                   event: \"user_entered\",\n                   payload: %{\"user\" => nil},\n                   ref: nil,\n                   join_ref: nil,\n                   topic: \"room:lobby\"\n                 }\n\n          # poll without messages sends 204 no_content\n          resp = poll(:get, \"/ws\", @vsn, session)\n          assert resp.body[\"status\"] == 204\n        end\n\n        test \"#{@mode}: transport x_headers are extracted to the socket connect_info\" do\n          session =\n            join(\"/ws/connect_info\", \"room:lobby\", @vsn, \"1\", @mode, %{}, %{}, %{\n              \"x-application\" => \"Phoenix\"\n            })\n\n          # pull messages\n          resp = poll(:get, \"/ws/connect_info\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          [_phx_reply, _user_entered, status_msg] = resp.body[\"messages\"]\n\n          assert %{\"connect_info\" => %{\"x_headers\" => %{\"x-application\" => \"Phoenix\"}}} =\n                   status_msg.payload\n        end\n\n        test \"#{@mode}: transport trace_context_headers are extracted to the socket connect_info\" do\n          ctx_headers = %{\n            \"traceparent\" => \"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\",\n            \"tracestate\" => \"congo=t61rcWkgMz\"\n          }\n\n          session =\n            join(\"/ws/connect_info\", \"room:lobby\", @vsn, \"1\", @mode, %{}, %{}, ctx_headers)\n\n          # pull messages\n          resp = poll(:get, \"/ws/connect_info\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          [_phx_reply, _user_entered, status_msg] = resp.body[\"messages\"]\n\n          assert %{\"connect_info\" => %{\"trace_context_headers\" => ^ctx_headers}} =\n                   status_msg.payload\n        end\n\n        test \"#{@mode}: transport peer_data is extracted to the socket connect_info\" do\n          session =\n            join(\"/ws/connect_info\", \"room:lobby\", @vsn, \"1\", @mode, %{}, %{}, %{\n              \"x-application\" => \"Phoenix\"\n            })\n\n          # pull messages\n          resp = poll(:get, \"/ws/connect_info\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          [_phx_reply, _user_entered, status_msg] = resp.body[\"messages\"]\n\n          assert %{\"connect_info\" => %{\"peer_data\" => %{\"address\" => \"127.0.0.1\"}}} =\n                   status_msg.payload\n        end\n\n        test \"#{@mode}: transport uri is extracted to the socket connect_info\" do\n          session =\n            join(\"/ws/connect_info\", \"room:lobby\", @vsn, \"1\", @mode, %{}, %{}, %{\n              \"x-application\" => \"Phoenix\"\n            })\n\n          # pull messages\n          resp = poll(:get, \"/ws/connect_info\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          [_phx_reply, _user_entered, status_msg] = resp.body[\"messages\"]\n          query = \"vsn=#{@vsn}\"\n\n          assert %{\n                   \"connect_info\" => %{\n                     \"uri\" => %{\n                       \"host\" => \"127.0.0.1\",\n                       \"path\" => \"/ws/connect_info/longpoll\",\n                       \"query\" => ^query,\n                       \"scheme\" => \"http\"\n                     }\n                   }\n                 } = status_msg.payload\n        end\n\n        test \"#{@mode}: publishing events\" do\n          Phoenix.PubSub.subscribe(__MODULE__, \"room:lobby\")\n          join_ref = \"1\"\n          session = join(\"/ws\", \"room:lobby\", @vsn, join_ref, @mode)\n\n          # Publish successfully\n          resp =\n            poll(:post, \"/ws\", @vsn, session, %{\n              \"topic\" => \"room:lobby\",\n              \"event\" => \"new_msg\",\n              \"ref\" => \"1\",\n              \"join_ref\" => join_ref,\n              \"payload\" => %{\"body\" => \"hi!\"}\n            })\n\n          assert resp.body[\"status\"] == 200\n          assert_receive %Broadcast{event: \"new_msg\", payload: %{\"body\" => \"hi!\"}}\n\n          # Get published message\n          resp = poll(:get, \"/ws\", @vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          assert List.last(resp.body[\"messages\"]) == %Message{\n                   event: \"new_msg\",\n                   payload: %{\"transport\" => \":longpoll\", \"body\" => \"hi!\"},\n                   ref: nil,\n                   join_ref: nil,\n                   topic: \"room:lobby\"\n                 }\n\n          # Publish event to an unjoined room\n          capture_log(fn ->\n            Phoenix.PubSub.subscribe(__MODULE__, \"room:private-room\")\n\n            resp =\n              poll(:post, \"/ws\", @vsn, session, %{\n                \"topic\" => \"room:private-room\",\n                \"event\" => \"new_msg\",\n                \"ref\" => \"12300\",\n                \"payload\" => %{\"body\" => \"this method shouldn't send!'\"}\n              })\n\n            assert resp.body[\"status\"] == 200\n            refute_receive %Broadcast{event: \"new_msg\"}\n\n            # Get join error\n            resp = poll(:get, \"/ws\", @vsn, session)\n            assert resp.body[\"status\"] == 200\n\n            assert List.last(resp.body[\"messages\"]) == %Message{\n                     join_ref: nil,\n                     event: \"phx_reply\",\n                     payload: %{\n                       \"response\" => %{\"reason\" => \"unmatched topic\"},\n                       \"status\" => \"error\"\n                     },\n                     ref: \"12300\",\n                     topic: \"room:private-room\"\n                   }\n          end)\n        end\n\n        test \"#{@mode}: lonpoll publishing batch events on v2 protocol\" do\n          vsn = \"2.0.0\"\n          Phoenix.PubSub.subscribe(__MODULE__, \"room:lobby\")\n          session = join(\"/ws\", \"room:lobby\", vsn, \"1\", @mode)\n          # Publish successfully\n          resp =\n            poll(:post, \"/ws\", vsn, session, [\n              %{\n                \"topic\" => \"room:lobby\",\n                \"event\" => \"new_msg\",\n                \"ref\" => \"2\",\n                \"join_ref\" => \"1\",\n                \"payload\" => %{\"body\" => \"hi1\"}\n              },\n              %{\n                \"topic\" => \"room:lobby\",\n                \"event\" => \"new_msg\",\n                \"ref\" => \"3\",\n                \"join_ref\" => \"1\",\n                \"payload\" => %{\"body\" => \"hi2\"}\n              }\n            ])\n\n          assert resp.body[\"status\"] == 200\n          assert_receive %Broadcast{event: \"new_msg\", payload: %{\"body\" => \"hi1\"}}\n          assert_receive %Broadcast{event: \"new_msg\", payload: %{\"body\" => \"hi2\"}}\n\n          # Publish base64 binary successfully\n          resp =\n            poll(:post, \"/ws\", vsn, session, [\n              %{\n                \"topic\" => \"room:lobby\",\n                \"event\" => \"bin\",\n                \"join_ref\" => \"1\",\n                \"ref\" => \"4\",\n                \"payload\" => {:binary, <<1, 2, 3>>}\n              },\n              %{\n                \"topic\" => \"room:lobby\",\n                \"event\" => \"new_msg\",\n                \"ref\" => \"5\",\n                \"join_ref\" => \"1\",\n                \"payload\" => %{\"body\" => \"hi3\"}\n              }\n            ])\n\n          assert resp.body[\"status\"] == 200\n          assert_receive %Broadcast{event: \"bin_ack\", payload: %{}}\n          assert_receive %Broadcast{event: \"new_msg\", payload: %{\"body\" => \"hi3\"}}\n\n          # Get published message\n          resp = poll(:get, \"/ws\", vsn, session)\n          assert resp.body[\"status\"] == 200\n\n          assert [\n                   _phx_reply,\n                   _user_entered,\n                   _joined,\n                   %Message{\n                     topic: \"room:lobby\",\n                     event: \"new_msg\",\n                     payload: %{\"body\" => \"hi1\", \"transport\" => \":longpoll\"},\n                     ref: nil,\n                     join_ref: \"1\"\n                   },\n                   %Message{\n                     topic: \"room:lobby\",\n                     event: \"new_msg\",\n                     payload: %{\"body\" => \"hi2\", \"transport\" => \":longpoll\"},\n                     ref: nil,\n                     join_ref: \"1\"\n                   },\n                   %Message{\n                     topic: \"room:lobby\",\n                     event: \"bin_ack\",\n                     payload: %{\"transport\" => \":longpoll\"},\n                     ref: nil,\n                     join_ref: \"1\"\n                   },\n                   %Message{\n                     topic: \"room:lobby\",\n                     event: \"new_msg\",\n                     payload: %{\"body\" => \"hi3\", \"transport\" => \":longpoll\"},\n                     ref: nil,\n                     join_ref: \"1\"\n                   }\n                 ] = resp.body[\"messages\"]\n        end\n\n        test \"#{@mode}: shuts down after timeout\" do\n          session = join(\"/ws\", \"room:lobby\", @vsn, \"1\", @mode)\n\n          channel = Process.whereis(:\"room:lobby\")\n          assert channel\n          Process.monitor(channel)\n\n          assert_receive({:DOWN, _, :process, ^channel, {:shutdown, :inactive}}, 5000)\n          resp = poll(:post, \"/ws\", @vsn, session)\n          assert resp.body[\"status\"] == 410\n        end\n      end\n    end\n\n    for {serializer, vsn, join_ref} <- [\n          {V1.JSONSerializer, \"1.0.0\", nil},\n          {V2.JSONSerializer, \"2.0.0\", \"1\"}\n        ] do\n      @vsn vsn\n      @join_ref join_ref\n\n      describe \"adapter: #{inspect(adapter)} - with #{vsn} serializer #{inspect(serializer)}\" do\n        @describetag adapter: adapter\n\n        test \"refuses connects that error with 403 response\" do\n          resp = poll(:get, \"/ws\", @vsn, %{\"reject\" => \"true\"}, %{})\n          assert resp.body[\"status\"] == 403\n\n          resp = poll(:get, \"/ws\", @vsn, %{\"custom_error\" => \"true\"}, %{})\n          assert resp.body[\"status\"] == 403\n        end\n\n        test \"refuses unallowed origins\" do\n          capture_log(fn ->\n            resp = poll(:get, \"/ws\", @vsn, %{}, nil, %{\"origin\" => \"https://example.com\"})\n            assert resp.body[\"status\"] == 410\n\n            resp = poll(:get, \"/ws\", @vsn, %{}, nil, %{\"origin\" => \"http://notallowed.com\"})\n            assert resp.body[\"status\"] == 403\n          end)\n        end\n\n        test \"filter params on join\" do\n          log =\n            capture_log(fn ->\n              join(\n                \"/ws\",\n                \"room:lobby\",\n                @vsn,\n                @join_ref,\n                :local,\n                %{\"foo\" => \"bar\", \"password\" => \"shouldnotshow\"},\n                %{\"logging\" => \"enabled\"}\n              )\n            end)\n\n          assert log =~ \"Parameters: %{\\\"foo\\\" => \\\"bar\\\", \\\"password\\\" => \\\"[FILTERED]\\\"}\"\n        end\n\n        test \"sends phx_error if a channel server abnormally exits\", %{topic: topic} do\n          session = join(\"/ws\", topic, @vsn, @join_ref)\n\n          capture_log(fn ->\n            resp =\n              poll(:post, \"/ws\", @vsn, session, %{\n                \"topic\" => topic,\n                \"event\" => \"boom\",\n                \"ref\" => @join_ref,\n                \"join_ref\" => @join_ref,\n                \"payload\" => %{}\n              })\n\n            assert resp.body[\"status\"] == 200\n            assert resp.status == 200\n          end)\n\n          assert_down(topic)\n\n          resp = poll(:get, \"/ws\", @vsn, session)\n          [_phx_reply, _user_entered, _joined, chan_error] = resp.body[\"messages\"]\n\n          assert chan_error == %Message{\n                   event: \"phx_error\",\n                   payload: %{},\n                   topic: topic,\n                   ref: @join_ref,\n                   join_ref: @join_ref\n                 }\n        end\n\n        test \"sends phx_close if a channel server normally exits\" do\n          session = join(\"/ws\", \"room:lobby\", @vsn, @join_ref)\n\n          resp =\n            poll(:post, \"/ws\", @vsn, session, %{\n              \"topic\" => \"room:lobby\",\n              \"event\" => \"phx_leave\",\n              \"join_ref\" => @join_ref,\n              \"ref\" => \"2\",\n              \"payload\" => %{}\n            })\n\n          assert resp.body[\"status\"] == 200\n          assert resp.status == 200\n\n          resp = poll(:get, \"/ws\", @vsn, session)\n          [_phx_reply, _joined, _user_entered, _leave_reply, phx_close] = resp.body[\"messages\"]\n\n          assert phx_close == %Message{\n                   event: \"phx_close\",\n                   payload: %{},\n                   ref: @join_ref,\n                   join_ref: @join_ref,\n                   topic: \"room:lobby\"\n                 }\n        end\n\n        test \"shuts down when receiving disconnect broadcasts on socket's id\" do\n          resp = poll(:get, \"/ws\", @vsn, %{\"user_id\" => \"456\"}, %{})\n          session = Map.take(resp.body, [\"token\"])\n\n          for topic <- [\"room:lpdisconnect1\", \"room:lpdisconnect2\"] do\n            poll(:post, \"/ws\", @vsn, session, %{\n              \"topic\" => topic,\n              \"event\" => \"phx_join\",\n              \"ref\" => \"1\",\n              \"payload\" => %{}\n            })\n          end\n\n          chan1 = Process.whereis(:\"room:lpdisconnect1\")\n          assert chan1\n          chan2 = Process.whereis(:\"room:lpdisconnect2\")\n          assert chan2\n          Process.monitor(chan1)\n          Process.monitor(chan2)\n\n          Endpoint.broadcast(\"user_sockets:456\", \"disconnect\", %{})\n\n          assert_receive {:DOWN, _, :process, ^chan1, {:shutdown, :disconnected}}\n          assert_receive {:DOWN, _, :process, ^chan2, {:shutdown, :disconnected}}\n\n          poll(:get, \"/ws\", @vsn, session)\n          assert resp.body[\"status\"] == 410\n        end\n\n        test \"refuses non-matching versions\" do\n          log =\n            capture_log(fn ->\n              resp =\n                poll(:get, \"/ws\", \"123.1.1\", %{}, nil, %{\"origin\" => \"https://example.com\"})\n\n              assert resp.body[\"status\"] == 403\n            end)\n\n          assert log =~\n                   \"The client's requested transport version \\\"123.1.1\\\" does not match server's version\"\n        end\n\n        test \"forces application/json content-type\" do\n          session = join(\"/ws\", \"room:lobby\", @vsn, @join_ref)\n\n          resp =\n            poll(\n              :post,\n              \"/ws\",\n              @vsn,\n              session,\n              %{\n                \"topic\" => \"room:lobby\",\n                \"event\" => \"phx_leave\",\n                \"ref\" => \"2\",\n                \"join_ref\" => @join_ref,\n                \"payload\" => %{}\n              },\n              %{\"content-type\" => \"\"}\n            )\n\n          assert resp.body[\"status\"] == 200\n          assert resp.status == 200\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/integration/long_poll_socket_test.exs",
    "content": "Code.require_file(\"../../support/http_client.exs\", __DIR__)\n\ndefmodule Phoenix.Integration.LongPollSocketTest do\n  # TODO: use parameterized tests once we require Elixir 1.18\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Integration.HTTPClient\n  alias __MODULE__.Endpoint\n\n  @moduletag :capture_log\n  @port 5908\n  @pool_size 1\n\n  Application.put_env(\n    :phoenix,\n    Endpoint,\n    https: false,\n    http: [port: @port],\n    debug_errors: false,\n    secret_key_base: String.duplicate(\"abcdefgh\", 8),\n    server: true,\n    drainer: false,\n    pubsub_server: __MODULE__\n  )\n\n  defmodule UserSocket do\n    @behaviour Phoenix.Socket.Transport\n\n    def child_spec(opts) do\n      :value = Keyword.fetch!(opts, :custom)\n      :ignore\n    end\n\n    def connect(map) do\n      %{endpoint: Endpoint, params: params, transport: :longpoll} = map\n      {:ok, {:params, params}}\n    end\n\n    def init({:params, _} = state) do\n      {:ok, state}\n    end\n\n    def handle_in({\"params\", opts}, {:params, params} = state) do\n      :text = Keyword.fetch!(opts, :opcode)\n      {:reply, :ok, {:text, inspect(params)}, state}\n    end\n\n    def handle_in({\"ping\", opts}, state) do\n      :text = Keyword.fetch!(opts, :opcode)\n      send(self(), :ping)\n      {:ok, state}\n    end\n\n    def handle_info(:ping, state) do\n      {:push, {:text, \"pong\"}, state}\n    end\n\n    def terminate(_reason, {:params, _}) do\n      :ok\n    end\n  end\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    socket \"/ws\", UserSocket,\n      longpoll: [window_ms: 200, pubsub_timeout_ms: 200, check_origin: [\"//example.com\"]],\n      custom: :value\n\n    socket \"/custom/:socket_var\", UserSocket,\n      longpoll: [path: \":path_var/path\", check_origin: [\"//example.com\"], pubsub_timeout_ms: 200],\n      custom: :value\n  end\n\n  setup %{adapter: adapter} do\n    config = Application.get_env(:phoenix, Endpoint)\n    Application.put_env(:phoenix, Endpoint, Keyword.merge(config, adapter: adapter))\n    capture_log(fn -> start_supervised!(Endpoint) end)\n    start_supervised!({Phoenix.PubSub, name: __MODULE__, pool_size: @pool_size})\n    :ok\n  end\n\n  setup do\n    for {_, pid, _, _} <- DynamicSupervisor.which_children(Phoenix.Transports.LongPoll.Supervisor) do\n      DynamicSupervisor.terminate_child(Phoenix.Transports.LongPoll.Supervisor, pid)\n    end\n\n    :ok\n  end\n\n  def poll(method, path, params, body \\\\ nil, headers \\\\ %{}) do\n    headers = Map.merge(%{\"content-type\" => \"application/json\"}, headers)\n    url = \"http://127.0.0.1:#{@port}/#{path}?\" <> URI.encode_query(params)\n    {:ok, resp} = HTTPClient.request(method, url, headers, body)\n    update_in(resp.body, &Phoenix.json_library().decode!(&1))\n  end\n\n  for %{adapter: adapter} <- [\n        %{adapter: Bandit.PhoenixAdapter},\n        %{adapter: Phoenix.Endpoint.Cowboy2Adapter}\n      ] do\n    describe \"adapter: #{inspect(adapter)}\" do\n      @describetag adapter: adapter\n\n      test \"refuses unallowed origins\" do\n        capture_log(fn ->\n          resp = poll(:get, \"ws/longpoll\", %{}, nil, %{\"origin\" => \"https://example.com\"})\n          assert resp.body[\"status\"] == 410\n\n          resp = poll(:get, \"ws/longpoll\", %{}, nil, %{\"origin\" => \"http://notallowed.com\"})\n          assert resp.body[\"status\"] == 403\n        end)\n      end\n\n      test \"returns params with sync request\" do\n        resp = poll(:get, \"ws/longpoll\", %{\"hello\" => \"world\"}, nil)\n        assert resp.body[\"token\"]\n        assert resp.body[\"status\"] == 410\n        assert resp.status == 200\n        secret = Map.take(resp.body, [\"token\"])\n\n        resp = poll(:post, \"ws/longpoll\", secret, \"params\")\n        assert resp.body[\"status\"] == 200\n\n        resp = poll(:get, \"ws/longpoll\", secret, nil)\n        assert resp.body[\"messages\"] == [~s(%{\"hello\" => \"world\"})]\n      end\n\n      test \"allows a path with variables\" do\n        path = \"custom/123/456/path\"\n        resp = poll(:get, path, %{\"key\" => \"value\"}, nil)\n        secret = Map.take(resp.body, [\"token\"])\n\n        resp = poll(:post, path, secret, \"params\")\n        assert resp.body[\"status\"] == 200\n\n        resp = poll(:get, path, secret, nil)\n        [params] = resp.body[\"messages\"]\n        assert params =~ ~s(\"key\" => \"value\")\n        assert params =~ ~s(\"socket_var\" => \"123\")\n        assert params =~ ~s(path_var\" => \"456\")\n      end\n\n      test \"returns pong from async request\" do\n        resp = poll(:get, \"ws/longpoll\", %{\"hello\" => \"world\"}, nil)\n        assert resp.body[\"token\"]\n        assert resp.body[\"status\"] == 410\n        assert resp.status == 200\n        secret = Map.take(resp.body, [\"token\"])\n\n        resp = poll(:post, \"ws/longpoll\", secret, \"ping\")\n        assert resp.body[\"status\"] == 200\n\n        resp = poll(:get, \"ws/longpoll\", secret, nil)\n        assert resp.body[\"messages\"] == [\"pong\"]\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/integration/websocket_channels_test.exs",
    "content": "Code.require_file(\"../../support/websocket_client.exs\", __DIR__)\n\ndefmodule Phoenix.Integration.WebSocketChannelsTest do\n  # TODO: use parameterized tests once we require Elixir 1.18\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Integration.WebsocketClient\n  alias Phoenix.Socket.{V1, V2, Message}\n  alias __MODULE__.Endpoint\n\n  @moduletag :capture_log\n  @port 5807\n\n  Application.put_env(:phoenix, Endpoint,\n    https: false,\n    http: [port: @port],\n    debug_errors: false,\n    server: true,\n    drainer: false,\n    pubsub_server: __MODULE__,\n    secret_key_base: String.duplicate(\"a\", 64)\n  )\n\n  defp lobby do\n    \"room:lobby#{System.unique_integer()}\"\n  end\n\n  defmodule RoomChannel do\n    use Phoenix.Channel\n\n    intercept [\"new_msg\"]\n\n    def join(topic, message, socket) do\n      Process.register(self(), String.to_atom(topic))\n      send(self(), {:after_join, message})\n      {:ok, socket}\n    end\n\n    def handle_info({:after_join, message}, socket) do\n      broadcast(socket, \"user_entered\", %{user: message[\"user\"]})\n      push(socket, \"joined\", Map.merge(%{status: \"connected\"}, socket.assigns))\n      {:noreply, socket}\n    end\n\n    def handle_in(\"new_msg\", message, socket) do\n      broadcast!(socket, \"new_msg\", message)\n      {:reply, :ok, socket}\n    end\n\n    def handle_in(\"boom\", _message, _socket) do\n      raise \"boom\"\n    end\n\n    def handle_in(\"binary_event\", {:binary, data}, socket) do\n      push(socket, \"binary_event\", {:binary, <<0, 1>>})\n      {:reply, {:ok, {:binary, <<data::binary, 3, 4>>}}, socket}\n    end\n\n    def handle_out(\"new_msg\", payload, socket) do\n      push(socket, \"new_msg\", Map.put(payload, \"transport\", inspect(socket.transport)))\n      {:noreply, socket}\n    end\n\n    def terminate(_reason, socket) do\n      push(socket, \"you_left\", %{message: \"bye!\"})\n      :ok\n    end\n  end\n\n  defmodule CustomChannel do\n    use GenServer, restart: :temporary\n\n    def start_link(from) do\n      GenServer.start_link(__MODULE__, from)\n    end\n\n    def init({_, _}) do\n      {:ok, :init}\n    end\n\n    def handle_info({Phoenix.Channel, payload, from, socket}, :init) do\n      case payload[\"action\"] do\n        \"ok\" ->\n          GenServer.reply(from, {:ok, %{\"action\" => \"ok\"}})\n          {:noreply, socket}\n\n        \"ignore\" ->\n          GenServer.reply(from, {:error, %{\"action\" => \"ignore\"}})\n          send(self(), :stop)\n          {:noreply, socket}\n\n        \"error\" ->\n          raise \"oops\"\n      end\n    end\n\n    def handle_info(%Message{event: \"close\"}, socket) do\n      send(socket.transport_pid, {:socket_close, self(), :shutdown})\n      {:stop, :shutdown, socket}\n    end\n\n    def handle_info(:stop, socket) do\n      {:stop, :shutdown, socket}\n    end\n  end\n\n  defmodule UserSocketConnectInfo do\n    use Phoenix.Socket, log: false\n\n    channel \"room:*\", RoomChannel\n\n    def connect(params, socket, connect_info) do\n      unless params[\"logging\"] == \"enabled\", do: Logger.put_process_level(self(), :none)\n      address = Tuple.to_list(connect_info.peer_data.address) |> Enum.join(\".\")\n\n      connect_info =\n        connect_info\n        |> Map.update!(:peer_data, &Map.put(&1, :address, address))\n        |> Map.update!(:trace_context_headers, &Map.new/1)\n        |> Map.update!(:uri, &Map.from_struct/1)\n        |> Map.update!(:x_headers, &Map.new/1)\n        |> Map.update!(:sec_websocket_headers, &Map.new/1)\n\n      socket =\n        socket\n        |> assign(:user_id, params[\"user_id\"])\n        |> assign(:connect_info, connect_info)\n\n      {:ok, socket}\n    end\n\n    def id(socket) do\n      if id = socket.assigns.user_id, do: \"user_sockets:#{id}\"\n    end\n  end\n\n  defmodule UserSocket do\n    use Phoenix.Socket\n\n    channel \"room:*\", RoomChannel\n    channel \"custom:*\", CustomChannel\n\n    def connect(%{\"reject\" => \"true\"}, _socket) do\n      :error\n    end\n\n    def connect(%{\"ratelimit\" => \"true\"}, _socket) do\n      {:error, :rate_limit}\n    end\n\n    def connect(params, socket) do\n      unless params[\"logging\"] == \"enabled\", do: Logger.put_process_level(self(), :none)\n      {:ok, assign(socket, :user_id, params[\"user_id\"])}\n    end\n\n    def id(socket) do\n      if id = socket.assigns.user_id, do: \"user_sockets:#{id}\"\n    end\n\n    def handle_error(conn, :rate_limit), do: Plug.Conn.send_resp(conn, 429, \"Too many requests\")\n  end\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    @session_config store: :cookie,\n                    key: \"_hello_key\",\n                    signing_salt: \"change_me\"\n\n    socket \"/ws\", UserSocket,\n      websocket: [\n        check_origin: [\"//example.com\"],\n        timeout: 200,\n        error_handler: {UserSocket, :handle_error, []}\n      ]\n\n    socket \"/ws/admin\", UserSocket,\n      websocket: [\n        check_origin: [\"//example.com\"],\n        timeout: 200\n      ]\n\n    socket \"/ws/connect_info\", UserSocketConnectInfo,\n      websocket: [\n        check_origin: [\"//example.com\"],\n        timeout: 200,\n        connect_info: [\n          :trace_context_headers,\n          :x_headers,\n          :peer_data,\n          :uri,\n          :user_agent,\n          :sec_websocket_headers,\n          session: @session_config,\n          signing_salt: \"salt\"\n        ]\n      ]\n\n    plug Plug.Session, @session_config\n    plug :fetch_session\n    plug Plug.CSRFProtection\n    plug :put_session\n\n    defp put_session(conn, _) do\n      conn\n      |> put_session(:from_session, \"123\")\n      |> send_resp(200, Plug.CSRFProtection.get_csrf_token())\n    end\n  end\n\n  setup %{adapter: adapter} do\n    config = Application.get_env(:phoenix, Endpoint)\n    Application.put_env(:phoenix, Endpoint, Keyword.merge(config, adapter: adapter))\n    capture_log(fn -> start_supervised!(Endpoint) end)\n    start_supervised!({Phoenix.PubSub, name: __MODULE__})\n    :ok\n  end\n\n  @endpoint Endpoint\n\n  for %{adapter: adapter} <- [\n        %{adapter: Bandit.PhoenixAdapter},\n        %{adapter: Phoenix.Endpoint.Cowboy2Adapter}\n      ] do\n    for {serializer, vsn, join_ref} <- [\n          {V1.JSONSerializer, \"1.0.0\", nil},\n          {V2.JSONSerializer, \"2.0.0\", \"11\"}\n        ] do\n      @serializer serializer\n      @vsn vsn\n      @vsn_path \"ws://127.0.0.1:#{@port}/ws/websocket?vsn=#{@vsn}\"\n      @join_ref join_ref\n\n      describe \"adapter: #{inspect(adapter)} - with #{vsn} serializer #{inspect(serializer)}\" do\n        @describetag adapter: adapter\n\n        test \"endpoint handles multiple mount segments\" do\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/admin/websocket?vsn=#{@vsn}\",\n              @serializer\n            )\n\n          WebsocketClient.join(sock, \"room:admin-lobby1\", %{})\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n            join_ref: @join_ref,\n            ref: \"1\",\n            topic: \"room:admin-lobby1\"\n          }\n        end\n\n        test \"join, leave, and event messages\" do\n          {:ok, sock} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n          lobby = lobby()\n          WebsocketClient.join(sock, lobby, %{})\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            join_ref: @join_ref,\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n            ref: \"1\",\n            topic: ^lobby\n          }\n\n          assert_receive %Message{\n            event: \"joined\",\n            join_ref: @join_ref,\n            payload: %{\"status\" => \"connected\", \"user_id\" => nil}\n          }\n\n          assert_receive %Message{\n            event: \"user_entered\",\n            payload: %{\"user\" => nil},\n            join_ref: nil,\n            ref: nil,\n            topic: ^lobby\n          }\n\n          channel_pid = Process.whereis(String.to_atom(lobby))\n          assert channel_pid\n          assert Process.alive?(channel_pid)\n\n          WebsocketClient.send_event(sock, lobby, \"new_msg\", %{body: \"hi!\"})\n\n          assert_receive %Message{\n            event: \"new_msg\",\n            payload: %{\"transport\" => \":websocket\", \"body\" => \"hi!\"}\n          }\n\n          WebsocketClient.leave(sock, lobby, %{})\n          assert_receive %Message{event: \"you_left\", payload: %{\"message\" => \"bye!\"}}\n          assert_receive %Message{event: \"phx_reply\", payload: %{\"status\" => \"ok\"}}\n          assert_receive %Message{event: \"phx_close\", payload: %{}}\n          refute Process.alive?(channel_pid)\n\n          WebsocketClient.send_event(sock, lobby, \"new_msg\", %{body: \"Should ignore\"})\n          refute_receive %Message{event: \"new_msg\"}\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            payload: %{\"response\" => %{\"reason\" => \"unmatched topic\"}}\n          }\n\n          WebsocketClient.send_event(sock, lobby, \"new_msg\", %{body: \"Should ignore\"})\n          refute_receive %Message{event: \"new_msg\"}\n        end\n\n        test \"transport x_headers are extracted to the socket connect_info\" do\n          extra_headers = [{\"x-application\", \"Phoenix\"}]\n\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer,\n              extra_headers\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\"connect_info\" => %{\"x_headers\" => %{\"x-application\" => \"Phoenix\"}}}\n          }\n        end\n\n        test \"transport sec-websocket-* headers are extracted to the socket connect_info\" do\n          extra_headers = [\n            {\"sec-websocket-protocol\", \"phoenix, 123\"},\n            {\"sec-websocket-extensions\", \"permessage-deflate; client_max_window_bits=15\"}\n          ]\n\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer,\n              extra_headers\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\n                \"sec_websocket_headers\" => %{\n                  \"sec-websocket-protocol\" => \"phoenix, 123\",\n                  \"sec-websocket-extensions\" => \"permessage-deflate; client_max_window_bits=15\"\n                }\n              }\n            }\n          }\n        end\n\n        test \"transport trace_context_headers are extracted to the socket connect_info\" do\n          extra_headers = [\n            {\"traceparent\", \"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\"},\n            {\"tracestate\", \"congo=t61rcWkgMzE\"}\n          ]\n\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer,\n              extra_headers\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\n                \"trace_context_headers\" => %{\n                  \"traceparent\" => \"00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01\",\n                  \"tracestate\" => \"congo=t61rcWkgMzE\"\n                }\n              }\n            }\n          }\n        end\n\n        test \"transport peer_data is extracted to the socket connect_info\" do\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\n                \"peer_data\" => %{\"address\" => \"127.0.0.1\", \"port\" => _, \"ssl_cert\" => nil}\n              }\n            }\n          }\n        end\n\n        test \"transport uri is extracted to the socket connect_info\" do\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\n                \"uri\" => %{\n                  \"host\" => \"127.0.0.1\",\n                  \"path\" => \"/ws/connect_info/websocket\",\n                  \"query\" => \"vsn=#{@vsn}\",\n                  \"scheme\" => \"http\",\n                  \"port\" => @port\n                }\n              }\n            }\n          }\n        end\n\n        test \"transport user agent is extracted to the socket connect_info\" do\n          extra_headers = [{\"user-agent\", \"foo/1.0\"}]\n\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer,\n              extra_headers\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\n                \"user_agent\" => \"foo/1.0\"\n              }\n            }\n          }\n        end\n\n        test \"transport session is extracted to the socket connect_info\" do\n          import Phoenix.ConnTest\n          path = \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\"\n\n          # GET the cookie and CSRF token\n          conn = get(build_conn(), \"/\")\n          extra_headers = [{\"cookie\", \"_hello_key=\" <> conn.resp_cookies[\"_hello_key\"].value}]\n          csrf_token_query = \"&_csrf_token=\" <> URI.encode_www_form(conn.resp_body)\n\n          # It works with headers and cookie\n          {:ok, sock} =\n            WebsocketClient.connect(self(), path <> csrf_token_query, @serializer, extra_headers)\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\n              \"connect_info\" => %{\"session\" => %{\"from_session\" => \"123\", \"_csrf_token\" => _}}\n            }\n          }\n\n          # It doesn't work without headers\n          {:ok, sock} = WebsocketClient.connect(self(), path <> csrf_token_query, @serializer)\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\"connect_info\" => %{\"session\" => nil}}\n          }\n\n          # It doesn't work with invalid csrf token\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              path <> \"&_csrf_token=bad\",\n              @serializer,\n              extra_headers\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\"connect_info\" => %{\"session\" => nil}}\n          }\n        end\n\n        test \"transport custom keywords are extracted to the socket connect_info\" do\n          {:ok, sock} =\n            WebsocketClient.connect(\n              self(),\n              \"ws://127.0.0.1:#{@port}/ws/connect_info/websocket?vsn=#{@vsn}\",\n              @serializer\n            )\n\n          WebsocketClient.join(sock, lobby(), %{})\n\n          assert_receive %Message{\n            event: \"joined\",\n            payload: %{\"connect_info\" => %{\"signing_salt\" => \"salt\"}}\n          }\n        end\n\n        test \"logs user socket connect when enabled\" do\n          log =\n            capture_log(fn ->\n              {:ok, _} =\n                WebsocketClient.connect(self(), \"#{@vsn_path}&logging=enabled\", @serializer)\n            end)\n\n          assert log =~ \"CONNECTED TO Phoenix.Integration.WebSocketChannelsTest.UserSocket in \"\n          assert log =~ \"  Transport: :websocket\"\n          assert log =~ \"  Serializer: #{inspect(@serializer)}\"\n          assert log =~ \"  Parameters: %{\\\"logging\\\" => \\\"enabled\\\", \\\"vsn\\\" => #{inspect(@vsn)}}\"\n        end\n\n        test \"does not log user socket connect when disabled\" do\n          log =\n            capture_log(fn ->\n              {:ok, _} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n            end)\n\n          assert log == \"\"\n        end\n\n        test \"logs and filter params on join and handle_in\" do\n          topic = \"room:admin-lobby2\"\n\n          {:ok, sock} =\n            WebsocketClient.connect(self(), \"#{@vsn_path}&logging=enabled\", @serializer)\n\n          log =\n            capture_log(fn ->\n              WebsocketClient.join(sock, topic, %{\"join\" => \"yes\", \"password\" => \"no\"})\n\n              assert_receive %Message{\n                event: \"phx_reply\",\n                join_ref: @join_ref,\n                payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n                ref: \"1\",\n                topic: \"room:admin-lobby2\"\n              }\n            end)\n\n          assert log =~ \"JOINED room:admin-lobby2 in \"\n          assert log =~ \"Parameters: %{\\\"join\\\" => \\\"yes\\\", \\\"password\\\" => \\\"[FILTERED]\\\"}\"\n\n          log =\n            capture_log(fn ->\n              WebsocketClient.send_event(sock, topic, \"new_msg\", %{\n                \"in\" => \"yes\",\n                \"password\" => \"no\"\n              })\n\n              assert_receive %Message{event: \"phx_reply\", ref: \"2\"}\n            end)\n\n          assert log =~\n                   \"HANDLED new_msg INCOMING ON room:admin-lobby2 (Phoenix.Integration.WebSocketChannelsTest.RoomChannel)\"\n\n          assert log =~ \"Parameters: %{\\\"in\\\" => \\\"yes\\\", \\\"password\\\" => \\\"[FILTERED]\\\"}\"\n        end\n\n        test \"sends phx_error if a channel server abnormally exits\" do\n          {:ok, sock} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n\n          lobby = lobby()\n          WebsocketClient.join(sock, lobby, %{})\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            ref: \"1\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n\n          assert_receive %Message{event: \"joined\"}\n          assert_receive %Message{event: \"user_entered\"}\n\n          capture_log(fn ->\n            WebsocketClient.send_event(sock, lobby, \"boom\", %{})\n            assert_receive %Message{event: \"phx_error\", payload: %{}, topic: ^lobby}\n          end)\n        end\n\n        test \"channels are terminated if transport normally exits\" do\n          {:ok, sock} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n\n          lobby = lobby()\n          WebsocketClient.join(sock, lobby, %{})\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            ref: \"1\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n\n          assert_receive %Message{event: \"joined\"}\n\n          channel = Process.whereis(String.to_atom(lobby))\n          assert channel\n          Process.monitor(channel)\n          WebsocketClient.close(sock)\n\n          assert_receive {:DOWN, _, :process, ^channel, shutdown}\n                         when shutdown in [\n                                :shutdown,\n                                {:shutdown, :closed},\n                                {:shutdown, :local_closed}\n                              ]\n        end\n\n        test \"refuses websocket events that haven't joined\" do\n          {:ok, sock} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n\n          WebsocketClient.send_event(sock, lobby(), \"new_msg\", %{body: \"hi!\"})\n          refute_receive %Message{event: \"new_msg\"}\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            payload: %{\"response\" => %{\"reason\" => \"unmatched topic\"}}\n          }\n\n          WebsocketClient.send_event(sock, lobby(), \"new_msg\", %{body: \"Should ignore\"})\n          refute_receive %Message{event: \"new_msg\"}\n        end\n\n        test \"refuses unallowed origins\" do\n          capture_log(fn ->\n            assert {:ok, _} =\n                     WebsocketClient.connect(self(), @vsn_path, @serializer, [\n                       {\"origin\", \"https://example.com\"}\n                     ])\n\n            assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 403}} =\n                     WebsocketClient.connect(self(), @vsn_path, @serializer, [\n                       {\"origin\", \"http://notallowed.com\"}\n                     ])\n          end)\n        end\n\n        test \"refuses connects that error with 403 response\" do\n          assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 403}} =\n                   WebsocketClient.connect(self(), \"#{@vsn_path}&reject=true\", @serializer)\n        end\n\n        test \"refuses connects that error with custom error response\" do\n          assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 429}} =\n                   WebsocketClient.connect(self(), \"#{@vsn_path}&ratelimit=true\", @serializer)\n        end\n\n        test \"shuts down when receiving disconnect broadcasts on socket's id\" do\n          {:ok, sock} = WebsocketClient.connect(self(), \"#{@vsn_path}&user_id=1001\", @serializer)\n\n          WebsocketClient.join(sock, \"room:wsdisconnect1\", %{})\n\n          assert_receive %Message{\n            topic: \"room:wsdisconnect1\",\n            event: \"phx_reply\",\n            ref: \"1\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n\n          WebsocketClient.join(sock, \"room:wsdisconnect2\", %{})\n\n          assert_receive %Message{\n            topic: \"room:wsdisconnect2\",\n            event: \"phx_reply\",\n            ref: \"2\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n\n          chan1 = Process.whereis(:\"room:wsdisconnect1\")\n          assert chan1\n          chan2 = Process.whereis(:\"room:wsdisconnect2\")\n          assert chan2\n          Process.monitor(sock)\n          Process.monitor(chan1)\n          Process.monitor(chan2)\n\n          Endpoint.broadcast(\"user_sockets:1001\", \"disconnect\", %{})\n\n          assert_receive {:DOWN, _, :process, ^sock, :normal}\n          assert_receive {:DOWN, _, :process, ^chan1, shutdown}\n          # :shutdown for cowboy, {:shutdown, :closed} for cowboy 2, {:shutdown, :disconnected}\n          # for bandit\n          assert shutdown in [:shutdown, {:shutdown, :closed}, {:shutdown, :disconnected}]\n          assert_receive {:DOWN, _, :process, ^chan2, shutdown}\n          assert shutdown in [:shutdown, {:shutdown, :closed}, {:shutdown, :disconnected}]\n        end\n\n        test \"duplicate join event closes existing channel\" do\n          {:ok, sock} = WebsocketClient.connect(self(), \"#{@vsn_path}&user_id=1001\", @serializer)\n          WebsocketClient.join(sock, \"room:joiner\", %{})\n\n          assert_receive %Message{\n            topic: \"room:joiner\",\n            event: \"phx_reply\",\n            ref: \"1\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n\n          WebsocketClient.join(sock, \"room:joiner\", %{})\n\n          assert_receive %Message{\n            topic: \"room:joiner\",\n            event: \"phx_reply\",\n            ref: \"2\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"}\n          }\n        end\n\n        test \"returns 403 when versions to not match\" do\n          assert capture_log(fn ->\n                   url = \"ws://127.0.0.1:#{@port}/ws/websocket?vsn=123.1.1\"\n\n                   assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 403}} =\n                            WebsocketClient.connect(self(), url, @serializer)\n                 end) =~\n                   \"The client's requested transport version \\\"123.1.1\\\" does not match server's version\"\n        end\n\n        test \"shuts down if client goes quiet\" do\n          {:ok, socket} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n          Process.monitor(socket)\n          WebsocketClient.send_heartbeat(socket)\n\n          assert_receive %Message{\n            event: \"phx_reply\",\n            payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n            ref: \"1\",\n            topic: \"phoenix\"\n          }\n\n          assert_receive {:DOWN, _, :process, ^socket, :normal}, 400\n        end\n\n        test \"warns for unmatched topic\" do\n          {:ok, sock} =\n            WebsocketClient.connect(self(), \"#{@vsn_path}&logging=enabled\", @serializer)\n\n          log =\n            capture_log(fn ->\n              WebsocketClient.join(sock, \"unmatched-topic\", %{})\n\n              assert_receive %Message{\n                event: \"phx_reply\",\n                ref: \"1\",\n                topic: \"unmatched-topic\",\n                join_ref: nil,\n                payload: %{\n                  \"status\" => \"error\",\n                  \"response\" => %{\"reason\" => \"unmatched topic\"}\n                }\n              }\n            end)\n\n          assert log =~\n                   \"Ignoring unmatched topic \\\"unmatched-topic\\\" in Phoenix.Integration.WebSocketChannelsTest.UserSocket\"\n        end\n      end\n    end\n\n    # Those tests are not transport specific but for integration purposes\n    # it is best to assert custom channels work throughout the whole stack,\n    # compared to only testing the socket <-> channel communication. Which\n    # is why test them under the latest websocket transport.\n    describe \"adapter: #{inspect(adapter)} - custom channels\" do\n      @describetag adapter: adapter\n      @serializer V2.JSONSerializer\n      @vsn \"2.0.0\"\n      @vsn_path \"ws://127.0.0.1:#{@port}/ws/websocket?vsn=#{@vsn}\"\n\n      test \"join, ignore, error, and event messages\" do\n        {:ok, sock} = WebsocketClient.connect(self(), @vsn_path, @serializer)\n\n        WebsocketClient.join(sock, \"custom:ignore\", %{\"action\" => \"ignore\"})\n\n        assert_receive %Message{\n          event: \"phx_reply\",\n          join_ref: \"11\",\n          payload: %{\"response\" => %{\"action\" => \"ignore\"}, \"status\" => \"error\"},\n          ref: \"1\",\n          topic: \"custom:ignore\"\n        }\n\n        WebsocketClient.join(sock, \"custom:error\", %{\"action\" => \"error\"})\n\n        assert_receive %Message{\n          event: \"phx_reply\",\n          join_ref: \"12\",\n          payload: %{\"response\" => %{\"reason\" => \"join crashed\"}, \"status\" => \"error\"},\n          ref: \"2\",\n          topic: \"custom:error\"\n        }\n\n        WebsocketClient.join(sock, \"custom:ok\", %{\"action\" => \"ok\"})\n\n        assert_receive %Message{\n          event: \"phx_reply\",\n          join_ref: \"13\",\n          payload: %{\"response\" => %{\"action\" => \"ok\"}, \"status\" => \"ok\"},\n          ref: \"3\",\n          topic: \"custom:ok\"\n        }\n\n        WebsocketClient.send_event(sock, \"custom:ok\", \"close\", %{body: \"bye!\"})\n        assert_receive %Message{event: \"phx_close\", payload: %{}}\n      end\n    end\n\n    describe \"adapter: #{inspect(adapter)} - binary\" do\n      @describetag adapter: adapter\n      @serializer V2.JSONSerializer\n      @vsn \"2.0.0\"\n      @join_ref \"11\"\n\n      test \"messages can be pushed and received\" do\n        topic = \"room:bin\"\n\n        {:ok, socket} =\n          WebsocketClient.connect(\n            self(),\n            \"ws://127.0.0.1:#{@port}/ws/websocket?vsn=#{@vsn}\",\n            @serializer\n          )\n\n        WebsocketClient.join(socket, topic, %{})\n\n        assert_receive %Message{\n          event: \"phx_reply\",\n          payload: %{\"response\" => %{}, \"status\" => \"ok\"},\n          join_ref: @join_ref,\n          ref: \"1\",\n          topic: ^topic\n        }\n\n        WebsocketClient.send_event(socket, topic, \"binary_event\", {:binary, <<1, 2>>})\n\n        assert_receive %Message{\n          event: \"phx_reply\",\n          payload: %{\"response\" => {:binary, <<1, 2, 3, 4>>}, \"status\" => \"ok\"},\n          join_ref: @join_ref,\n          ref: \"2\",\n          topic: ^topic\n        }\n\n        assert_receive %Message{\n          event: \"binary_event\",\n          join_ref: @join_ref,\n          payload: {:binary, <<0, 1>>}\n        }\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/integration/websocket_socket_test.exs",
    "content": "Code.require_file(\"../../support/websocket_client.exs\", __DIR__)\nCode.require_file(\"../../support/http_client.exs\", __DIR__)\n\ndefmodule Phoenix.Integration.WebSocketTest do\n  # TODO: use parameterized tests once we require Elixir 1.18\n  use ExUnit.Case\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Integration.{HTTPClient, WebsocketClient}\n  alias __MODULE__.Endpoint\n\n  @moduletag :capture_log\n  @port 5907\n  @path \"ws://127.0.0.1:#{@port}/ws/websocket\"\n\n  Application.put_env(\n    :phoenix,\n    Endpoint,\n    https: false,\n    http: [port: @port],\n    debug_errors: false,\n    server: true,\n    drainer: false\n  )\n\n  defmodule UserSocket do\n    @behaviour Phoenix.Socket.Transport\n\n    def child_spec(opts) do\n      :value = Keyword.fetch!(opts, :custom)\n      :ignore\n    end\n\n    def connect(map) do\n      %{endpoint: Endpoint, params: params, transport: :websocket} = map\n      {:ok, {:params, params}}\n    end\n\n    def init({:params, _} = state) do\n      {:ok, state}\n    end\n\n    def handle_in({\"params\", opts}, {:params, params} = state) do\n      :text = Keyword.fetch!(opts, :opcode)\n      {:reply, :ok, {:text, inspect(params)}, state}\n    end\n\n    def handle_in({\"ping\", opts}, state) do\n      :text = Keyword.fetch!(opts, :opcode)\n      send(self(), :ping)\n      {:ok, state}\n    end\n\n    def handle_info(:ping, state) do\n      {:push, {:text, \"pong\"}, state}\n    end\n\n    def terminate(_reason, {:params, _}) do\n      :ok\n    end\n  end\n\n  defmodule PingSocket do\n    @behaviour Phoenix.Socket.Transport\n\n    def child_spec(_opts), do: :ignore\n    def connect(_), do: {:ok, %{}}\n    def init(state), do: {:ok, state}\n\n    def handle_in({\"ping:start\", _}, state) do\n      {:reply, :ok, {:ping, <<>>}, state}\n    end\n\n    def handle_in({\"ping:start:\" <> payload, _}, state) do\n      {:reply, :ok, {:ping, payload}, state}\n    end\n\n    def handle_info(_, state), do: {:ok, state}\n\n    def handle_control({payload, opts}, state) do\n      opcode = Keyword.fetch!(opts, :opcode)\n      {:push, {:text, \"#{opcode}:#{payload}\"}, state}\n    end\n\n    def terminate(_reason, _state), do: :ok\n  end\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    socket \"/ws\", UserSocket,\n      websocket: [check_origin: [\"//example.com\"], subprotocols: [\"sip\"], timeout: 200],\n      custom: :value\n\n    socket \"/custom/some_path\", UserSocket,\n      websocket: [path: \"nested/path\", check_origin: [\"//example.com\"], timeout: 200],\n      custom: :value\n\n    socket \"/custom/:socket_var\", UserSocket,\n      websocket: [path: \":path_var/path\", check_origin: [\"//example.com\"], timeout: 200],\n      custom: :value\n\n    socket \"/ws/ping\", PingSocket, websocket: true\n  end\n\n  setup %{adapter: adapter} do\n    config = Application.get_env(:phoenix, Endpoint)\n    Application.put_env(:phoenix, Endpoint, Keyword.merge(config, adapter: adapter))\n    capture_log(fn -> start_supervised!(Endpoint) end)\n    :ok\n  end\n\n  for %{adapter: adapter} <- [\n        %{adapter: Bandit.PhoenixAdapter},\n        %{adapter: Phoenix.Endpoint.Cowboy2Adapter}\n      ] do\n    describe \"adapter: #{inspect(adapter)}\" do\n      @describetag adapter: adapter\n\n      test \"handles invalid upgrade requests\" do\n        capture_log(fn ->\n          path = String.replace_prefix(@path, \"ws\", \"http\")\n          assert {:ok, %{body: body, status: 400}} = HTTPClient.request(:get, path, %{})\n          assert body =~ \"'connection' header must contain 'upgrade'\"\n        end)\n      end\n\n      test \"refuses unallowed origins\" do\n        capture_log(fn ->\n          headers = [{\"origin\", \"https://example.com\"}]\n          assert {:ok, _} = WebsocketClient.connect(self(), @path, :noop, headers)\n\n          headers = [{\"origin\", \"http://notallowed.com\"}]\n\n          assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 403}} =\n                   WebsocketClient.connect(self(), @path, :noop, headers)\n        end)\n      end\n\n      test \"refuses unallowed Websocket subprotocols\" do\n        assert capture_log(fn ->\n                 headers = [{\"sec-websocket-protocol\", \"sip\"}]\n                 assert {:ok, _} = WebsocketClient.connect(self(), @path, :noop, headers)\n\n                 headers = []\n                 assert {:ok, _} = WebsocketClient.connect(self(), @path, :noop, headers)\n\n                 headers = [{\"sec-websocket-protocol\", \"mqtt\"}]\n\n                 assert {:error, %Mint.WebSocket.UpgradeFailureError{status_code: 403}} =\n                          WebsocketClient.connect(self(), @path, :noop, headers)\n               end) =~ \"Could not check Websocket subprotocols\"\n      end\n\n      test \"returns params with sync request\" do\n        assert {:ok, client} = WebsocketClient.connect(self(), \"#{@path}?key=value\", :noop)\n        WebsocketClient.send(client, {:text, \"params\"})\n        assert_receive {:text, ~s(%{\"key\" => \"value\"})}\n      end\n\n      test \"ignores control frames when handle_control/2 is not defined\" do\n        assert {:ok, client} = WebsocketClient.connect(self(), @path, :noop)\n        WebsocketClient.send(client, :ping)\n        WebsocketClient.send(client, {:text, \"ping\"})\n        assert_receive {:text, \"pong\"}\n      end\n\n      test \"returns pong from async request\" do\n        assert {:ok, client} = WebsocketClient.connect(self(), \"#{@path}?key=value\", :noop)\n        WebsocketClient.send(client, {:text, \"ping\"})\n        assert_receive {:text, \"pong\"}\n      end\n\n      test \"allows a custom path\" do\n        path = \"ws://127.0.0.1:#{@port}/custom/some_path/nested/path\"\n        assert {:ok, _} = WebsocketClient.connect(self(), \"#{path}?key=value\", :noop)\n      end\n\n      test \"allows a path with variables\" do\n        path = \"ws://127.0.0.1:#{@port}/custom/123/456/path\"\n        assert {:ok, client} = WebsocketClient.connect(self(), \"#{path}?key=value\", :noop)\n        WebsocketClient.send(client, {:text, \"params\"})\n        assert_receive {:text, params}\n        assert params =~ ~s(\"key\" => \"value\")\n        assert params =~ ~s(\"socket_var\" => \"123\")\n        assert params =~ ~s(path_var\" => \"456\")\n      end\n\n      test \"allows using control frames with a payload\" do\n        path = \"ws://127.0.0.1:#{@port}/ws/ping/websocket\"\n        assert {:ok, client} = WebsocketClient.connect(self(), path, :noop)\n        WebsocketClient.send(client, {:ping, \"\"})\n        assert_receive {:pong, \"\"}\n        assert_receive {:text, \"ping:\"}\n\n        WebsocketClient.send(client, {:ping, \"123\"})\n        assert_receive {:pong, \"123\"}\n        assert_receive {:text, \"ping:123\"}\n\n        WebsocketClient.send(client, {:text, \"ping:start\"})\n        assert_receive {:ping, \"\"}\n        assert_receive {:text, \"pong:\"}\n\n        WebsocketClient.send(client, {:text, \"ping:start:123\"})\n        assert_receive {:ping, \"123\"}\n        assert_receive {:text, \"pong:123\"}\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/logger_test.exs",
    "content": "defmodule Phoenix.LoggerTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  describe \"filter_values/2 with discard strategy\" do\n    test \"in top level map\" do\n      values = %{\"foo\" => \"bar\", \"password\" => \"should_not_show\"}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{\"foo\" => \"bar\", \"password\" => \"[FILTERED]\"}\n\n      assert Phoenix.Logger.filter_values(values, Phoenix.Logger.compile_filter([\"password\"])) ==\n               %{\"foo\" => \"bar\", \"password\" => \"[FILTERED]\"}\n    end\n\n    test \"when a map has secret key\" do\n      values = %{\"foo\" => \"bar\", \"map\" => %{\"password\" => \"should_not_show\"}}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{\"foo\" => \"bar\", \"map\" => %{\"password\" => \"[FILTERED]\"}}\n    end\n\n    test \"when a list has a map with secret\" do\n      values = %{\"foo\" => \"bar\", \"list\" => [%{\"password\" => \"should_not_show\"}]}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{\"foo\" => \"bar\", \"list\" => [%{\"password\" => \"[FILTERED]\"}]}\n    end\n\n    test \"does not filter structs\" do\n      values = %{\"foo\" => \"bar\", \"file\" => %Plug.Upload{}}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{\"foo\" => \"bar\", \"file\" => %Plug.Upload{}}\n\n      values = %{\"foo\" => \"bar\", \"file\" => %{__struct__: \"s\"}}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{\"foo\" => \"bar\", \"file\" => %{:__struct__ => \"s\"}}\n    end\n\n    test \"does not fail on atomic keys\" do\n      values = %{:foo => \"bar\", \"password\" => \"should_not_show\"}\n\n      assert Phoenix.Logger.filter_values(values, [\"password\"]) ==\n               %{:foo => \"bar\", \"password\" => \"[FILTERED]\"}\n    end\n  end\n\n  describe \"filter_values/2 with keep strategy\" do\n    test \"discards values not specified in params\" do\n      values = %{\"foo\" => \"bar\", \"password\" => \"abc123\", \"file\" => %Plug.Upload{}}\n\n      assert Phoenix.Logger.filter_values(values, {:keep, []}) ==\n               %{\"foo\" => \"[FILTERED]\", \"password\" => \"[FILTERED]\", \"file\" => \"[FILTERED]\"}\n\n      assert Phoenix.Logger.filter_values(values, Phoenix.Logger.compile_filter({:keep, []})) ==\n               %{\"foo\" => \"[FILTERED]\", \"password\" => \"[FILTERED]\", \"file\" => \"[FILTERED]\"}\n    end\n\n    test \"keeps values that are specified in params\" do\n      values = %{\"foo\" => \"bar\", \"password\" => \"abc123\", \"file\" => %Plug.Upload{}}\n\n      assert Phoenix.Logger.filter_values(values, {:keep, [\"foo\", \"file\"]}) ==\n               %{\"foo\" => \"bar\", \"password\" => \"[FILTERED]\", \"file\" => %Plug.Upload{}}\n    end\n\n    test \"keeps all values under keys that are kept\" do\n      values = %{\"foo\" => %{\"bar\" => 1, \"baz\" => 2}}\n\n      assert Phoenix.Logger.filter_values(values, {:keep, [\"foo\"]}) ==\n               %{\"foo\" => %{\"bar\" => 1, \"baz\" => 2}}\n    end\n\n    test \"only filters leaf values\" do\n      values = %{\"foo\" => %{\"bar\" => 1, \"baz\" => 2}, \"ids\" => [1, 2]}\n\n      assert Phoenix.Logger.filter_values(values, {:keep, []}) ==\n               %{\n                 \"foo\" => %{\"bar\" => \"[FILTERED]\", \"baz\" => \"[FILTERED]\"},\n                 \"ids\" => [\"[FILTERED]\", \"[FILTERED]\"]\n               }\n    end\n  end\n\n  describe \"telemetry\" do\n    def log_level(conn) do\n      case conn.path_info do\n        [] -> :debug\n        [\"warn\" | _] -> :warning\n        [\"error\" | _] -> :error\n        [\"false\" | _] -> false\n        _ -> :info\n      end\n    end\n\n    test \"invokes log level callback from Plug.Telemetry\" do\n      opts =\n        Plug.Telemetry.init(\n          event_prefix: [:phoenix, :endpoint],\n          log: {__MODULE__, :log_level, []}\n        )\n\n      assert ExUnit.CaptureLog.capture_log(fn ->\n               Plug.Telemetry.call(conn(:get, \"/\"), opts)\n             end) =~ \"[debug] GET /\"\n\n      assert ExUnit.CaptureLog.capture_log(fn ->\n               Plug.Telemetry.call(conn(:get, \"/warn\"), opts)\n             end) =~ ~r\"\\[warn(ing)?\\]  ?GET /warn\"\n\n      assert ExUnit.CaptureLog.capture_log(fn ->\n               Plug.Telemetry.call(conn(:get, \"/error/404\"), opts)\n             end) =~ \"[error] GET /error/404\"\n\n      assert ExUnit.CaptureLog.capture_log(fn ->\n               Plug.Telemetry.call(conn(:get, \"/any\"), opts)\n             end) =~ \"[info] GET /any\"\n    end\n\n    test \"invokes log level from Plug.Telemetry\" do\n      assert ExUnit.CaptureLog.capture_log(fn ->\n               opts = Plug.Telemetry.init(event_prefix: [:phoenix, :endpoint], log: :error)\n               Plug.Telemetry.call(conn(:get, \"/\"), opts)\n             end) =~ \"[error] GET /\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/naming_test.exs",
    "content": "defmodule Phoenix.NamingTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Naming\n\n  doctest Naming\n\n  test \"underscore/1 converts Strings to underscore\" do\n    assert Naming.underscore(\"FooBar\") == \"foo_bar\"\n    assert Naming.underscore(\"Foobar\") == \"foobar\"\n    assert Naming.underscore(\"APIWorld\") == \"api_world\"\n    assert Naming.underscore(\"ErlangVM\") == \"erlang_vm\"\n    assert Naming.underscore(\"API.V1.User\") == \"api/v1/user\"\n    assert Naming.underscore(\"\") == \"\"\n    assert Naming.underscore(\"FooBar1\") == \"foo_bar1\"\n    assert Naming.underscore(\"fooBar1\") == \"foo_bar1\"\n  end\n\n  test \"camelize/1 converts Strings to camel case\" do\n    assert Naming.camelize(\"foo_bar\") == \"FooBar\"\n    assert Naming.camelize(\"foo__bar\") == \"FooBar\"\n    assert Naming.camelize(\"foobar\") == \"Foobar\"\n    assert Naming.camelize(\"_foobar\") == \"Foobar\"\n    assert Naming.camelize(\"__foobar\") == \"Foobar\"\n    assert Naming.camelize(\"_FooBar\") == \"FooBar\"\n    assert Naming.camelize(\"foobar_\") == \"Foobar\"\n    assert Naming.camelize(\"foobar_1\") == \"Foobar1\"\n    assert Naming.camelize(\"\") == \"\"\n    assert Naming.camelize(\"_foo_bar\") == \"FooBar\"\n    assert Naming.camelize(\"foo_bar_1\") == \"FooBar1\"\n  end\n\n  test \"camelize/2 converts Strings to lower camel case\" do\n    assert Naming.camelize(\"foo_bar\", :lower) == \"fooBar\"\n    assert Naming.camelize(\"foo__bar\", :lower) == \"fooBar\"\n    assert Naming.camelize(\"foobar\", :lower) == \"foobar\"\n    assert Naming.camelize(\"_foobar\", :lower) == \"foobar\"\n    assert Naming.camelize(\"__foobar\", :lower) == \"foobar\"\n    assert Naming.camelize(\"_FooBar\", :lower) == \"fooBar\"\n    assert Naming.camelize(\"foobar_\", :lower) == \"foobar\"\n    assert Naming.camelize(\"foobar_1\", :lower) == \"foobar1\"\n    assert Naming.camelize(\"\", :lower) == \"\"\n    assert Naming.camelize(\"_foo_bar\", :lower) == \"fooBar\"\n    assert Naming.camelize(\"foo_bar_1\", :lower) == \"fooBar1\"\n  end\nend\n"
  },
  {
    "path": "test/phoenix/param_test.exs",
    "content": "defmodule Phoenix.ParamTest do\n  use ExUnit.Case, async: true\n\n  import Phoenix.Param\n\n  test \"to_param for integers\" do\n    assert to_param(1) == \"1\"\n  end\n\n  test \"to_param for floats\" do\n    assert to_param(3.14) == \"3.14\"\n  end\n\n  test \"to_param for binaries\" do\n    assert to_param(\"foo\") == \"foo\"\n  end\n\n  test \"to_param for atoms\" do\n    assert to_param(:foo) == \"foo\"\n    assert to_param(true) == \"true\"\n    assert to_param(false) == \"false\"\n    assert_raise ArgumentError, fn -> to_param(nil) end\n  end\n\n  test \"to_param for maps\" do\n    assert_raise ArgumentError, fn -> to_param(%{id: 1}) end\n  end\n\n  test \"to_param for structs\" do\n    defmodule Foo do\n      defstruct [:id]\n    end\n    assert to_param(struct(Foo, id: 1)) == \"1\"\n    assert to_param(struct(Foo, id: \"foo\")) == \"foo\"\n  after\n    :code.purge(__MODULE__.Foo)\n    :code.delete(__MODULE__.Foo)\n  end\n\n  test \"to_param for derivable structs without id\" do\n    msg = ~r\"cannot derive Phoenix.Param for struct Phoenix.ParamTest.Bar\"\n    assert_raise ArgumentError, msg, fn ->\n      defmodule Bar do\n        @derive Phoenix.Param\n        defstruct [:uuid]\n      end\n    end\n\n    defmodule Bar do\n      @derive {Phoenix.Param, key: :uuid}\n      defstruct [:uuid]\n    end\n\n    assert to_param(struct(Bar, uuid: 1)) == \"1\"\n    assert to_param(struct(Bar, uuid: \"foo\")) == \"foo\"\n\n    msg = ~r\"cannot convert Phoenix.ParamTest.Bar to param, key :uuid contains a nil value\"\n    assert_raise ArgumentError, msg, fn ->\n      to_param(struct(Bar, uuid: nil))\n    end\n  after\n    :code.purge(Module.concat(Phoenix.Param, __MODULE__.Bar))\n    :code.delete(Module.concat(Phoenix.Param, __MODULE__.Bar))\n    :code.purge(__MODULE__.Bar)\n    :code.delete(__MODULE__.Bar)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/presence_test.exs",
    "content": "defmodule Phoenix.PresenceTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Socket.Broadcast\n\n  defmodule DefaultPresence do\n    use Phoenix.Presence, otp_app: :phoenix\n  end\n\n  defmodule MyPresence do\n    use Phoenix.Presence, otp_app: :phoenix\n\n    def fetch(_topic, entries) do\n      for {key, %{metas: metas}} <- entries, into: %{} do\n        {key, %{metas: metas, extra: \"extra\"}}\n      end\n    end\n  end\n\n  defmodule MetasPresence do\n    use Phoenix.Presence, otp_app: :phoenix\n\n    def init(state), do: {:ok, state}\n\n    def handle_metas(topic, diff, presences, state) do\n      Phoenix.PubSub.local_broadcast(PresPub, topic, %{diff: diff, presences: presences})\n      {:ok, state}\n    end\n  end\n\n  defmodule MetasMissingInitPresence do\n    use Phoenix.Presence, otp_app: :phoenix\n\n    def init_presence do\n      Phoenix.Presence.init({\n        __MODULE__,\n        __MODULE__.TaskSupervisor,\n        PresPub,\n        Phoenix.Channel.Server\n      })\n    end\n\n    def handle_metas(_topic, _diff, _presences, _state) do\n      raise ArgumentError, \"should not be called due to missing init/1\"\n    end\n  end\n\n  defmodule CustomDispatcher do\n    def dispatch(entries, from, message) do\n      for {pid, _} <- entries, pid != from, do: send(pid, {:custom_dispatcher, message})\n\n      :ok\n    end\n  end\n\n  defmodule CustomDispatcherPresence do\n    use Phoenix.Presence, otp_app: :phoenix, dispatcher: CustomDispatcher\n  end\n\n  Application.put_env(:phoenix, MyPresence, pubsub_server: PresPub)\n  Application.put_env(:phoenix, MetasPresence, pubsub_server: PresPub)\n  Application.put_env(:phoenix, CustomDispatcherPresence, pubsub_server: PresPub)\n\n  setup_all do\n    start_supervised!({Phoenix.PubSub, name: PresPub, pool_size: 1})\n    start_supervised!(MyPresence)\n    start_supervised!(MetasPresence)\n    start_supervised!(CustomDispatcherPresence)\n    {:ok, pubsub: PresPub}\n  end\n\n  setup config do\n    {:ok, topic: to_string(config.test)}\n  end\n\n  test \"defines child_spec/1\" do\n    assert DefaultPresence.child_spec([]) == %{\n             id: DefaultPresence,\n             start:\n               {Phoenix.Presence, :start_link,\n                [\n                  Phoenix.PresenceTest.DefaultPresence,\n                  Phoenix.PresenceTest.DefaultPresence.TaskSupervisor,\n                  [otp_app: :phoenix]\n                ]},\n             type: :supervisor\n           }\n  end\n\n  test \"default fetch/2 returns pass-through data\", config do\n    presences = %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: \"ref\"}]}}\n    assert DefaultPresence.fetch(config.topic, presences) == presences\n  end\n\n  test \"list/1 lists presences from tracker\", config do\n    assert MyPresence.list(config.topic) == %{}\n    assert MyPresence.list(%Phoenix.Socket{topic: config.topic}) == %{}\n    assert {:ok, _} = MyPresence.track(self(), config.topic, \"u1\", %{name: \"u1\"})\n\n    assert %{\"u1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: _}]}} =\n             MyPresence.list(config.topic)\n\n    assert %{\"u1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: _}]}} =\n             MyPresence.list(%Phoenix.Socket{topic: config.topic})\n  end\n\n  test \"list/1 returns keys as strings\", config do\n    assert {:ok, _} = MyPresence.track(self(), config.topic, 1, %{name: \"u1\"})\n\n    assert %{\"1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: _}]}} =\n             MyPresence.list(config.topic)\n  end\n\n  test \"get_by_key/2 returns metadata for key\", config do\n    pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n    pid3 = spawn(fn -> :timer.sleep(:infinity) end)\n    assert MyPresence.get_by_key(config.topic, 1) == []\n    assert {:ok, _} = MyPresence.track(self(), config.topic, 1, %{name: \"u1\"})\n    assert {:ok, _} = MyPresence.track(pid2, config.topic, 1, %{name: \"u1.2\"})\n    assert {:ok, _} = MyPresence.track(pid3, config.topic, 2, %{name: \"u1.2\"})\n\n    assert %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: _}, %{name: \"u1.2\", phx_ref: _}]} =\n             MyPresence.get_by_key(config.topic, 1)\n\n    assert MyPresence.get_by_key(config.topic, \"another_key\") == []\n    assert MyPresence.get_by_key(\"another_topic\", 2) == []\n  end\n\n  test \"handle_diff broadcasts events with default fetched data\",\n       %{topic: topic} = config do\n    pid = spawn(fn -> :timer.sleep(:infinity) end)\n    Phoenix.PubSub.subscribe(config.pubsub, topic)\n    start_supervised!({DefaultPresence, pubsub_server: config.pubsub})\n    DefaultPresence.track(pid, topic, \"u1\", %{name: \"u1\"})\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: u1_ref}]}},\n        leaves: %{}\n      }\n    }\n\n    assert %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}} = DefaultPresence.list(topic)\n\n    Process.exit(pid, :kill)\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{},\n        leaves: %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}}\n      }\n    }\n\n    assert DefaultPresence.list(topic) == %{}\n  end\n\n  test \"handle_diff broadcasts events with custom fetched data\",\n       %{topic: topic} = config do\n    pid = spawn(fn -> :timer.sleep(:infinity) end)\n    Phoenix.PubSub.subscribe(config.pubsub, topic)\n    MyPresence.track(pid, topic, \"u1\", %{name: \"u1\"})\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{\"u1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: u1_ref}]}},\n        leaves: %{}\n      }\n    }\n\n    assert %{\"u1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}} =\n             MyPresence.list(topic)\n\n    Process.exit(pid, :kill)\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{},\n        leaves: %{\"u1\" => %{extra: \"extra\", metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}}\n      }\n    }\n\n    assert MyPresence.list(topic) == %{}\n  end\n\n  test \"handle_diff with custom dispatcher\", %{topic: topic} = config do\n    pid = spawn(fn -> :timer.sleep(:infinity) end)\n    Phoenix.PubSub.subscribe(config.pubsub, topic)\n    start_supervised!({DefaultPresence, pubsub_server: config.pubsub})\n    CustomDispatcherPresence.track(pid, topic, \"u1\", %{name: \"u1\"})\n\n    assert_receive {:custom_dispatcher,\n                    %Broadcast{\n                      topic: ^topic,\n                      event: \"presence_diff\",\n                      payload: %{\n                        joins: %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: u1_ref}]}},\n                        leaves: %{}\n                      }\n                    }}\n\n    assert %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}} =\n             CustomDispatcherPresence.list(topic)\n\n    Process.exit(pid, :kill)\n\n    assert_receive {:custom_dispatcher,\n                    %Broadcast{\n                      topic: ^topic,\n                      event: \"presence_diff\",\n                      payload: %{\n                        joins: %{},\n                        leaves: %{\"u1\" => %{metas: [%{name: \"u1\", phx_ref: ^u1_ref}]}}\n                      }\n                    }}\n\n    assert CustomDispatcherPresence.list(topic) == %{}\n  end\n\n  test \"untrack with pid\", %{topic: topic} = config do\n    Phoenix.PubSub.subscribe(config.pubsub, config.topic)\n    MyPresence.track(self(), config.topic, \"u1\", %{})\n    MyPresence.track(self(), config.topic, \"u2\", %{})\n\n    assert %{\n             \"u1\" => %{extra: \"extra\", metas: [%{}]},\n             \"u2\" => %{extra: \"extra\", metas: [%{}]}\n           } = MyPresence.list(config.topic)\n\n    assert MyPresence.untrack(self(), config.topic, \"u1\") == :ok\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{},\n        leaves: %{\"u1\" => %{metas: [%{}]}}\n      }\n    }\n\n    assert %{\"u2\" => %{extra: \"extra\", metas: [%{}]}} = MyPresence.list(config.topic)\n    assert map_size(MyPresence.list(config.topic)) == 1\n  end\n\n  test \"track and untrack with %Socket{}\", %{topic: topic} = config do\n    Phoenix.PubSub.subscribe(config.pubsub, topic)\n    socket = %Phoenix.Socket{topic: topic, channel_pid: self()}\n    MyPresence.track(socket, \"u1\", %{})\n    assert %{\"u1\" => %{metas: [%{}]}} = MyPresence.list(topic)\n    assert MyPresence.untrack(socket, \"u1\") == :ok\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{},\n        leaves: %{\"u1\" => %{metas: [%{}]}}\n      }\n    }\n\n    assert MyPresence.list(topic) == %{}\n  end\n\n  test \"untrack with no tracked presence\", config do\n    assert MyPresence.untrack(self(), config.topic, \"u1\") == :ok\n  end\n\n  test \"update sends join and leave diff\", %{topic: topic} = config do\n    Phoenix.PubSub.subscribe(config.pubsub, topic)\n    MyPresence.track(self(), topic, \"u1\", %{name: \"u1\"})\n    assert %{\"u1\" => %{metas: [%{name: \"u1\"}]}} = MyPresence.list(topic)\n    assert {:ok, _} = MyPresence.update(self(), topic, \"u1\", %{name: \"updated\"})\n\n    assert_receive %Broadcast{\n      topic: ^topic,\n      event: \"presence_diff\",\n      payload: %{\n        joins: %{\"u1\" => %{metas: [%{name: \"updated\"}]}},\n        leaves: %{\"u1\" => %{metas: [%{name: \"u1\"}]}}\n      }\n    }\n\n    assert %{\"u1\" => %{metas: [%{name: \"updated\"}]}} = MyPresence.list(topic)\n  end\n\n  test \"update with no tracked presence\" do\n    assert MyPresence.update(self(), \"topic\", \"u1\", %{}) == {:error, :nopresence}\n  end\n\n  test \"fetchers_pid\" do\n    assert is_list(MyPresence.fetchers_pids())\n  end\n\n  describe \"Presence behaviour when handle_metas is defined\" do\n    test \"raises when missing init/1\" do\n      assert_raise ArgumentError,\n                   ~r|missing Phoenix.PresenceTest.MetasMissingInitPresence.init/1 callback for client state|,\n                   fn ->\n                     MetasMissingInitPresence.init_presence()\n                   end\n    end\n\n    test \"async_merge/2 creates new topic and metas\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n      MetasPresence.track(self(), topic, \"u1\", %{name: \"u1\"})\n\n      assert_receive %{\n        diff: %{\n          joins: %{\"u1\" => %{metas: [%{name: \"u1\"}]}},\n          leaves: %{}\n        },\n        presences: presences\n      }\n\n      assert %{\"u1\" => [%{name: \"u1\", phx_ref: _ref}]} = presences\n    end\n\n    test \"async_merge/2 adds new presences to existing topic\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n      pid1 = spawn(fn -> :timer.sleep(:infinity) end)\n      pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n      MetasPresence.track(pid1, topic, \"u1\", %{name: \"u1\"})\n      MetasPresence.track(pid2, topic, \"u2\", %{name: \"u2\"})\n      MetasPresence.track(self(), topic, \"u3\", %{name: \"u3\"})\n\n      assert_receive %{\n        diff: %{\n          joins: %{\"u3\" => %{metas: [%{name: \"u3\"}]}},\n          leaves: %{}\n        },\n        presences: presences\n      }\n\n      assert %{\n               \"u1\" => [%{name: \"u1\", phx_ref: _u1_ref}],\n               \"u2\" => [%{name: \"u2\", phx_ref: _u2_ref}],\n               \"u3\" => [%{name: \"u3\", phx_ref: _u3_ref}]\n             } = presences\n    end\n\n    test \"async_merge/2 adds new metas to existing presence\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n      pid1 = spawn(fn -> :timer.sleep(:infinity) end)\n      pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n      MetasPresence.track(pid1, topic, \"u1\", %{name: \"u1.1\"})\n      MetasPresence.track(pid2, topic, \"u1\", %{name: \"u1.2\"})\n      MetasPresence.track(self(), topic, \"u1\", %{name: \"u1.3\"})\n\n      assert_receive %{\n        diff: %{\n          joins: %{\"u1\" => %{metas: [%{name: \"u1.3\"}]}},\n          leaves: %{}\n        },\n        presences: presences\n      }\n\n      assert %{\n               \"u1\" => [\n                 %{name: \"u1.1\", phx_ref: _u1_1_ref},\n                 %{name: \"u1.2\", phx_ref: _u1_2_ref},\n                 %{name: \"u1.3\", phx_ref: _u1_3_ref}\n               ]\n             } = presences\n    end\n\n    test \"async_merge/2 removes topic if it doesn't have presences\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n      pid1 = spawn(fn -> :timer.sleep(:infinity) end)\n      pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n\n      MetasPresence.track(pid1, topic, \"u1\", %{name: \"u1\"})\n      MetasPresence.track(pid2, topic, \"u2\", %{name: \"u2\"})\n      MetasPresence.track(self(), topic, \"u3\", %{name: \"u3\"})\n\n      MetasPresence.untrack(pid1, topic, \"u1\")\n      MetasPresence.untrack(pid2, topic, \"u2\")\n      MetasPresence.untrack(self(), topic, \"u3\")\n\n      assert_receive %{\n        diff: %{\n          joins: %{},\n          leaves: %{\"u3\" => %{metas: [%{name: \"u3\"}]}}\n        },\n        presences: presences\n      }\n\n      assert presences == %{}\n    end\n\n    test \"async_merge/2 removes presence info if it only has one meta\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n\n      pid1 = spawn(fn -> :timer.sleep(:infinity) end)\n      pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n      MetasPresence.track(pid1, topic, \"u1\", %{name: \"u1\"})\n      MetasPresence.track(pid2, topic, \"u2\", %{name: \"u2\"})\n      MetasPresence.track(self(), topic, \"u3\", %{name: \"u3\"})\n\n      MetasPresence.untrack(self(), topic, \"u3\")\n\n      assert_receive %{\n        diff: %{\n          joins: %{},\n          leaves: %{\"u3\" => %{metas: [%{name: \"u3\"}]}}\n        },\n        presences: presences\n      }\n\n      assert map_size(presences) == 2\n\n      assert %{\n               \"u1\" => [%{name: \"u1\", phx_ref: _u1_ref}],\n               \"u2\" => [%{name: \"u2\", phx_ref: _u2_ref}]\n             } = presences\n    end\n\n    test \"async_merge/2 removes metas when a presence left\",\n         %{topic: topic} = config do\n      Phoenix.PubSub.subscribe(config.pubsub, topic)\n      pid1 = spawn(fn -> :timer.sleep(:infinity) end)\n      pid2 = spawn(fn -> :timer.sleep(:infinity) end)\n      MetasPresence.track(pid1, topic, \"u1\", %{name: \"u1.1\"})\n      MetasPresence.track(pid2, topic, \"u1\", %{name: \"u1.2\"})\n      MetasPresence.track(self(), topic, \"u1\", %{name: \"u1.3\"})\n\n      MetasPresence.untrack(self(), topic, \"u1\")\n\n      assert_receive %{\n        diff: %{\n          joins: %{},\n          leaves: %{\"u1\" => %{metas: [%{name: \"u1.3\"}]}}\n        },\n        presences: presences\n      }\n\n      assert %{\"u1\" => metas} = presences\n\n      assert length(metas) == 2\n\n      assert [\n               %{name: \"u1.1\", phx_ref: _u1_1_ref},\n               %{name: \"u1.2\", phx_ref: _u1_2_ref}\n             ] = metas\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/console_formatter_test.exs",
    "content": "for module <- [RouteFormatter.PageController, RouteFormatter.ImageController] do\n  defmodule module do\n    def init(opts), do: opts\n    def call(conn, _opts), do: conn\n  end\nend\n\ndefmodule Phoenix.Router.ConsoleFormatterTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Router.ConsoleFormatter\n\n  defmodule RouterTestSingleRoutes do\n    use Phoenix.Router\n\n    get \"/\", RouteFormatter.PageController, :index, as: :page\n    post \"/images\", RouteFormatter.ImageController, :upload, as: :upload_image\n    delete \"/images\", RouteFormatter.ImageController, :delete, as: :remove_image\n  end\n\n  def __sockets__, do: []\n\n  defmodule FormatterEndpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    socket \"/socket\", TestSocket, websocket: true\n  end\n\n  test \"format multiple routes\" do\n    assert draw(RouterTestSingleRoutes) == \"\"\"\n                   page_path  GET     /        RouteFormatter.PageController :index\n           upload_image_path  POST    /images  RouteFormatter.ImageController :upload\n           remove_image_path  DELETE  /images  RouteFormatter.ImageController :delete\n           \"\"\"\n  end\n\n  defmodule RouterTestResources do\n    use Phoenix.Router\n    resources \"/images\", RouteFormatter.ImageController\n  end\n\n  test \"format resource routes\" do\n    assert draw(RouterTestResources) == \"\"\"\n           image_path  GET     /images           RouteFormatter.ImageController :index\n           image_path  GET     /images/:id/edit  RouteFormatter.ImageController :edit\n           image_path  GET     /images/new       RouteFormatter.ImageController :new\n           image_path  GET     /images/:id       RouteFormatter.ImageController :show\n           image_path  POST    /images           RouteFormatter.ImageController :create\n           image_path  PATCH   /images/:id       RouteFormatter.ImageController :update\n                       PUT     /images/:id       RouteFormatter.ImageController :update\n           image_path  DELETE  /images/:id       RouteFormatter.ImageController :delete\n           \"\"\"\n  end\n\n  defmodule RouterTestResource do\n    use Phoenix.Router\n    resources \"/image\", RouteFormatter.ImageController, singleton: true\n    forward \"/admin\", RouteFormatter.PageController, [], as: :admin\n    forward \"/f1\", RouteFormatter.ImageController\n  end\n\n  test \"format single resource routes\" do\n    assert draw(RouterTestResource) == \"\"\"\n           image_path  GET     /image/edit  RouteFormatter.ImageController :edit\n           image_path  GET     /image/new   RouteFormatter.ImageController :new\n           image_path  GET     /image       RouteFormatter.ImageController :show\n           image_path  POST    /image       RouteFormatter.ImageController :create\n           image_path  PATCH   /image       RouteFormatter.ImageController :update\n                       PUT     /image       RouteFormatter.ImageController :update\n           image_path  DELETE  /image       RouteFormatter.ImageController :delete\n                       *       /admin       RouteFormatter.PageController []\n                       *       /f1          RouteFormatter.ImageController []\n           \"\"\"\n  end\n\n  describe \"endpoint sockets\" do\n    test \"format with sockets\" do\n      assert draw(RouterTestSingleRoutes, FormatterEndpoint) == \"\"\"\n                     page_path  GET     /                  RouteFormatter.PageController :index\n             upload_image_path  POST    /images            RouteFormatter.ImageController :upload\n             remove_image_path  DELETE  /images            RouteFormatter.ImageController :delete\n                                WS      /socket/websocket  TestSocket\n                                GET     /socket/longpoll   TestSocket\n                                POST    /socket/longpoll   TestSocket\n             \"\"\"\n    end\n\n    test \"format without sockets\" do\n      assert draw(RouterTestSingleRoutes, __MODULE__) == \"\"\"\n                     page_path  GET     /        RouteFormatter.PageController :index\n             upload_image_path  POST    /images  RouteFormatter.ImageController :upload\n             remove_image_path  DELETE  /images  RouteFormatter.ImageController :delete\n             \"\"\"\n    end\n  end\n\n  defmodule HelpersFalseRouter do\n    use Phoenix.Router, helpers: false\n    resources \"/image\", RouteFormatter.ImageController\n  end\n\n  test \"helpers: false\" do\n    assert draw(HelpersFalseRouter) == \"\"\"\n             GET     /image           RouteFormatter.ImageController :index\n             GET     /image/:id/edit  RouteFormatter.ImageController :edit\n             GET     /image/new       RouteFormatter.ImageController :new\n             GET     /image/:id       RouteFormatter.ImageController :show\n             POST    /image           RouteFormatter.ImageController :create\n             PATCH   /image/:id       RouteFormatter.ImageController :update\n             PUT     /image/:id       RouteFormatter.ImageController :update\n             DELETE  /image/:id       RouteFormatter.ImageController :delete\n           \"\"\"\n\n    assert draw(HelpersFalseRouter, FormatterEndpoint) == \"\"\"\n             GET     /image             RouteFormatter.ImageController :index\n             GET     /image/:id/edit    RouteFormatter.ImageController :edit\n             GET     /image/new         RouteFormatter.ImageController :new\n             GET     /image/:id         RouteFormatter.ImageController :show\n             POST    /image             RouteFormatter.ImageController :create\n             PATCH   /image/:id         RouteFormatter.ImageController :update\n             PUT     /image/:id         RouteFormatter.ImageController :update\n             DELETE  /image/:id         RouteFormatter.ImageController :delete\n             WS      /socket/websocket  TestSocket\n             GET     /socket/longpoll   TestSocket\n             POST    /socket/longpoll   TestSocket\n           \"\"\"\n  end\n\n  defp draw(router, endpoint \\\\ nil) do\n    ConsoleFormatter.format(router, endpoint)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/forward_test.exs",
    "content": "defmodule Phoenix.Router.HealthController do\n  use Phoenix.Controller, formats: []\n  def health(conn, _params), do: text(conn, \"health\")\nend\n\ndefmodule Phoenix.Router.ForwardTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  defmodule Controller do\n    use Phoenix.Controller, formats: []\n    plug :assign_fwd_script\n\n    def index(conn, _params), do: text(conn, \"admin index\")\n    def stats(conn, _params), do: text(conn, \"stats\")\n    def api_users(conn, _params), do: text(conn, \"api users\")\n    def api_root(conn, _params), do: text(conn, \"api root\")\n    defp assign_fwd_script(conn, _), do: assign(conn, :fwd_script, conn.script_name)\n  end\n\n  defmodule ApiRouter do\n    use Phoenix.Router\n\n    get \"/\", Controller, :api_root\n    get \"/users\", Controller, :api_users\n\n    scope \"/health\", Phoenix.Router do\n      forward \"/\", HealthController, :health\n    end\n  end\n\n  defmodule AdminDashboard do\n    use Phoenix.Router\n\n    get \"/\", Controller, :index, as: :page\n    get \"/stats\", Controller, :stats, as: :page\n    forward \"/api-admin\", ApiRouter\n  end\n\n  defmodule InitPlug do\n    def init(_), do: %{non: :literal}\n    def call(conn, %{non: :literal} = opts), do: assign(conn, :opts, opts)\n  end\n\n  defmodule AssignOptsPlug do\n    def init(opts), do: opts\n    def call(conn, opts), do: assign(conn, :opts, opts)\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n\n    scope \"/\" do\n      get \"/stats\", Controller, :stats\n      forward \"/admin\", AdminDashboard\n      forward \"/init\", InitPlug\n      forward \"/assign/opts\", AssignOptsPlug, %{foo: \"bar\"}\n    end\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"forwards path to plug\" do\n    conn = call(Router, :get, \"/admin\")\n    assert conn.script_name == []\n    assert conn.assigns[:fwd_script] == [\"admin\"]\n    assert conn.status == 200\n    assert conn.resp_body == \"admin index\"\n  end\n\n  test \"forwards any request starting with forward path\" do\n    conn = call(Router, :get, \"/admin/stats\")\n    assert conn.script_name == []\n    assert conn.assigns[:fwd_script] == [\"admin\"]\n    assert conn.status == 200\n    assert conn.resp_body == \"stats\"\n  end\n\n  test \"forward with dynamic segments raises\" do\n    router =\n      quote do\n        defmodule BadRouter do\n          use Phoenix.Router\n          forward \"/api/:version\", ApiRouter\n        end\n      end\n\n    assert_raise ArgumentError, ~r{dynamic segment \"/api/:version\" not allowed}, fn ->\n      Code.eval_quoted(router)\n    end\n  end\n\n  test \"accumulates script names\" do\n    conn = call(Router, :get, \"/admin\")\n    assert conn.private[Router] == []\n    assert conn.private[AdminDashboard] == [\"admin\"]\n  end\n\n  test \"helpers cascade script name across forwards based on main router\" do\n    import AdminDashboard.Helpers\n    assert page_path(%Plug.Conn{}, :stats) == \"/stats\"\n\n    conn = call(Router, :get, \"/stats\")\n    assert page_path(conn, :stats) == \"/admin/stats\"\n\n    conn = call(Router, :get, \"/stats\", _params = nil, [\"phx\"])\n    assert page_path(conn, :stats) == \"/phx/admin/stats\"\n\n    conn = call(Router, :get, \"/admin/stats\")\n    assert page_path(conn, :stats) == \"/admin/stats\"\n\n    conn = call(Router, :get, \"/admin/stats\", _params = nil, [\"phx\"])\n    assert page_path(conn, :stats) == \"/phx/admin/stats\"\n  end\n\n  test \"forward can handle plugs with non-literal init returns\" do\n    assert call(Router, :get, \"/init\").assigns.opts == %{non: :literal}\n  end\n\n  test \"forward can handle plugs with custom options\" do\n    assert call(Router, :get, \"/assign/opts\").assigns.opts == %{foo: \"bar\"}\n  end\n\n  test \"forward with scoped alias\" do\n    conn = call(ApiRouter, :get, \"/health\")\n    assert conn.resp_body == \"health\"\n    assert conn.private[ApiRouter] == []\n  end\n\n  test \"forwards raises if using the plug to arguments\" do\n    error_message = ~r/expect a module/\n\n    assert_raise(ArgumentError, error_message, fn ->\n      defmodule BrokenRouter do\n        use Phoenix.Router\n\n        scope \"/\" do\n          forward \"/health\", to: HealthController\n        end\n      end\n    end)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/helpers_test.exs",
    "content": "modules = [\n  PostController,\n  ChatController,\n  UserController,\n  CommentController,\n  FileController,\n  ProductController,\n  Admin.MessageController,\n  SubPlug\n]\n\nfor module <- modules do\n  defmodule module do\n    def init(opts), do: opts\n    def call(conn, _opts), do: conn\n  end\nend\n\ndefmodule Phoenix.Router.HelpersTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  defmodule Router do\n    use Phoenix.Router\n\n    get \"/posts/top\", PostController, :top, as: :top\n    get \"/posts/bottom/:order/:count\", PostController, :bottom, as: :bottom\n    get \"/posts/:id\", PostController, :show\n    get \"/posts/file/*file\", PostController, :file\n    get \"/posts/skip\", PostController, :skip, as: nil\n\n    resources \"/users\", UserController do\n      resources \"/comments\", CommentController do\n        resources \"/files\", FileController\n      end\n    end\n\n    scope \"/\", host: \"users.\" do\n      post \"/host_users/:id/info\", UserController, :create\n    end\n\n    resources \"/files\", FileController\n\n    resources \"/account\", UserController, as: :account, singleton: true do\n      resources \"/page\", PostController, as: :page, only: [:show], singleton: true\n    end\n\n    scope \"/admin\", alias: Admin do\n      resources \"/messages\", MessageController\n    end\n\n    scope \"/admin/new\", alias: Admin, as: \"admin\" do\n      resources \"/messages\", MessageController\n\n      scope \"/unscoped\", as: false do\n        resources \"/messages\", MessageController, as: :my_admin_message\n      end\n    end\n\n    get \"/\", PostController, :root, as: :page\n    get \"/products/:id\", ProductController, :show\n    get \"/products\", ProductController, :show\n    get \"/products/:id/:sort\", ProductController, :show\n    get \"/products/:id/:sort/:page\", ProductController, :show\n\n    get \"/mfa_path\", SubPlug, func: {M, :f, [10]}\n  end\n\n  # Emulate regular endpoint functions\n\n  defmodule Endpoint do\n    def url do\n      \"https://example.com\"\n    end\n\n    def static_url do\n      \"https://static.example.com\"\n    end\n\n    def path(path) do\n      path\n    end\n\n    def static_path(path) do\n      path\n    end\n\n    def static_integrity(_path), do: nil\n  end\n\n  alias Router.Helpers\n\n  test \"defines a __helpers__ function\" do\n    assert Router.__helpers__() == Router.Helpers\n  end\n\n  test \"root helper\" do\n    conn = conn(:get, \"/\") |> put_private(:phoenix_endpoint, Endpoint)\n    assert Helpers.page_path(conn, :root) == \"/\"\n    assert Helpers.page_path(Endpoint, :root) == \"/\"\n  end\n\n  test \"url helper with query strings\" do\n    assert Helpers.post_path(Endpoint, :show, 5, id: 3) == \"/posts/5\"\n    assert Helpers.post_path(Endpoint, :show, 5, foo: \"bar\") == \"/posts/5?foo=bar\"\n    assert Helpers.post_path(Endpoint, :show, 5, foo: :bar) == \"/posts/5?foo=bar\"\n    assert Helpers.post_path(Endpoint, :show, 5, foo: true) == \"/posts/5?foo=true\"\n    assert Helpers.post_path(Endpoint, :show, 5, foo: false) == \"/posts/5?foo=false\"\n    assert Helpers.post_path(Endpoint, :show, 5, foo: nil) == \"/posts/5?foo=\"\n\n    assert Helpers.post_path(Endpoint, :show, 5, foo: ~w(bar baz)) ==\n             \"/posts/5?foo[]=bar&foo[]=baz\"\n\n    assert Helpers.post_path(Endpoint, :show, 5, foo: %{id: 5}) ==\n             \"/posts/5?foo[id]=5\"\n\n    assert Helpers.post_path(Endpoint, :show, 5, foo: %{__struct__: Foo, id: 5}) ==\n             \"/posts/5?foo=5\"\n  end\n\n  test \"url helper with param protocol\" do\n    assert Helpers.post_path(Endpoint, :show, %{__struct__: Foo, id: 5}) == \"/posts/5\"\n\n    assert_raise ArgumentError, fn ->\n      Helpers.post_path(Endpoint, :show, nil)\n    end\n  end\n\n  test \"url helper shows an error if an id is accidentally passed\" do\n    error_suggestion = ~r/bottom_path\\(conn, :bottom, order, count, page: 5, per_page: 10\\)/\n\n    assert_raise ArgumentError, error_suggestion, fn ->\n      Helpers.bottom_path(Endpoint, :bottom, :asc, 8, {:not, :enumerable})\n    end\n\n    error_suggestion = ~r/top_path\\(conn, :top, page: 5, per_page: 10\\)/\n\n    assert_raise ArgumentError, error_suggestion, fn ->\n      Helpers.top_path(Endpoint, :top, \"invalid\")\n    end\n  end\n\n  test \"top-level named route\" do\n    assert Helpers.post_path(Endpoint, :show, 5) == \"/posts/5\"\n    assert Helpers.post_path(Endpoint, :show, 5, []) == \"/posts/5\"\n    assert Helpers.post_path(Endpoint, :show, 5, id: 5) == \"/posts/5\"\n    assert Helpers.post_path(Endpoint, :show, 5, %{\"id\" => 5}) == \"/posts/5\"\n    assert Helpers.post_path(Endpoint, :show, \"foo\") == \"/posts/foo\"\n    assert Helpers.post_path(Endpoint, :show, \"foo bar\") == \"/posts/foo%20bar\"\n\n    assert Helpers.post_path(Endpoint, :file, [\"foo\", \"bar/baz\"]) == \"/posts/file/foo/bar%2Fbaz\"\n    assert Helpers.post_path(Endpoint, :file, [\"foo\", \"bar\"], []) == \"/posts/file/foo/bar\"\n\n    assert Helpers.post_path(Endpoint, :file, [\"foo\", \"bar baz\"], []) ==\n             \"/posts/file/foo/bar%20baz\"\n\n    assert Helpers.top_path(Endpoint, :top) == \"/posts/top\"\n    assert Helpers.top_path(Endpoint, :top, id: 5) == \"/posts/top?id=5\"\n    assert Helpers.top_path(Endpoint, :top, %{\"id\" => 5}) == \"/posts/top?id=5\"\n    assert Helpers.top_path(Endpoint, :top, %{\"id\" => \"foo\"}) == \"/posts/top?id=foo\"\n    assert Helpers.top_path(Endpoint, :top, %{\"id\" => \"foo bar\"}) == \"/posts/top?id=foo+bar\"\n\n    error_message = fn helper, arity ->\n      \"\"\"\n      no action :skip for #{inspect(Helpers)}.#{helper}/#{arity}. The following actions/clauses are supported:\n\n          #{helper}(conn_or_endpoint, :file, file, params \\\\\\\\ [])\n          #{helper}(conn_or_endpoint, :show, id, params \\\\\\\\ [])\n\n      \"\"\"\n      |> String.trim()\n    end\n\n    assert_raise ArgumentError, error_message.(\"post_path\", 3), fn ->\n      Helpers.post_path(Endpoint, :skip, 5)\n    end\n\n    assert_raise ArgumentError, error_message.(\"post_url\", 3), fn ->\n      Helpers.post_url(Endpoint, :skip, 5)\n    end\n\n    assert_raise ArgumentError, error_message.(\"post_path\", 4), fn ->\n      Helpers.post_path(Endpoint, :skip, 5, foo: \"bar\", other: \"param\")\n    end\n\n    assert_raise ArgumentError, error_message.(\"post_url\", 4), fn ->\n      Helpers.post_url(Endpoint, :skip, 5, foo: \"bar\", other: \"param\")\n    end\n  end\n\n  test \"top-level named routes with complex ids\" do\n    assert Helpers.post_path(Endpoint, :show, \"==d--+\") ==\n             \"/posts/%3D%3Dd--%2B\"\n\n    assert Helpers.post_path(Endpoint, :show, \"==d--+\", []) ==\n             \"/posts/%3D%3Dd--%2B\"\n\n    assert Helpers.top_path(Endpoint, :top, id: \"==d--+\") ==\n             \"/posts/top?id=%3D%3Dd--%2B\"\n\n    assert Helpers.post_path(Endpoint, :file, [\"==d--+\", \":O.jpg\"]) ==\n             \"/posts/file/%3D%3Dd--%2B/%3AO.jpg\"\n\n    assert Helpers.post_path(Endpoint, :file, [\"==d--+\", \":O.jpg\"], []) ==\n             \"/posts/file/%3D%3Dd--%2B/%3AO.jpg\"\n\n    assert Helpers.post_path(Endpoint, :file, [\"==d--+\", \":O.jpg\"], xx: \"/=+/\") ==\n             \"/posts/file/%3D%3Dd--%2B/%3AO.jpg?xx=%2F%3D%2B%2F\"\n  end\n\n  test \"resources generates named routes for :index, :edit, :show, :new\" do\n    assert Helpers.user_path(Endpoint, :index, []) == \"/users\"\n    assert Helpers.user_path(Endpoint, :index) == \"/users\"\n    assert Helpers.user_path(Endpoint, :edit, 123, []) == \"/users/123/edit\"\n    assert Helpers.user_path(Endpoint, :edit, 123) == \"/users/123/edit\"\n    assert Helpers.user_path(Endpoint, :show, 123, []) == \"/users/123\"\n    assert Helpers.user_path(Endpoint, :show, 123) == \"/users/123\"\n    assert Helpers.user_path(Endpoint, :new, []) == \"/users/new\"\n    assert Helpers.user_path(Endpoint, :new) == \"/users/new\"\n  end\n\n  test \"resources generated named routes with complex ids\" do\n    assert Helpers.user_path(Endpoint, :edit, \"1a+/31d\", []) == \"/users/1a%2B%2F31d/edit\"\n    assert Helpers.user_path(Endpoint, :edit, \"1a+/31d\") == \"/users/1a%2B%2F31d/edit\"\n    assert Helpers.user_path(Endpoint, :show, \"1a+/31d\", []) == \"/users/1a%2B%2F31d\"\n    assert Helpers.user_path(Endpoint, :show, \"1a+/31d\") == \"/users/1a%2B%2F31d\"\n\n    assert Helpers.message_path(Endpoint, :update, \"8=/=d\", []) == \"/admin/messages/8%3D%2F%3Dd\"\n    assert Helpers.message_path(Endpoint, :update, \"8=/=d\") == \"/admin/messages/8%3D%2F%3Dd\"\n    assert Helpers.message_path(Endpoint, :delete, \"8=/=d\", []) == \"/admin/messages/8%3D%2F%3Dd\"\n    assert Helpers.message_path(Endpoint, :delete, \"8=/=d\") == \"/admin/messages/8%3D%2F%3Dd\"\n\n    assert Helpers.user_path(Endpoint, :show, \"1a+/31d\", dog: \"8d=\") ==\n             \"/users/1a%2B%2F31d?dog=8d%3D\"\n\n    assert Helpers.user_path(Endpoint, :index, cat: \"=8+/&\") == \"/users?cat=%3D8%2B%2F%26\"\n  end\n\n  test \"resources generates named routes for :create, :update, :delete\" do\n    assert Helpers.message_path(Endpoint, :create, []) == \"/admin/messages\"\n    assert Helpers.message_path(Endpoint, :create) == \"/admin/messages\"\n\n    assert Helpers.message_path(Endpoint, :update, 1, []) == \"/admin/messages/1\"\n    assert Helpers.message_path(Endpoint, :update, 1) == \"/admin/messages/1\"\n\n    assert Helpers.message_path(Endpoint, :delete, 1, []) == \"/admin/messages/1\"\n    assert Helpers.message_path(Endpoint, :delete, 1) == \"/admin/messages/1\"\n  end\n\n  test \"1-Level nested resources generates nested named routes for :index, :edit, :show, :new\" do\n    assert Helpers.user_comment_path(Endpoint, :index, 99, []) == \"/users/99/comments\"\n    assert Helpers.user_comment_path(Endpoint, :index, 99) == \"/users/99/comments\"\n    assert Helpers.user_comment_path(Endpoint, :edit, 88, 2, []) == \"/users/88/comments/2/edit\"\n    assert Helpers.user_comment_path(Endpoint, :edit, 88, 2) == \"/users/88/comments/2/edit\"\n    assert Helpers.user_comment_path(Endpoint, :show, 123, 2, []) == \"/users/123/comments/2\"\n    assert Helpers.user_comment_path(Endpoint, :show, 123, 2) == \"/users/123/comments/2\"\n    assert Helpers.user_comment_path(Endpoint, :new, 88, []) == \"/users/88/comments/new\"\n    assert Helpers.user_comment_path(Endpoint, :new, 88) == \"/users/88/comments/new\"\n\n    assert_raise ArgumentError, ~r/no action :skip/, fn ->\n      Helpers.user_comment_file_path(Endpoint, :skip, 123, 456)\n    end\n\n    assert_raise ArgumentError, ~r/no action :skip/, fn ->\n      Helpers.user_comment_file_path(Endpoint, :skip, 123, 456, foo: \"bar\")\n    end\n\n    assert_raise ArgumentError,\n                 ~r/no function clause for Phoenix.Router.HelpersTest.Router.Helpers.user_comment_path\\/3 and action :show/,\n                 fn ->\n                   Helpers.user_comment_path(Endpoint, :show, 123)\n                 end\n  end\n\n  test \"multi-level nested resources generated named routes with complex ids\" do\n    assert Helpers.user_comment_path(Endpoint, :index, \"f4/d+~=\", []) ==\n             \"/users/f4%2Fd%2B~%3D/comments\"\n\n    assert Helpers.user_comment_path(Endpoint, :index, \"f4/d+~=\") ==\n             \"/users/f4%2Fd%2B~%3D/comments\"\n\n    assert Helpers.user_comment_path(Endpoint, :edit, \"f4/d+~=\", \"x-+=/\", []) ==\n             \"/users/f4%2Fd%2B~%3D/comments/x-%2B%3D%2F/edit\"\n\n    assert Helpers.user_comment_path(Endpoint, :edit, \"f4/d+~=\", \"x-+=/\") ==\n             \"/users/f4%2Fd%2B~%3D/comments/x-%2B%3D%2F/edit\"\n\n    assert Helpers.user_comment_path(Endpoint, :show, \"f4/d+~=\", \"x-+=/\", []) ==\n             \"/users/f4%2Fd%2B~%3D/comments/x-%2B%3D%2F\"\n\n    assert Helpers.user_comment_path(Endpoint, :show, \"f4/d+~=\", \"x-+=/\") ==\n             \"/users/f4%2Fd%2B~%3D/comments/x-%2B%3D%2F\"\n\n    assert Helpers.user_comment_path(Endpoint, :new, \"/==/\", []) ==\n             \"/users/%2F%3D%3D%2F/comments/new\"\n\n    assert Helpers.user_comment_path(Endpoint, :new, \"/==/\") ==\n             \"/users/%2F%3D%3D%2F/comments/new\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :show, \"f4/d+~=\", \"/==/\", \"x-+=/\", []) ==\n             \"/users/f4%2Fd%2B~%3D/comments/%2F%3D%3D%2F/files/x-%2B%3D%2F\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :show, \"f4/d+~=\", \"/==/\", \"x-+=/\") ==\n             \"/users/f4%2Fd%2B~%3D/comments/%2F%3D%3D%2F/files/x-%2B%3D%2F\"\n  end\n\n  test \"2-Level nested resources generates nested named routes for :index, :edit, :show, :new\" do\n    assert Helpers.user_comment_file_path(Endpoint, :index, 99, 1, []) ==\n             \"/users/99/comments/1/files\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :index, 99, 1) ==\n             \"/users/99/comments/1/files\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :edit, 88, 1, 2, []) ==\n             \"/users/88/comments/1/files/2/edit\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :edit, 88, 1, 2) ==\n             \"/users/88/comments/1/files/2/edit\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :show, 123, 1, 2, []) ==\n             \"/users/123/comments/1/files/2\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :show, 123, 1, 2) ==\n             \"/users/123/comments/1/files/2\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :new, 88, 1, []) ==\n             \"/users/88/comments/1/files/new\"\n\n    assert Helpers.user_comment_file_path(Endpoint, :new, 88, 1) ==\n             \"/users/88/comments/1/files/new\"\n  end\n\n  test \"resources without block generates named routes for :index, :edit, :show, :new\" do\n    assert Helpers.file_path(Endpoint, :index, []) == \"/files\"\n    assert Helpers.file_path(Endpoint, :index) == \"/files\"\n    assert Helpers.file_path(Endpoint, :edit, 123, []) == \"/files/123/edit\"\n    assert Helpers.file_path(Endpoint, :edit, 123) == \"/files/123/edit\"\n    assert Helpers.file_path(Endpoint, :show, 123, []) == \"/files/123\"\n    assert Helpers.file_path(Endpoint, :show, 123) == \"/files/123\"\n    assert Helpers.file_path(Endpoint, :new, []) == \"/files/new\"\n    assert Helpers.file_path(Endpoint, :new) == \"/files/new\"\n  end\n\n  test \"resource generates named routes for :show, :edit, :new, :update, :delete\" do\n    assert Helpers.account_path(Endpoint, :show, []) == \"/account\"\n    assert Helpers.account_path(Endpoint, :show) == \"/account\"\n    assert Helpers.account_path(Endpoint, :edit, []) == \"/account/edit\"\n    assert Helpers.account_path(Endpoint, :edit) == \"/account/edit\"\n    assert Helpers.account_path(Endpoint, :new, []) == \"/account/new\"\n    assert Helpers.account_path(Endpoint, :new) == \"/account/new\"\n    assert Helpers.account_path(Endpoint, :update, []) == \"/account\"\n    assert Helpers.account_path(Endpoint, :update) == \"/account\"\n    assert Helpers.account_path(Endpoint, :delete, []) == \"/account\"\n    assert Helpers.account_path(Endpoint, :delete) == \"/account\"\n  end\n\n  test \"2-Level nested resource generates nested named routes for :show\" do\n    assert Helpers.account_page_path(Endpoint, :show, []) == \"/account/page\"\n    assert Helpers.account_page_path(Endpoint, :show) == \"/account/page\"\n  end\n\n  test \"scoped route helpers generated named routes with :path, and :alias options\" do\n    assert Helpers.message_path(Endpoint, :index, []) == \"/admin/messages\"\n    assert Helpers.message_path(Endpoint, :index) == \"/admin/messages\"\n    assert Helpers.message_path(Endpoint, :show, 1, []) == \"/admin/messages/1\"\n    assert Helpers.message_path(Endpoint, :show, 1) == \"/admin/messages/1\"\n  end\n\n  test \"scoped route helpers generated named routes with :path, :alias, and :helper options\" do\n    assert Helpers.admin_message_path(Endpoint, :index, []) == \"/admin/new/messages\"\n    assert Helpers.admin_message_path(Endpoint, :index) == \"/admin/new/messages\"\n    assert Helpers.admin_message_path(Endpoint, :show, 1, []) == \"/admin/new/messages/1\"\n    assert Helpers.admin_message_path(Endpoint, :show, 1) == \"/admin/new/messages/1\"\n  end\n\n  test \"scoped route helpers generated unscoped :as options\" do\n    assert Helpers.my_admin_message_path(Endpoint, :index, []) == \"/admin/new/unscoped/messages\"\n    assert Helpers.my_admin_message_path(Endpoint, :index) == \"/admin/new/unscoped/messages\"\n\n    assert Helpers.my_admin_message_path(Endpoint, :show, 1, []) ==\n             \"/admin/new/unscoped/messages/1\"\n\n    assert Helpers.my_admin_message_path(Endpoint, :show, 1) == \"/admin/new/unscoped/messages/1\"\n  end\n\n  test \"can pass an {m, f, a} tuple as a plug argument\" do\n    assert Helpers.sub_plug_path(Endpoint, func: {M, :f, [10]}) == \"/mfa_path\"\n  end\n\n  ## Others\n\n  defp conn_with_endpoint do\n    conn(:get, \"/\") |> put_private(:phoenix_endpoint, Endpoint)\n  end\n\n  defp socket_with_endpoint do\n    %Phoenix.Socket{endpoint: Endpoint}\n  end\n\n  defp uri do\n    %URI{scheme: \"https\", host: \"example.com\", port: 443}\n  end\n\n  test \"helpers module generates a static_path helper\" do\n    assert Helpers.static_path(Endpoint, \"/images/foo.png\") == \"/images/foo.png\"\n    assert Helpers.static_path(conn_with_endpoint(), \"/images/foo.png\") == \"/images/foo.png\"\n    assert Helpers.static_path(socket_with_endpoint(), \"/images/foo.png\") == \"/images/foo.png\"\n  end\n\n  test \"helpers module generates a static_url helper\" do\n    url = \"https://static.example.com/images/foo.png\"\n    assert Helpers.static_url(Endpoint, \"/images/foo.png\") == url\n    assert Helpers.static_url(conn_with_endpoint(), \"/images/foo.png\") == url\n    assert Helpers.static_url(socket_with_endpoint(), \"/images/foo.png\") == url\n  end\n\n  test \"helpers module generates a url helper\" do\n    assert Helpers.url(Endpoint) == \"https://example.com\"\n    assert Helpers.url(conn_with_endpoint()) == \"https://example.com\"\n    assert Helpers.url(socket_with_endpoint()) == \"https://example.com\"\n    assert Helpers.url(uri()) == \"https://example.com\"\n  end\n\n  test \"helpers module generates a path helper\" do\n    assert Helpers.path(Endpoint, \"/\") == \"/\"\n    assert Helpers.path(conn_with_endpoint(), \"/\") == \"/\"\n    assert Helpers.path(socket_with_endpoint(), \"/\") == \"/\"\n    assert Helpers.path(uri(), \"/\") == \"/\"\n  end\n\n  test \"helpers module generates a static_integrity helper\" do\n    assert is_nil(Helpers.static_integrity(Endpoint, \"/images/foo.png\"))\n    assert is_nil(Helpers.static_integrity(conn_with_endpoint(), \"/images/foo.png\"))\n    assert is_nil(Helpers.static_integrity(socket_with_endpoint(), \"/images/foo.png\"))\n  end\n\n  test \"helpers module generates named routes url helpers\" do\n    url = \"https://example.com/admin/new/messages/1\"\n    assert Helpers.admin_message_url(Endpoint, :show, 1) == url\n    assert Helpers.admin_message_url(Endpoint, :show, 1, []) == url\n    assert Helpers.admin_message_url(conn_with_endpoint(), :show, 1) == url\n    assert Helpers.admin_message_url(conn_with_endpoint(), :show, 1, []) == url\n    assert Helpers.admin_message_url(socket_with_endpoint(), :show, 1) == url\n    assert Helpers.admin_message_url(socket_with_endpoint(), :show, 1, []) == url\n    assert Helpers.admin_message_url(uri(), :show, 1) == url\n    assert Helpers.admin_message_url(uri(), :show, 1, []) == url\n  end\n\n  test \"helpers properly encode named and query string params\" do\n    assert Router.Helpers.post_path(Endpoint, :show, \"my path\", foo: \"my param\") ==\n             \"/posts/my%20path?foo=my+param\"\n  end\n\n  test \"duplicate helpers with unique arities\" do\n    assert Helpers.product_path(Endpoint, :show) == \"/products\"\n    assert Helpers.product_path(Endpoint, :show, foo: \"bar\") == \"/products?foo=bar\"\n    assert Helpers.product_path(Endpoint, :show, 123) == \"/products/123\"\n    assert Helpers.product_path(Endpoint, :show, 123, foo: \"bar\") == \"/products/123?foo=bar\"\n    assert Helpers.product_path(Endpoint, :show, 123, \"asc\") == \"/products/123/asc\"\n\n    assert Helpers.product_path(Endpoint, :show, 123, \"asc\", foo: \"bar\") ==\n             \"/products/123/asc?foo=bar\"\n\n    assert Helpers.product_path(Endpoint, :show, 123, \"asc\", 1) == \"/products/123/asc/1\"\n\n    assert Helpers.product_path(Endpoint, :show, 123, \"asc\", 1, foo: \"bar\") ==\n             \"/products/123/asc/1?foo=bar\"\n  end\n\n  ## Script name\n\n  defmodule ScriptName do\n    def url do\n      \"https://example.com\"\n    end\n\n    def static_url do\n      \"https://static.example.com\"\n    end\n\n    def path(path) do\n      \"/api\" <> path\n    end\n\n    def static_path(path) do\n      \"/api\" <> path\n    end\n  end\n\n  def conn_with_script_name(script_name \\\\ ~w(api)) do\n    conn =\n      conn(:get, \"/\")\n      |> put_private(:phoenix_endpoint, ScriptName)\n\n    put_in(conn.script_name, script_name)\n  end\n\n  defp uri_with_script_name do\n    %URI{scheme: \"https\", host: \"example.com\", port: 123, path: \"/api\"}\n  end\n\n  test \"paths use script name\" do\n    assert Helpers.page_path(ScriptName, :root) == \"/api/\"\n    assert Helpers.page_path(conn_with_script_name(), :root) == \"/api/\"\n    assert Helpers.page_path(uri_with_script_name(), :root) == \"/api/\"\n    assert Helpers.post_path(ScriptName, :show, 5) == \"/api/posts/5\"\n    assert Helpers.post_path(conn_with_script_name(), :show, 5) == \"/api/posts/5\"\n    assert Helpers.post_path(uri_with_script_name(), :show, 5) == \"/api/posts/5\"\n  end\n\n  test \"urls use script name\" do\n    assert Helpers.page_url(ScriptName, :root) ==\n             \"https://example.com/api/\"\n\n    assert Helpers.page_url(conn_with_script_name(~w(foo)), :root) ==\n             \"https://example.com/foo/\"\n\n    assert Helpers.page_url(uri_with_script_name(), :root) ==\n             \"https://example.com:123/api/\"\n\n    assert Helpers.post_url(ScriptName, :show, 5) ==\n             \"https://example.com/api/posts/5\"\n\n    assert Helpers.post_url(conn_with_script_name(), :show, 5) ==\n             \"https://example.com/api/posts/5\"\n\n    assert Helpers.post_url(conn_with_script_name(~w(foo)), :show, 5) ==\n             \"https://example.com/foo/posts/5\"\n\n    assert Helpers.post_url(uri_with_script_name(), :show, 5) ==\n             \"https://example.com:123/api/posts/5\"\n  end\n\n  test \"static use endpoint script name only\" do\n    assert Helpers.static_path(conn_with_script_name(~w(foo)), \"/images/foo.png\") ==\n             \"/api/images/foo.png\"\n\n    assert Helpers.static_url(conn_with_script_name(~w(foo)), \"/images/foo.png\") ==\n             \"https://static.example.com/api/images/foo.png\"\n  end\n\n  ## Dynamics\n\n  test \"phoenix_router_url with string takes precedence over endpoint\" do\n    url = \"https://phoenixframework.org\"\n    conn = Phoenix.Controller.put_router_url(conn_with_endpoint(), url)\n    assert Helpers.url(conn) == url\n    assert Helpers.admin_message_url(conn, :show, 1) == url <> \"/admin/new/messages/1\"\n  end\n\n  test \"phoenix_router_url with URI takes precedence over endpoint\" do\n    uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123, path: \"/path\"}\n    conn = Phoenix.Controller.put_router_url(conn_with_endpoint(), uri)\n\n    assert Helpers.url(conn) == \"https://phoenixframework.org:123/path\"\n\n    assert Helpers.admin_message_url(conn, :show, 1) ==\n             \"https://phoenixframework.org:123/path/admin/new/messages/1\"\n  end\n\n  test \"phoenix_static_url with string takes precedence over endpoint\" do\n    url = \"https://phoenixframework.org\"\n    conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), url)\n    assert Helpers.static_url(conn, \"/images/foo.png\") == url <> \"/images/foo.png\"\n\n    conn = Phoenix.Controller.put_static_url(conn_with_script_name(), url)\n    assert Helpers.static_url(conn, \"/images/foo.png\") == url <> \"/images/foo.png\"\n  end\n\n  test \"phoenix_static_url set to string with path results in static url with that path\" do\n    url = \"https://phoenixframework.org/path\"\n    conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), url)\n    assert Helpers.static_url(conn, \"/images/foo.png\") == url <> \"/images/foo.png\"\n\n    conn = Phoenix.Controller.put_static_url(conn_with_script_name(), url)\n    assert Helpers.static_url(conn, \"/images/foo.png\") == url <> \"/images/foo.png\"\n  end\n\n  test \"phoenix_static_url with URI takes precedence over endpoint\" do\n    uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123}\n\n    conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), uri)\n\n    assert Helpers.static_url(conn, \"/images/foo.png\") ==\n             \"https://phoenixframework.org:123/images/foo.png\"\n\n    conn = Phoenix.Controller.put_static_url(conn_with_script_name(), uri)\n\n    assert Helpers.static_url(conn, \"/images/foo.png\") ==\n             \"https://phoenixframework.org:123/images/foo.png\"\n  end\n\n  test \"phoenix_static_url set to URI with path results in static url with that path\" do\n    uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123, path: \"/path\"}\n\n    conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), uri)\n\n    assert Helpers.static_url(conn, \"/images/foo.png\") ==\n             \"https://phoenixframework.org:123/path/images/foo.png\"\n\n    conn = Phoenix.Controller.put_static_url(conn_with_script_name(), uri)\n\n    assert Helpers.static_url(conn, \"/images/foo.png\") ==\n             \"https://phoenixframework.org:123/path/images/foo.png\"\n  end\n\n  describe \"helpers: false\" do\n    defmodule NoHelpersRouter do\n      use Phoenix.Router, helpers: false\n\n      get \"/\", PostController, :home\n    end\n\n    test \"__helpers__ return nil\" do\n      assert NoHelpersRouter.__helpers__() == nil\n    end\n\n    test \"test not generate Helpers module\" do\n      refute Code.ensure_loaded?(NoHelpersRouter.Helpers)\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/pipeline_test.exs",
    "content": "defmodule Phoenix.Router.PipelineTest.SampleController do\n  use Phoenix.Controller, formats: []\n  def index(conn, _params), do: text(conn, \"index\")\n  def crash(_conn, _params), do: raise(\"crash!\")\n  def noop_plug(conn, _opts), do: conn\nend\n\nalias Phoenix.Router.PipelineTest.SampleController\n\ndefmodule Phoenix.Router.PipelineTest.Router do\n  use Phoenix.Router\n\n  # This should work even if the import comes\n  # after the Phoenix.Router definition\n  import SampleController, only: [noop_plug: 2]\n\n  pipeline :browser do\n    plug :put_assign, \"browser\"\n  end\n\n  pipeline :api do\n    plug :put_assign, \"api\"\n  end\n\n  pipeline :params do\n    plug :put_params\n  end\n\n  pipeline :halt do\n    plug :stop\n  end\n\n  pipeline :halt_again do\n    plug :stop\n  end\n\n  get \"/root\", SampleController, :index\n  put \"/root/:id\", SampleController, :index\n  get \"/route_that_crashes\", SampleController, :crash\n\n  scope \"/browser\" do\n    pipe_through :browser\n    get \"/root\", SampleController, :index\n\n    scope \"/api\" do\n      pipe_through :api\n      get \"/root\", SampleController, :index\n    end\n\n    scope \"/:id\" do\n      pipe_through :params\n      get \"/\", SampleController, :index\n    end\n  end\n\n  scope \"/browser-api\" do\n    pipe_through [:browser, :api]\n    get \"/root\", SampleController, :index\n  end\n\n  scope \"/stop\" do\n    pipe_through [:noop_plug, :halt, :halt_again]\n    get \"/\", SampleController, :index\n  end\n\n  defp stop(conn, _) do\n    conn |> send_resp(200, \"stop\") |> halt\n  end\n\n  defp put_assign(conn, value) do\n    assign(conn, :stack, [value | conn.assigns[:stack] || []])\n  end\n\n  defp put_params(conn, _) do\n    assign(conn, :params, conn.params)\n  end\nend\n\nalias Phoenix.Router.PipelineTest.Router\n\ndefmodule Phoenix.Router.PipelineTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"does not invoke pipelines at root\" do\n    conn = call(Router, :get, \"/root\")\n    assert conn.assigns[:stack] == nil\n  end\n\n  test \"invokes pipelines per scope\" do\n    conn = call(Router, :get, \"/browser/root\")\n    assert conn.assigns[:stack] == [\"browser\"]\n  end\n\n  test \"invokes pipelines in a nested scope\" do\n    conn = call(Router, :get, \"/browser/api/root\")\n    assert conn.assigns[:stack] == [\"api\", \"browser\"]\n  end\n\n  test \"invokes multiple pipelines\" do\n    conn = call(Router, :get, \"/browser-api/root\")\n    assert conn.assigns[:stack] == [\"api\", \"browser\"]\n  end\n\n  test \"halts on pipeline multiple pipelines\" do\n    conn = call(Router, :get, \"/stop\")\n    assert conn.halted\n    assert conn.status == 200\n    assert conn.resp_body == \"stop\"\n  end\n\n  test \"wraps failures on call\" do\n    assert_raise Plug.Conn.WrapperError, fn ->\n      call(Router, :get, \"/route_that_crashes\")\n    end\n  end\n\n  test \"merge parameters before invoking pipelines\" do\n    conn = call(Router, :get, \"/browser/hello\")\n    assert conn.assigns[:params] == %{\"id\" => \"hello\"}\n  end\n\n  test \"duplicate pipe_through's raises\" do\n    assert_raise ArgumentError, ~r{duplicate pipe_through for :browser}, fn ->\n      defmodule DupPipeThroughRouter do\n        use Phoenix.Router, otp_app: :phoenix\n\n        pipeline :browser do\n        end\n\n        scope \"/\" do\n          pipe_through [:browser, :auth]\n          pipe_through [:browser]\n        end\n      end\n    end\n\n    assert_raise ArgumentError, ~r{duplicate pipe_through for :browser}, fn ->\n      defmodule DupScopedPipeThroughRouter do\n        use Phoenix.Router, otp_app: :phoenix\n\n        pipeline :browser do\n        end\n\n        scope \"/\" do\n          pipe_through [:browser]\n\n          scope \"/nested\" do\n            pipe_through [:browser]\n          end\n        end\n      end\n    end\n  end\n\n  test \"pipeline raises on conflict\" do\n    assert_raise ArgumentError, ~r{there is an import from Kernel with the same name}, fn ->\n      defmodule ConflictingPipeline do\n        use Phoenix.Router, otp_app: :phoenix\n\n        pipeline :raise do\n          plug Plug.Head\n        end\n\n        scope \"/\" do\n          pipe_through [:raise]\n          get \"/\", UnknownController, :index\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/resource_test.exs",
    "content": "defmodule Phoenix.Router.ResourceTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  defmodule Api.GenericController do\n    use Phoenix.Controller, formats: []\n    def show(conn, _params), do: text(conn, \"show\")\n    def new(conn, _params), do: text(conn, \"new\")\n    def edit(conn, _params), do: text(conn, \"edit\")\n    def create(conn, _params), do: text(conn, \"create\")\n    def update(conn, _params), do: text(conn, \"update\")\n    def delete(conn, _params), do: text(conn, \"delete\")\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n\n    resources \"/account\", Api.GenericController, alias: Api, singleton: true do\n      resources \"/comments\", GenericController\n      resources \"/session\", GenericController, except: [:delete], singleton: true\n    end\n\n    resources \"/session\", Api.GenericController, only: [:show], singleton: true\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"toplevel route matches new action\" do\n    conn = call(Router, :get, \"/account/new\")\n    assert conn.status == 200\n    assert conn.resp_body == \"new\"\n  end\n\n  test \"toplevel route matches show action\" do\n    conn = call(Router, :get, \"/account\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show\"\n  end\n\n  test \"toplevel route matches edit action\" do\n    conn = call(Router, :get, \"/account/edit\")\n    assert conn.status == 200\n    assert conn.resp_body == \"edit\"\n  end\n\n  test \"toplevel route matches create action\" do\n    conn = call(Router, :post, \"/account\")\n    assert conn.status == 200\n    assert conn.resp_body == \"create\"\n  end\n\n  test \"toplevel route matches update action with both PUT and PATCH\" do\n    for method <- [:put, :patch] do\n      conn = call(Router, method, \"/account\")\n      assert conn.status == 200\n      assert conn.resp_body == \"update\"\n\n      conn = call(Router, method, \"/account\")\n      assert conn.status == 200\n      assert conn.resp_body == \"update\"\n    end\n  end\n\n  test \"toplevel route matches delete action\" do\n    conn = call(Router, :delete, \"/account\")\n    assert conn.status == 200\n    assert conn.resp_body == \"delete\"\n  end\n\n  test \"1-Level nested route matches\" do\n    conn = call(Router, :get, \"/account/comments/2\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show\"\n    assert conn.params[\"id\"] == \"2\"\n  end\n\n  test \"nested prefix context reverts back to previous scope after expansion\" do\n    conn = call(Router, :get, \"/account/session\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show\"\n\n    conn = call(Router, :get, \"/session\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show\"\n  end\n\n  test \"limit resource by passing :except option\" do\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :delete, \"/account/session\")\n    end\n\n    conn = call(Router, :get, \"/account/session/new\")\n    assert conn.status == 200\n  end\n\n  test \"limit resource by passing :only option\" do\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :patch, \"/session/new\")\n    end\n\n    conn = call(Router, :get, \"/session\")\n    assert conn.status == 200\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/resources_test.exs",
    "content": "defmodule Phoenix.Router.ResourcesTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  defmodule UserController do\n    use Phoenix.Controller, formats: []\n    def show(conn, _params), do: text(conn, \"show users\")\n    def index(conn, _params), do: text(conn, \"index users\")\n    def new(conn, _params), do: text(conn, \"new users\")\n    def edit(conn, _params), do: text(conn, \"edit users\")\n    def create(conn, _params), do: text(conn, \"create users\")\n    def update(conn, _params), do: text(conn, \"update users\")\n    def delete(conn, _params), do: text(conn, \"delete users\")\n  end\n\n  defmodule Api.FileController do\n    use Phoenix.Controller, formats: []\n    def show(conn, _params), do: text(conn, \"show files\")\n    def index(conn, _params), do: text(conn, \"index files\")\n    def new(conn, _params), do: text(conn, \"new files\")\n  end\n\n  defmodule Api.CommentController do\n    use Phoenix.Controller, formats: []\n    def show(conn, _params), do: text(conn, \"show comments\")\n    def index(conn, _params), do: text(conn, \"index comments\")\n    def new(conn, _params), do: text(conn, \"new comments\")\n    def create(conn, _params), do: text(conn, \"create comments\")\n    def update(conn, _params), do: text(conn, \"update comments\")\n    def delete(conn, _params), do: text(conn, \"delete comments\")\n    def special(conn, _params), do: text(conn, \"special comments\")\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n\n    resources \"/users\", UserController, alias: Api do\n      resources \"/comments\", CommentController do\n        get \"/special\", CommentController, :special\n      end\n\n      resources \"/files\", FileController, except: [:delete]\n    end\n\n    resources \"/members\", UserController, only: [:show, :new, :delete]\n\n    resources \"/files\", Api.FileController, only: [:index]\n\n    resources \"/admin\", UserController, param: \"slug\", name: \"admin\", only: [:show], alias: Api do\n      resources \"/comments\", CommentController, param: \"key\", name: \"post\", except: [:delete]\n      resources \"/files\", FileController, only: [:show, :index, :new]\n    end\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"toplevel route matches new action\" do\n    conn = call(Router, :get, \"/users/new\")\n    assert conn.status == 200\n    assert conn.resp_body == \"new users\"\n  end\n\n  test \"toplevel route matches index action\" do\n    conn = call(Router, :get, \"/users\")\n    assert conn.status == 200\n    assert conn.resp_body == \"index users\"\n  end\n\n  test \"toplevel route matches show action with named param\" do\n    conn = call(Router, :get, \"/users/123\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show users\"\n    assert conn.params[\"id\"] == \"123\"\n  end\n\n  test \"toplevel route matches edit action with named param\" do\n    conn = call(Router, :get, \"/users/123/edit\")\n    assert conn.status == 200\n    assert conn.resp_body == \"edit users\"\n    assert conn.params[\"id\"] == \"123\"\n  end\n\n  test \"toplevel route matches create action\" do\n    conn = call(Router, :post, \"/users\")\n    assert conn.status == 200\n    assert conn.resp_body == \"create users\"\n  end\n\n  test \"toplevel route matches update action with both PUT and PATCH\" do\n    for method <- [:put, :patch] do\n      conn = call(Router, method, \"/users/1\")\n      assert conn.status == 200\n      assert conn.resp_body == \"update users\"\n      assert conn.params[\"id\"] == \"1\"\n\n      conn = call(Router, method, \"/users/2\")\n      assert conn.status == 200\n      assert conn.resp_body == \"update users\"\n      assert conn.params[\"id\"] == \"2\"\n    end\n  end\n\n  test \"toplevel route matches delete action\" do\n    conn = call(Router, :delete, \"/users/2\")\n    assert conn.status == 200\n    assert conn.resp_body == \"delete users\"\n    assert conn.params[\"id\"] == \"2\"\n  end\n\n  test \"1-Level nested route matches with named param prefix on show\" do\n    conn = call(Router, :get, \"/users/1/comments/2\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show comments\"\n    assert conn.params[\"id\"] == \"2\"\n    assert conn.params[\"user_id\"] == \"1\"\n  end\n\n  test \"1-Level nested route matches with named param prefix on index\" do\n    conn = call(Router, :get, \"/users/1/comments\")\n    assert conn.status == 200\n    assert conn.resp_body == \"index comments\"\n    assert conn.params[\"user_id\"] == \"1\"\n  end\n\n  test \"1-Level nested route matches with named param prefix on create\" do\n    conn = call(Router, :post, \"/users/1/comments\")\n    assert conn.status == 200\n    assert conn.resp_body == \"create comments\"\n    assert conn.params[\"user_id\"] == \"1\"\n  end\n\n  test \"1-Level nested route matches with named param prefix on update\" do\n    conn = call(Router, :patch, \"/users/1/comments/123\")\n    assert conn.status == 200\n    assert conn.resp_body == \"update comments\"\n    assert conn.params[\"user_id\"] == \"1\"\n    assert conn.params[\"id\"] == \"123\"\n  end\n\n  test \"1-Level nested route matches with named param prefix on delete\" do\n    conn = call(Router, :delete, \"/users/1/comments/123\")\n    assert conn.status == 200\n    assert conn.resp_body == \"delete comments\"\n    assert conn.params[\"user_id\"] == \"1\"\n    assert conn.params[\"id\"] == \"123\"\n  end\n\n  test \"2-Level nested route with get matches\" do\n    conn = call(Router, :get, \"/users/1/comments/123/special\")\n    assert conn.status == 200\n    assert conn.resp_body == \"special comments\"\n    assert conn.params[\"user_id\"] == \"1\"\n    assert conn.params[\"comment_id\"] == \"123\"\n  end\n\n  test \"nested prefix context reverts back to previous scope after expansion\" do\n    conn = call(Router, :get, \"/users/8/files/10\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show files\"\n    assert conn.params[\"user_id\"] == \"8\"\n    assert conn.params[\"id\"] == \"10\"\n\n    conn = call(Router, :get, \"/files\")\n    assert conn.status == 200\n    assert conn.resp_body == \"index files\"\n  end\n\n  test \"nested options limit resource by passing :except option\" do\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :delete, \"/users/1/files/2\")\n    end\n\n    conn = call(Router, :get, \"/users/1/files/new\")\n    assert conn.status == 200\n  end\n\n  test \"nested options limit resource by passing :only option\" do\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :patch, \"/admin/1/files/2\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :post, \"/admin/1/files\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :delete, \"/admin/1/files/1\")\n    end\n\n    conn = call(Router, :get, \"/admin/1/files/\")\n    assert conn.status == 200\n    conn = call(Router, :get, \"/admin/1/files/1\")\n    assert conn.status == 200\n    conn = call(Router, :get, \"/admin/1/files/new\")\n    assert conn.status == 200\n  end\n\n  test \"resource limiting options should work for nested resources\" do\n    conn = call(Router, :get, \"/admin/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show users\"\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :get, \"/admin/\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :patch, \"/admin/1\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :post, \"/admin\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :delete, \"/admin/1\")\n    end\n\n    conn = call(Router, :get, \"/admin/1/comments\")\n    assert conn.status == 200\n    assert conn.resp_body == \"index comments\"\n\n    conn = call(Router, :get, \"/admin/1/comments/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show comments\"\n\n    conn = call(Router, :patch, \"/admin/1/comments/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"update comments\"\n\n    conn = call(Router, :post, \"/admin/1/comments\")\n    assert conn.status == 200\n    assert conn.resp_body == \"create comments\"\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :delete, \"/scoped_files/1\")\n    end\n  end\n\n  test \"param option allows default singularized _id param to be overridden\" do\n    conn = call(Router, :get, \"/admin/foo\")\n    assert conn.status == 200\n    assert conn.params[\"slug\"] == \"foo\"\n    assert conn.resp_body == \"show users\"\n\n    assert Router.Helpers.admin_path(conn, :show, \"foo\") ==\n             \"/admin/foo\"\n\n    conn = call(Router, :get, \"/admin/bar/comments/the_key\")\n    assert conn.status == 200\n    assert conn.params[\"admin_slug\"] == \"bar\"\n    assert conn.params[\"key\"] == \"the_key\"\n    assert conn.resp_body == \"show comments\"\n\n    assert Router.Helpers.admin_post_path(conn, :show, \"bar\", \"the_key\") ==\n             \"/admin/bar/comments/the_key\"\n  end\n\n  test \"resources with :only sets proper match order for :show and :new\" do\n    conn = call(Router, :get, \"/members/new\")\n    assert conn.status == 200\n    assert conn.resp_body == \"new users\"\n\n    conn = call(Router, :get, \"/members/2\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show users\"\n    assert conn.params[\"id\"] == \"2\"\n  end\n\n  test \"singleton resources declaring an :index route throws an ArgumentError\" do\n    assert_raise ArgumentError,\n                 ~r/supported singleton actions: \\[:edit, :new, :show, :create, :update, :delete\\]/,\n                 fn ->\n                   defmodule SingletonRouter.Router do\n                     use Phoenix.Router\n                     resources \"/\", UserController, singleton: true, only: [:index]\n                   end\n                 end\n  end\n\n  test \"resources validates :only actions\" do\n    assert_raise ArgumentError,\n                 ~r/supported actions: \\[:index, :edit, :new, :show, :create, :update, :delete\\]/,\n                 fn ->\n                   defmodule SingletonRouter.Router do\n                     use Phoenix.Router\n                     resources \"/\", UserController, only: [:bad_index]\n                   end\n                 end\n  end\n\n  test \"resources validates :except actions\" do\n    assert_raise ArgumentError,\n                 ~r/supported actions: \\[:index, :edit, :new, :show, :create, :update, :delete\\]/,\n                 fn ->\n                   defmodule SingletonRouter.Router do\n                     use Phoenix.Router\n                     resources \"/\", UserController, except: [:bad_index]\n                   end\n                 end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/route_test.exs",
    "content": "defmodule Phoenix.Router.RouteTest do\n  use ExUnit.Case, async: true\n\n  import Phoenix.Router.Route\n\n  def init(opts), do: opts\n\n  defmodule AdminRouter do\n    def init(opts), do: opts\n    def call(conn, _), do: Plug.Conn.assign(conn, :fwd_conn, conn)\n  end\n\n  test \"builds a route based on verb, path, plug, plug options and helper\" do\n    route =\n      build(\n        1,\n        :match,\n        :get,\n        \"/foo/:bar\",\n        [],\n        Hello,\n        :world,\n        \"hello_world\",\n        [:foo, :bar],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{log: :debug},\n        true,\n        true\n      )\n\n    assert route.kind == :match\n    assert route.verb == :get\n    assert route.path == \"/foo/:bar\"\n    assert route.hosts == []\n    assert route.line == 1\n    assert route.plug == Hello\n    assert route.plug_opts == :world\n    assert route.helper == \"hello_world\"\n    assert route.pipe_through == [:foo, :bar]\n    assert route.private == %{foo: \"bar\"}\n    assert route.assigns == %{bar: \"baz\"}\n    assert route.metadata == %{log: :debug}\n    assert route.trailing_slash? == true\n  end\n\n  test \"builds expressions based on the route\" do\n    exprs =\n      build(\n        1,\n        :match,\n        :get,\n        \"/foo/:bar\",\n        [],\n        Hello,\n        :world,\n        \"hello_world\",\n        [],\n        %{},\n        %{},\n        %{},\n        false,\n        true\n      )\n      |> exprs()\n\n    assert exprs.verb_match == \"GET\"\n    assert exprs.path == [\"foo\", {:arg0, [], Phoenix.Router.Route}]\n    assert exprs.binding == [{\"bar\", {:arg0, [], Phoenix.Router.Route}}]\n    assert Macro.to_string(exprs.hosts) == \"[_]\"\n\n    exprs =\n      build(\n        1,\n        :match,\n        :get,\n        \"/\",\n        [\"foo.\"],\n        Hello,\n        :world,\n        \"hello_world\",\n        [:foo, :bar],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{},\n        false,\n        true\n      )\n      |> exprs()\n\n    assert Macro.to_string(exprs.hosts) == \"[\\\"foo.\\\" <> _]\"\n\n    exprs =\n      build(\n        1,\n        :match,\n        :get,\n        \"/\",\n        [\"foo.\", \"example.com\"],\n        Hello,\n        :world,\n        \"hello_world\",\n        [:foo, :bar],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{},\n        false,\n        true\n      )\n      |> exprs()\n\n    assert Macro.to_string(exprs.hosts) == \"[\\\"foo.\\\" <> _, \\\"example.com\\\"]\"\n\n    exprs =\n      build(\n        1,\n        :match,\n        :get,\n        \"/\",\n        [\"foo.com\"],\n        Hello,\n        :world,\n        \"hello_world\",\n        [],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{},\n        false,\n        true\n      )\n      |> exprs()\n\n    assert Macro.to_string(exprs.hosts) == \"[\\\"foo.com\\\"]\"\n  end\n\n  test \"builds a catch-all verb_match for match routes\" do\n    route =\n      build(\n        1,\n        :match,\n        :*,\n        \"/foo/:bar\",\n        [],\n        __MODULE__,\n        :world,\n        \"hello_world\",\n        [:foo, :bar],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{},\n        false,\n        true\n      )\n\n    assert route.verb == :*\n    assert route.kind == :match\n    assert exprs(route).verb_match == {:_verb, [], nil}\n  end\n\n  test \"builds a catch-all verb_match for forwarded routes\" do\n    route =\n      build(\n        1,\n        :forward,\n        :*,\n        \"/foo\",\n        [],\n        __MODULE__,\n        :world,\n        \"hello_world\",\n        [:foo],\n        %{foo: \"bar\"},\n        %{bar: \"baz\"},\n        %{forward: ~w(foo)},\n        false,\n        true\n      )\n\n    assert route.verb == :*\n    assert route.kind == :forward\n    assert exprs(route).verb_match == {:_verb, [], nil}\n  end\n\n  test \"as a plug, it forwards and sets path_info and script_name for target, then resumes\" do\n    conn = %Plug.Conn{path_info: [\"admin\", \"stats\"], script_name: [\"phoenix\"]}\n    conn = call(conn, {[\"admin\"], AdminRouter, []})\n    fwd_conn = conn.assigns[:fwd_conn]\n    assert fwd_conn.path_info == [\"stats\"]\n    assert fwd_conn.script_name == [\"phoenix\", \"admin\"]\n    assert conn.path_info == [\"admin\", \"stats\"]\n    assert conn.script_name == [\"phoenix\"]\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/routing_test.exs",
    "content": "defmodule Phoenix.Router.RoutingTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  import ExUnit.CaptureLog\n\n  defmodule SomePlug do\n    def init(opts), do: opts\n    def call(conn, _opts), do: conn\n  end\n\n  defmodule UserController do\n    use Phoenix.Controller, formats: []\n    def index(conn, _params), do: text(conn, \"users index\")\n    def show(conn, _params), do: text(conn, \"users show\")\n    def top(conn, _params), do: text(conn, \"users top\")\n    def options(conn, _params), do: text(conn, \"users options\")\n    def connect(conn, _params), do: text(conn, \"users connect\")\n    def trace(conn, _params), do: text(conn, \"users trace\")\n    def not_found(conn, _params), do: text(put_status(conn, :not_found), \"not found\")\n    def image(conn, _params), do: text(conn, conn.params[\"path\"] || \"show files\")\n    def move(conn, _params), do: text(conn, \"users move\")\n    def any(conn, _params), do: text(conn, \"users any\")\n    def raise(_conn, _params), do: raise(\"boom\")\n    def exit(_conn, _params), do: exit(:boom)\n\n    def halt(conn, _params) do\n      conn\n      |> send_resp(401, \"Unauthorized\")\n      |> halt()\n    end\n  end\n\n  defmodule LogLevel do\n    def log_level(%{params: %{\"level\" => \"info\"}}), do: :info\n    def log_level(%{params: %{\"level\" => \"error\"}}), do: :error\n    def log_level(_), do: :debug\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n\n    get \"/\", UserController, :index, as: :users\n    get \"/users/top\", UserController, :top, as: :top\n    get \"/users/:id\", UserController, :show, as: :users, metadata: %{access: :user}\n    match :*, \"/users/fallback\", UserController, :any\n    get \"/spaced users/:id\", UserController, :show\n    get \"/profiles/profile-:id\", UserController, :show\n    get \"/route_that_crashes\", UserController, :crash\n    get \"/files/:user_name/*path\", UserController, :image\n    get \"/backups/*path\", UserController, :image\n    get \"/static/images/icons/*image\", UserController, :image\n    get \"/exit\", UserController, :exit\n    get \"/halt-controller\", UserController, :halt\n\n    trace(\"/trace\", UserController, :trace)\n    options \"/options\", UserController, :options\n    connect \"/connect\", UserController, :connect\n    match :move, \"/move\", UserController, :move\n    match :*, \"/any\", UserController, :any\n\n    scope log: :info do\n      pipe_through :noop\n      get \"/plug\", SomePlug, []\n      get \"/users/:id/raise\", UserController, :raise\n      pipe_through :halt\n      get \"/info\", UserController, :raise\n    end\n\n    get \"/no_log\", SomePlug, [], log: false\n    get \"/fun_log\", SomePlug, [], log: {LogLevel, :log_level, []}\n    get \"/override-plug-name\", SomePlug, :action, metadata: %{mfa: {LogLevel, :log_level, 1}}\n    get \"/users/:user_id/files/:id\", UserController, :image\n\n    scope \"/halt-plug\" do\n      pipe_through :halt\n      get \"/\", UserController, :raise\n    end\n\n    get \"/*path\", UserController, :not_found\n\n    defp noop(conn, _), do: conn\n\n    defp halt(conn, _) do\n      conn |> Plug.Conn.send_resp(401, \"Unauthorized\") |> halt()\n    end\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"get root path\" do\n    conn = call(Router, :get, \"/\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users index\"\n  end\n\n  test \"get to named param with dashes\" do\n    conn = call(Router, :get, \"/users/75f6306d-a090-46f9-8b80-80fd57ec9a41\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users show\"\n    assert conn.params[\"id\"] == \"75f6306d-a090-46f9-8b80-80fd57ec9a41\"\n    assert conn.path_params[\"id\"] == \"75f6306d-a090-46f9-8b80-80fd57ec9a41\"\n\n    conn = call(Router, :get, \"/users/75f6306d-a0/files/34-95\")\n    assert conn.status == 200\n    assert conn.resp_body == \"show files\"\n    assert conn.params[\"user_id\"] == \"75f6306d-a0\"\n    assert conn.path_params[\"user_id\"] == \"75f6306d-a0\"\n    assert conn.params[\"id\"] == \"34-95\"\n    assert conn.path_params[\"id\"] == \"34-95\"\n  end\n\n  test \"get with named param\" do\n    conn = call(Router, :get, \"/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users show\"\n    assert conn.params[\"id\"] == \"1\"\n    assert conn.path_params[\"id\"] == \"1\"\n  end\n\n  test \"get with named param and late query string fetch\" do\n    conn =\n      conn(:get, \"/users/1\")\n      |> Router.call(Router.init([]))\n      |> fetch_query_params()\n\n    assert conn.status == 200\n    assert conn.resp_body == \"users show\"\n    assert conn.params[\"id\"] == \"1\"\n    assert conn.path_params[\"id\"] == \"1\"\n\n    conn =\n      conn(:get, \"/users/1?foo=bar\")\n      |> Router.call(Router.init([]))\n      |> fetch_query_params()\n\n    assert conn.status == 200\n    assert conn.resp_body == \"users show\"\n    assert conn.params[\"id\"] == \"1\"\n    assert conn.params[\"foo\"] == \"bar\"\n    assert conn.path_params[\"id\"] == \"1\"\n  end\n\n  test \"parameters are url decoded\" do\n    conn = call(Router, :get, \"/users/hello%20matey\")\n    assert conn.params == %{\"id\" => \"hello matey\"}\n\n    conn = call(Router, :get, \"/spaced%20users/hello%20matey\")\n    assert conn.params == %{\"id\" => \"hello matey\"}\n\n    conn = call(Router, :get, \"/spaced users/hello matey\")\n    assert conn.params == %{\"id\" => \"hello matey\"}\n\n    conn = call(Router, :get, \"/users/a%20b\")\n    assert conn.params == %{\"id\" => \"a b\"}\n\n    conn = call(Router, :get, \"/backups/a%20b/c%20d\")\n    assert conn.params == %{\"path\" => [\"a b\", \"c d\"]}\n  end\n\n  test \"get to custom action\" do\n    conn = call(Router, :get, \"/users/top\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users top\"\n  end\n\n  test \"options to custom action\" do\n    conn = call(Router, :options, \"/options\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users options\"\n  end\n\n  test \"connect to custom action\" do\n    conn = call(Router, :connect, \"/connect\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users connect\"\n  end\n\n  test \"trace to custom action\" do\n    conn = call(Router, :trace, \"/trace\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users trace\"\n  end\n\n  test \"splat arg with preceding named parameter to files/:user_name/*path\" do\n    conn = call(Router, :get, \"/files/elixir/Users/home/file.txt\")\n    assert conn.status == 200\n    assert conn.params[\"user_name\"] == \"elixir\"\n    assert conn.params[\"path\"] == [\"Users\", \"home\", \"file.txt\"]\n  end\n\n  test \"splat arg with preceding string to backups/*path\" do\n    conn = call(Router, :get, \"/backups/name\")\n    assert conn.status == 200\n    assert conn.params[\"path\"] == [\"name\"]\n  end\n\n  test \"splat arg with multiple preceding strings to static/images/icons/*path\" do\n    conn = call(Router, :get, \"/static/images/icons/elixir/logos/main.png\")\n    assert conn.status == 200\n    assert conn.params[\"image\"] == [\"elixir\", \"logos\", \"main.png\"]\n  end\n\n  test \"splat args are %encodings in path\" do\n    conn = call(Router, :get, \"/backups/silly%20name\")\n    assert conn.status == 200\n    assert conn.params[\"path\"] == [\"silly name\"]\n  end\n\n  test \"catch-all splat route matches\" do\n    conn = call(Router, :get, \"/foo/bar/baz\")\n    assert conn.status == 404\n    assert conn.params == %{\"path\" => ~w\"foo bar baz\"}\n    assert conn.resp_body == \"not found\"\n  end\n\n  test \"match on arbitrary http methods\" do\n    conn = call(Router, :move, \"/move\")\n    assert conn.method == \"MOVE\"\n    assert conn.status == 200\n    assert conn.resp_body == \"users move\"\n  end\n\n  test \"any verb matches\" do\n    conn = call(Router, :get, \"/any\")\n    assert conn.method == \"GET\"\n    assert conn.status == 200\n    assert conn.resp_body == \"users any\"\n\n    conn = call(Router, :put, \"/any\")\n    assert conn.method == \"PUT\"\n    assert conn.status == 200\n    assert conn.resp_body == \"users any\"\n  end\n\n  test \"different verbs with similar paths\" do\n    conn = call(Router, :post, \"/users/fallback\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users any\"\n\n    conn = call(Router, :get, \"/users/123\")\n    assert conn.status == 200\n    assert conn.resp_body == \"users show\"\n    assert conn.params[\"id\"] == \"123\"\n    assert conn.path_params[\"id\"] == \"123\"\n  end\n\n  describe \"logging\" do\n    setup do\n      Logger.delete_process_level(self())\n      :ok\n    end\n\n    test \"logs controller and action with (path) parameters\" do\n      assert capture_log(fn -> call(Router, :get, \"/users/1\", foo: \"bar\") end) =~ \"\"\"\n             [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2\n               Parameters: %{\"foo\" => \"bar\", \"id\" => \"1\"}\n               Pipelines: []\n             \"\"\"\n    end\n\n    test \"logs controller and action with filtered parameters\" do\n      assert capture_log(fn -> call(Router, :get, \"/users/1\", password: \"bar\") end) =~ \"\"\"\n             [debug] Processing with Phoenix.Router.RoutingTest.UserController.show/2\n               Parameters: %{\"id\" => \"1\", \"password\" => \"[FILTERED]\"}\n               Pipelines: []\n             \"\"\"\n    end\n\n    test \"logs plug with pipeline and custom level\" do\n      assert capture_log(fn -> call(Router, :get, \"/plug\") end) =~ \"\"\"\n             [info] Processing with Phoenix.Router.RoutingTest.SomePlug\n               Parameters: %{}\n               Pipelines: [:noop]\n             \"\"\"\n    end\n\n    test \"does not log when log is set to false\" do\n      refute capture_log(fn -> call(Router, :get, \"/no_log\", foo: \"bar\") end) =~\n               \"Processing with Phoenix.Router.RoutingTest.SomePlug\"\n    end\n\n    test \"overrides plug name that processes the route when set in metadata\" do\n      assert capture_log(fn -> call(Router, :get, \"/override-plug-name\") end) =~\n               \"Processing with Phoenix.Router.RoutingTest.LogLevel.log_level/1\"\n    end\n\n    test \"logs custom level when log is set to a 1-arity function\" do\n      assert capture_log(fn -> call(Router, :get, \"/fun_log\", level: \"info\") end) =~\n               \"[info] Processing with Phoenix.Router.RoutingTest.SomePlug\"\n\n      assert capture_log(fn -> call(Router, :get, \"/fun_log\", level: \"error\") end) =~\n               \"[error] Processing with Phoenix.Router.RoutingTest.SomePlug\"\n\n      assert capture_log(fn -> call(Router, :get, \"/fun_log\", level: \"yelling\") end) =~\n               \"[debug] Processing with Phoenix.Router.RoutingTest.SomePlug\"\n\n      assert capture_log(fn -> call(Router, :get, \"/fun_log\") end) =~\n               \"[debug] Processing with Phoenix.Router.RoutingTest.SomePlug\"\n    end\n  end\n\n  describe \"telemetry\" do\n    @router_start_event [:phoenix, :router_dispatch, :start]\n    @router_stop_event [:phoenix, :router_dispatch, :stop]\n    @router_exception_event [:phoenix, :router_dispatch, :exception]\n    @router_events [\n      @router_start_event,\n      @router_stop_event,\n      @router_exception_event\n    ]\n\n    setup context do\n      test_pid = self()\n      test_name = context.test\n\n      :telemetry.attach_many(\n        test_name,\n        @router_events,\n        fn event, measures, metadata, config ->\n          send(test_pid, {:telemetry_event, event, {measures, metadata, config}})\n        end,\n        nil\n      )\n\n      on_exit(fn -> :telemetry.detach(test_name) end)\n    end\n\n    test \"phoenix.router_dispatch.start and .stop are emitted on success\" do\n      call(Router, :get, \"/users/123\")\n\n      assert_received {:telemetry_event, @router_start_event, {_, %{route: \"/users/:id\"}, _}}\n\n      assert_received {:telemetry_event, @router_stop_event, {_, %{route: \"/users/:id\"}, _}}\n\n      refute_received {:telemetry_event, @router_exception_event, {_, %{route: \"/users/:id\"}, _}}\n    end\n\n    test \"phoenix.router_dispatch.start and .stop are emitted when conn halted in router\" do\n      conn = call(Router, :get, \"/halt-plug\")\n\n      assert conn.halted\n      assert conn.status == 401\n\n      assert_received {:telemetry_event, @router_start_event, {_, %{route: \"/halt-plug\"}, _}}\n\n      assert_received {:telemetry_event, @router_stop_event, {_, %{route: \"/halt-plug\"}, _}}\n\n      refute_received {:telemetry_event, @router_exception_event, {_, %{route: \"/halt-plug\"}, _}}\n    end\n\n    test \"phoenix.router_dispatch.start and .stop are emitted when conn is halted in controller\" do\n      conn = call(Router, :get, \"/halt-controller\")\n\n      assert conn.halted\n      assert conn.status == 401\n\n      assert_received {:telemetry_event, @router_start_event,\n                       {_, %{route: \"/halt-controller\"}, _}}\n\n      assert_received {:telemetry_event, @router_stop_event, {_, %{route: \"/halt-controller\"}, _}}\n\n      refute_received {:telemetry_event, @router_exception_event,\n                       {_, %{route: \"/halt-controller\"}, _}}\n    end\n\n    test \"phoenix.router_dispatch.start and .exception are emitted on crash\" do\n      assert_raise Plug.Conn.WrapperError, ~r/UndefinedFunctionError/, fn ->\n        call(Router, :get, \"/route_that_crashes\")\n      end\n\n      assert_received {:telemetry_event, @router_start_event,\n                       {_, %{route: \"/route_that_crashes\"}, _}}\n\n      assert_received {:telemetry_event, @router_exception_event,\n                       {_, %{route: \"/route_that_crashes\"}, _}}\n\n      refute_received {:telemetry_event, @router_stop_event,\n                       {_, %{route: \"/route_that_crashes\"}, _}}\n    end\n\n    test \"phoenix.router_dispatch.start and .exception are emitted on exit\" do\n      catch_exit(call(Router, :get, \"/exit\"))\n\n      assert_received {:telemetry_event, @router_start_event, {_, %{route: \"/exit\"}, _}}\n\n      assert_received {:telemetry_event, @router_exception_event, {_, %{route: \"/exit\"}, _}}\n\n      refute_received {:telemetry_event, @router_stop_event, {_, %{route: \"/exit\"}, _}}\n    end\n\n    test \"phoenix.router_dispatch.start has supported measurements and metadata\" do\n      call(Router, :get, \"/users/123\")\n\n      assert_received {:telemetry_event, @router_start_event,\n                       {measures, %{route: \"/users/:id\"} = meta, _config}}\n\n      assert is_integer(measures.system_time)\n\n      assert %{\n               access: :user,\n               conn: %Plug.Conn{state: :unset},\n               log: :debug,\n               path_params: %{\"id\" => \"123\"},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :show,\n               route: \"/users/:id\"\n             } = meta\n    end\n\n    test \"phoenix.router_dispatch.stop has supported measurements and metadata\" do\n      call(Router, :get, \"/users/123\")\n\n      assert_received {:telemetry_event, @router_stop_event,\n                       {measures, %{route: \"/users/:id\"} = meta, _config}}\n\n      assert is_integer(measures.duration)\n\n      assert %{\n               access: :user,\n               conn: %Plug.Conn{state: :sent},\n               log: :debug,\n               path_params: %{\"id\" => \"123\"},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :show,\n               route: \"/users/:id\"\n             } = meta\n    end\n\n    test \"phoenix.router_dispatch.exception has supported measurements and metadata on crash\" do\n      assert_raise Plug.Conn.WrapperError, \"** (RuntimeError) boom\", fn ->\n        call(Router, :get, \"/users/123/raise\")\n      end\n\n      assert_received {:telemetry_event, @router_exception_event,\n                       {measures, %{route: \"/users/:id/raise\"} = meta, _config}}\n\n      assert is_integer(measures.duration)\n\n      assert %{\n               conn: %Plug.Conn{state: :unset},\n               kind: :error,\n               log: :info,\n               path_params: %{\"id\" => \"123\"},\n               pipe_through: [:noop],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :raise,\n               reason: %Plug.Conn.WrapperError{\n                 conn: %Plug.Conn{state: :unset},\n                 kind: :error,\n                 reason: %RuntimeError{message: \"boom\"},\n                 stack: wrapped_stacktrace\n               },\n               route: \"/users/:id/raise\",\n               stacktrace: stacktrace\n             } = meta\n\n      assert is_list(wrapped_stacktrace) && length(wrapped_stacktrace) > 0\n      assert is_list(stacktrace) && length(stacktrace) > 0\n    end\n\n    test \"phoenix.router_dispatch.exception has supported measurements and metadata on exit\" do\n      catch_exit(call(Router, :get, \"/exit\"))\n\n      assert_received {:telemetry_event, @router_exception_event,\n                       {measures, %{route: \"/exit\"} = meta, _config}}\n\n      assert is_integer(measures.duration)\n\n      assert %{\n               conn: %Plug.Conn{state: :unset},\n               kind: :exit,\n               log: :debug,\n               path_params: %{},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :exit,\n               reason: :boom,\n               route: \"/exit\",\n               stacktrace: stacktrace\n             } = meta\n\n      assert is_list(stacktrace) && length(stacktrace) > 0\n    end\n  end\n\n  describe \"route_info\" do\n    test \" returns route string, path params, and more\" do\n      assert Phoenix.Router.route_info(Router, \"GET\", \"foo/bar/baz\", nil) == %{\n               log: :debug,\n               path_params: %{\"path\" => [\"foo\", \"bar\", \"baz\"]},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :not_found,\n               route: \"/*path\"\n             }\n\n      assert Phoenix.Router.route_info(Router, \"GET\", \"users/1\", nil) == %{\n               log: :debug,\n               path_params: %{\"id\" => \"1\"},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :show,\n               route: \"/users/:id\",\n               access: :user\n             }\n\n      assert Phoenix.Router.route_info(Router, \"GET\", \"/\", \"host\") == %{\n               log: :debug,\n               path_params: %{},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :index,\n               route: \"/\"\n             }\n\n      assert Phoenix.Router.route_info(Router, \"POST\", \"/not-exists\", \"host\") == :error\n    end\n\n    test \"returns route string, path params and more for split path\" do\n      assert Phoenix.Router.route_info(Router, \"GET\", ~w(foo bar baz), nil) == %{\n               log: :debug,\n               path_params: %{\"path\" => [\"foo\", \"bar\", \"baz\"]},\n               pipe_through: [],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :not_found,\n               route: \"/*path\"\n             }\n    end\n\n    test \"returns accumulated pipe_through metadata\" do\n      assert Phoenix.Router.route_info(Router, \"GET\", \"/info\", nil) == %{\n               log: :info,\n               path_params: %{},\n               pipe_through: [:noop, :halt],\n               plug: Phoenix.Router.RoutingTest.UserController,\n               plug_opts: :raise,\n               route: \"/info\"\n             }\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/router/scope_test.exs",
    "content": "defmodule Phoenix.Router.ScopedRoutingTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  # Path scoping\n\n  defmodule Api.V1.UserController do\n    use Phoenix.Controller, formats: []\n    def show(conn, _params), do: text(conn, \"api v1 users show\")\n    def delete(conn, _params), do: text(conn, \"api v1 users delete\")\n    def edit(conn, _params), do: text(conn, \"api v1 users edit\")\n    def foo_host(conn, _params), do: text(conn, \"foo request from #{conn.host}\")\n    def baz_host(conn, _params), do: text(conn, \"baz request from #{conn.host}\")\n    def multi_host(conn, _params), do: text(conn, \"multi_host request from #{conn.host}\")\n\n    def other_subdomain(conn, _params),\n      do: text(conn, \"other_subdomain request from #{conn.host}\")\n\n    def proxy(conn, _) do\n      {controller, action} = conn.private.proxy_to\n      controller.call(conn, controller.init(action))\n    end\n  end\n\n  defmodule Api.V1.VenueController do\n    def init(opts), do: opts\n    def call(conn, _opts), do: conn\n  end\n\n  defmodule :erlang_like do\n    def init(action), do: action\n    def call(conn, action), do: Plug.Conn.send_resp(conn, 200, \"Erlang like #{action}\")\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n\n    scope \"/admin\", host: \"baz.\" do\n      get \"/users/:id\", Api.V1.UserController, :baz_host\n    end\n\n    scope host: \"foobar.com\" do\n      scope \"/admin\" do\n        get \"/users/:id\", Api.V1.UserController, :foo_host\n      end\n    end\n\n    scope \"/admin\" do\n      get \"/erlang/like\", :erlang_like, :action, as: :erlang_like\n      get \"/users/:id\", Api.V1.UserController, :show\n    end\n\n    scope \"/api\" do\n      scope \"/v1\" do\n        get \"/users/:id\", Api.V1.UserController, :show\n      end\n    end\n\n    scope \"/api\", Api, private: %{private_token: \"foo\"} do\n      get \"/users\", V1.UserController, :show\n      get \"/users/:id\", V1.UserController, :show, private: %{private_token: \"bar\"}\n\n      scope \"/v1\", alias: V1 do\n        resources \"/users\", UserController, only: [:delete], private: %{private_token: \"baz\"}\n\n        get \"/noalias\", Api.V1.UserController, :proxy,\n          private: %{proxy_to: {scoped_alias(__MODULE__, UserController), :show}},\n          alias: false\n\n        scope \"/scoped\", alias: false do\n          get \"/noalias\", Api.V1.UserController, :proxy,\n            private: %{proxy_to: {scoped_alias(__MODULE__, Api.V1.UserController), :show}}\n        end\n      end\n    end\n\n    scope \"/assigns\", Api, assigns: %{assigns_token: \"foo\"} do\n      get \"/users\", V1.UserController, :show\n      get \"/users/:id\", V1.UserController, :show, assigns: %{assigns_token: \"bar\"}\n\n      scope \"/v1\", alias: V1 do\n        resources \"/users\", UserController, only: [:delete], assigns: %{assigns_token: \"baz\"}\n      end\n    end\n\n    scope \"/host\", host: \"baz.\" do\n      get \"/users/:id\", Api.V1.UserController, :baz_host\n    end\n\n    scope host: \"foobar.com\" do\n      scope \"/host\" do\n        get \"/users/:id\", Api.V1.UserController, :foo_host\n      end\n    end\n\n    scope \"/api\" do\n      scope \"/v1\", Api do\n        resources \"/venues\", V1.VenueController, only: [:show], alias: V1 do\n          resources \"/users\", UserController, only: [:edit]\n        end\n      end\n    end\n\n    # match www, no subdomain, and localhost\n    scope \"/multi_host\", host: [\"www.\", \"example.com\", \"localhost\"] do\n      get \"/\", Api.V1.UserController, :multi_host\n    end\n\n    # matched logged in subdomain user homepages\n    scope \"/multi_host\" do\n      get \"/\", Api.V1.UserController, :other_subdomain\n    end\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  test \"single scope for single routes\" do\n    conn = call(Router, :get, \"/admin/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users show\"\n    assert conn.params[\"id\"] == \"1\"\n\n    conn = call(Router, :get, \"/api/users/13\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users show\"\n    assert conn.params[\"id\"] == \"13\"\n  end\n\n  test \"single scope with Erlang like route\" do\n    conn = call(Router, :get, \"/admin/erlang/like\")\n    assert conn.status == 200\n    assert conn.resp_body == \"Erlang like action\"\n  end\n\n  test \"double scope for single routes\" do\n    conn = call(Router, :get, \"/api/v1/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users show\"\n    assert conn.params[\"id\"] == \"1\"\n  end\n\n  test \"scope for resources\" do\n    conn = call(Router, :delete, \"/api/v1/users/12\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users delete\"\n    assert conn.params[\"id\"] == \"12\"\n  end\n\n  test \"scope for double nested resources\" do\n    conn = call(Router, :get, \"/api/v1/venues/12/users/13/edit\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users edit\"\n    assert conn.params[\"venue_id\"] == \"12\"\n    assert conn.params[\"id\"] == \"13\"\n  end\n\n  test \"host scopes routes based on conn.host\" do\n    conn = call(Router, :get, \"http://foobar.com/admin/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"foo request from foobar.com\"\n    assert conn.params[\"id\"] == \"1\"\n  end\n\n  test \"host scopes allows partial host matching\" do\n    conn = call(Router, :get, \"http://baz.bing.com/admin/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"baz request from baz.bing.com\"\n\n    conn = call(Router, :get, \"http://baz.pang.com/admin/users/1\")\n    assert conn.status == 200\n    assert conn.resp_body == \"baz request from baz.pang.com\"\n  end\n\n  test \"host scopes allows list of hosts\" do\n    conn = call(Router, :get, \"http://www.example.com/multi_host\")\n    assert conn.status == 200\n    assert conn.resp_body == \"multi_host request from www.example.com\"\n\n    conn = call(Router, :get, \"http://www.anotherwww.com/multi_host\")\n    assert conn.status == 200\n    assert conn.resp_body == \"multi_host request from www.anotherwww.com\"\n\n    conn = call(Router, :get, \"http://localhost/multi_host\")\n    assert conn.status == 200\n    assert conn.resp_body == \"multi_host request from localhost\"\n\n    conn = call(Router, :get, \"http://subdomain.example.com/multi_host\")\n    assert conn.status == 200\n    assert conn.resp_body == \"other_subdomain request from subdomain.example.com\"\n  end\n\n  test \"host 404s when failed match\" do\n    conn = call(Router, :get, \"http://foobar.com/host/users/1\")\n    assert conn.status == 200\n\n    conn = call(Router, :get, \"http://baz.pang.com/host/users/1\")\n    assert conn.status == 200\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :get, \"http://foobar.com.br/host/users/1\")\n    end\n\n    assert_raise Phoenix.Router.NoRouteError, fn ->\n      call(Router, :get, \"http://ba.pang.com/host/users/1\")\n    end\n  end\n\n  test \"bad host raises\" do\n    assert_raise ArgumentError,\n                 \"expected router scope :host to be compile-time string or list of strings, got: nil\",\n                 fn ->\n                   defmodule BadRouter do\n                     use Phoenix.Router\n\n                     scope \"/admin\", host: [\"foo.\", nil] do\n                       get \"/users/:id\", Api.V1.UserController, :baz_host\n                     end\n                   end\n                 end\n  end\n\n  test \"private data in scopes\" do\n    conn = call(Router, :get, \"/api/users\")\n    assert conn.status == 200\n    assert conn.private[:private_token] == \"foo\"\n\n    conn = call(Router, :get, \"/api/users/13\")\n    assert conn.status == 200\n    assert conn.private[:private_token] == \"bar\"\n\n    conn = call(Router, :delete, \"/api/v1/users/13\")\n    assert conn.status == 200\n    assert conn.private[:private_token] == \"baz\"\n  end\n\n  test \"assigns data in scopes\" do\n    conn = call(Router, :get, \"/assigns/users\")\n    assert conn.status == 200\n    assert conn.assigns[:assigns_token] == \"foo\"\n\n    conn = call(Router, :get, \"/assigns/users/13\")\n    assert conn.status == 200\n    assert conn.assigns[:assigns_token] == \"bar\"\n\n    conn = call(Router, :delete, \"/assigns/v1/users/13\")\n    assert conn.status == 200\n    assert conn.assigns[:assigns_token] == \"baz\"\n  end\n\n  test \"string paths are enforced\" do\n    assert_raise ArgumentError, ~r{router paths must be strings, got: :bar}, fn ->\n      defmodule SomeRouter do\n        use Phoenix.Router, otp_app: :phoenix\n        get :bar, Router, []\n      end\n    end\n\n    assert_raise ArgumentError, ~r{router paths must be strings, got: :bar}, fn ->\n      defmodule SomeRouter do\n        use Phoenix.Router, otp_app: :phoenix\n        get \"/foo\", Router, []\n\n        scope \"/another\" do\n          resources :bar, Router, []\n        end\n      end\n    end\n  end\n\n  test \"alias false with expanded scoped alias via option\" do\n    conn = call(Router, :get, \"/api/v1/noalias\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users show\"\n  end\n\n  test \"alias false with expanded scoped alias via scope\" do\n    conn = call(Router, :get, \"/api/v1/scoped/noalias\")\n    assert conn.status == 200\n    assert conn.resp_body == \"api v1 users show\"\n  end\n\n  test \"raises for reserved prefixes\" do\n    assert_raise ArgumentError, ~r/`static` is a reserved route prefix/, fn ->\n      defmodule ErrorRouter do\n        use Phoenix.Router\n\n        scope \"/\" do\n          get \"/\", StaticController, :index\n        end\n      end\n    end\n\n    assert_raise ArgumentError, ~r/`static` is a reserved route prefix/, fn ->\n      defmodule ErrorRouter do\n        use Phoenix.Router\n\n        scope \"/\" do\n          get \"/\", Api.V1.UserController, :show, as: :static\n        end\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/socket/message_test.exs",
    "content": "defmodule Phoenix.Socket.MessageTest do\n  use ExUnit.Case, async: true\n  doctest Phoenix.Socket.Message\n\n  alias Phoenix.Socket.Message\n\n  describe \"inspect/2 custom implementation\" do\n    test \"filters sensitive values in form submit events\" do\n      message = %Message{\n        topic: \"lv:1\",\n        event: \"event\",\n        payload: %{\n          \"event\" => \"submit\",\n          \"type\" => \"form\",\n          \"value\" => \"username=john&password=secret123&email=john@example.com\"\n        },\n        ref: \"1\",\n        join_ref: \"1\"\n      }\n\n      assert inspect(message) =~ \"\\\"value\\\" => \\\"[FILTERED]\\\"\"\n    end\n\n    test \"filters sensitive values at the end of form submit events\" do\n      message = %Message{\n        topic: \"lv:1\",\n        event: \"event\",\n        payload: %{\n          \"event\" => \"submit\",\n          \"type\" => \"form\",\n          \"value\" => \"username=john&password=secret123\"\n        },\n        ref: \"1\",\n        join_ref: \"1\"\n      }\n\n      assert inspect(message) =~ \"\\\"value\\\" => \\\"[FILTERED]\\\"\"\n    end\n\n    test \"handles malformed query strings gracefully\" do\n      message = %Message{\n        topic: \"lv:1\",\n        event: \"event\",\n        payload: %{\n          \"event\" => \"submit\",\n          \"type\" => \"form\",\n          \"value\" => \"invalid=query=string&password=secret\"\n        },\n        ref: \"1\",\n        join_ref: \"1\"\n      }\n\n      assert inspect(message) =~ \"\\\"value\\\" => \\\"[FILTERED]\\\"\"\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/socket/socket_test.exs",
    "content": "defmodule Phoenix.SocketTest do\n  use ExUnit.Case, async: true\n\n  import Phoenix.Socket\n  alias Phoenix.Socket.{Message, InvalidMessageError}\n\n  defmodule UserSocket do\n    use Phoenix.Socket\n\n    channel \"food*\", FoodChannel\n    channel \"foo*\", FooChannel\n\n    def connect(_params, socket, _connect_info), do: {:ok, socket}\n    def id(_socket), do: nil\n  end\n\n  describe \"__channel__\" do\n    test \"returns the correct channel handler module\" do\n      assert {FooChannel, []} == UserSocket.__channel__(\"foo:1\")\n      assert {FoodChannel, []} == UserSocket.__channel__(\"food:1\")\n      assert nil == UserSocket.__channel__(\"unknown\")\n    end\n  end\n\n  describe \"messages\" do\n    test \"from_map! converts a map with string keys into a %Message{}\" do\n      msg = Message.from_map!(%{\"topic\" => \"c\", \"event\" => \"e\", \"payload\" => \"\", \"ref\" => \"r\"})\n      assert msg == %Message{topic: \"c\", event: \"e\", payload: \"\", ref: \"r\"}\n    end\n\n    test \"from_map! raises InvalidMessageError when any required key\" do\n      assert_raise InvalidMessageError, fn ->\n        Message.from_map!(%{\"event\" => \"e\", \"payload\" => \"\", \"ref\" => \"r\"})\n      end\n\n      assert_raise InvalidMessageError, fn ->\n        Message.from_map!(%{\"topic\" => \"c\", \"payload\" => \"\", \"ref\" => \"r\"})\n      end\n\n      assert_raise InvalidMessageError, fn ->\n        Message.from_map!(%{\"topic\" => \"c\", \"event\" => \"e\", \"ref\" => \"r\"})\n      end\n\n      assert_raise InvalidMessageError, fn ->\n        Message.from_map!(%{\"topic\" => \"c\", \"event\" => \"e\"})\n      end\n    end\n  end\n\n  describe \"assign/3\" do\n    test \"assigns to socket\" do\n      socket = %Phoenix.Socket{}\n      assert socket.assigns[:foo] == nil\n      socket = assign(socket, :foo, :bar)\n      assert socket.assigns[:foo] == :bar\n    end\n  end\n\n  describe \"assign/2\" do\n    test \"assigns a map socket\" do\n      socket = %Phoenix.Socket{}\n      assert socket.assigns[:foo] == nil\n      socket = assign(socket, %{foo: :bar, abc: :def})\n      assert socket.assigns[:foo] == :bar\n      assert socket.assigns[:abc] == :def\n    end\n\n    test \"merges if values exist\" do\n      socket = %Phoenix.Socket{}\n      socket = assign(socket, %{foo: :bar, abc: :def})\n      socket = assign(socket, %{foo: :baz})\n      assert socket.assigns[:foo] == :baz\n      assert socket.assigns[:abc] == :def\n    end\n\n    test \"merges keyword lists\" do\n      socket = %Phoenix.Socket{}\n      socket = assign(socket, %{foo: :bar, abc: :def})\n      socket = assign(socket, foo: :baz)\n      assert socket.assigns[:foo] == :baz\n      assert socket.assigns[:abc] == :def\n    end\n\n    test \"accepts functions\" do\n      socket = %Phoenix.Socket{}\n      assert socket.assigns[:foo] == nil\n      socket = assign(socket, :foo, :bar)\n      assert socket.assigns[:foo] == :bar\n      socket = assign(socket, fn %{foo: :bar} -> [baz: :quux] end)\n      assert socket.assigns[:baz] == :quux\n    end\n  end\n\n  describe \"drainer_spec/1\" do\n    defmodule Endpoint do\n      use Phoenix.Endpoint, otp_app: :phoenix\n    end\n\n    defmodule DrainerSpecSocket do\n      use Phoenix.Socket\n\n      def id(_), do: \"123\"\n\n      def dynamic_drainer_config do\n        [\n          batch_size: 200,\n          batch_interval: 2_000,\n          shutdown: 20_000\n        ]\n      end\n    end\n\n    test \"loads static drainer config\" do\n      drainer_spec = [\n        batch_size: 100,\n        batch_interval: 1_000,\n        shutdown: 10_000\n      ]\n\n      assert DrainerSpecSocket.drainer_spec(drainer: drainer_spec, endpoint: Endpoint) ==\n               {Phoenix.Socket.PoolDrainer,\n                {Endpoint, DrainerSpecSocket, [endpoint: Endpoint, drainer: drainer_spec]}}\n    end\n\n    test \"loads dynamic drainer config\" do\n      drainer_spec = DrainerSpecSocket.dynamic_drainer_config()\n\n      assert DrainerSpecSocket.drainer_spec(\n               drainer: {DrainerSpecSocket, :dynamic_drainer_config, []},\n               endpoint: Endpoint\n             ) ==\n               {Phoenix.Socket.PoolDrainer,\n                {Endpoint, DrainerSpecSocket, [endpoint: Endpoint, drainer: drainer_spec]}}\n    end\n\n    test \"returns ignore if drainer is set to false\" do\n      assert DrainerSpecSocket.drainer_spec(drainer: false, endpoint: Endpoint) == :ignore\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/socket/transport_test.exs",
    "content": "defmodule Phoenix.Socket.TransportTest do\n  use ExUnit.Case, async: true\n  use RouterHelper\n\n  import ExUnit.CaptureLog\n\n  alias Phoenix.Socket.Transport\n\n  @secret_key_base String.duplicate(\"abcdefgh\", 8)\n\n  Application.put_env(:phoenix, __MODULE__.Endpoint,\n    force_ssl: [],\n    url: [host: \"host.com\"],\n    check_origin: [\"//endpoint.com\"],\n    secret_key_base: @secret_key_base\n  )\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    @session_config [\n      store: :cookie,\n      key: \"_hello_key\",\n      signing_salt: \"change_me\"\n    ]\n\n    def session_config(overrides \\\\ []), do: Keyword.merge(@session_config, overrides)\n\n    plug Plug.Session, @session_config\n    plug :fetch_session\n    plug :put_csrf\n    plug :put_session\n\n    defp put_csrf(conn, _opts) do\n      conn = Plug.Conn.fetch_query_params(conn)\n      session_key = Map.get(conn.query_params, \"session_key\", \"_csrf_token\")\n      opts = Plug.CSRFProtection.init(session_key: session_key)\n      Plug.CSRFProtection.call(conn, opts)\n    end\n\n    defp put_session(conn, _) do\n      conn\n      |> put_session(:from_session, \"123\")\n      |> send_resp(200, Plug.CSRFProtection.get_csrf_token())\n    end\n  end\n\n  setup_all do\n    Endpoint.start_link()\n    :ok\n  end\n\n  setup do\n    Logger.put_process_level(self(), :none)\n  end\n\n  ## Check origin\n\n  describe \"check_origin/4\" do\n    defp check_origin(%Plug.Conn{} = conn, origin, opts) do\n      conn = put_req_header(conn, \"origin\", origin)\n      Transport.check_origin(conn, make_ref(), Endpoint, opts)\n    end\n\n    defp check_origin(origin, opts), do: check_origin(conn(:get, \"/\"), origin, opts)\n\n    test \"does not check origin if disabled\" do\n      refute check_origin(\"/\", check_origin: false).halted\n    end\n\n    test \"checks origin against host\" do\n      refute check_origin(\"https://host.com/\", check_origin: true).halted\n      conn = check_origin(\"https://another.com/\", check_origin: true)\n      assert conn.halted\n      assert conn.status == 403\n    end\n\n    test \"checks origin from endpoint config\" do\n      refute check_origin(\"https://endpoint.com/\", []).halted\n      conn = check_origin(\"https://another.com/\", [])\n      assert conn.halted\n      assert conn.status == 403\n    end\n\n    test \"can get the host from system variables\" do\n      refute check_origin(\"https://host.com\", check_origin: true).halted\n    end\n\n    test \"wildcard subdomains\" do\n      origins = [\"https://*.ex.com\", \"http://*.ex.com\"]\n\n      conn = check_origin(\"http://org1.ex.com\", check_origin: origins)\n      refute conn.halted\n      conn = check_origin(\"https://org1.ex.com\", check_origin: origins)\n      refute conn.halted\n\n      conn = check_origin(\"https://ex.com\", check_origin: origins)\n      refute conn.halted\n\n      conn = check_origin(\"https://org1.prefix-ex.com\", check_origin: origins)\n      assert conn.halted\n    end\n\n    test \"nested wildcard subdomains\" do\n      origins = [\"http://*.foo.example.com\"]\n\n      conn = check_origin(\"http://org1.foo.example.com\", check_origin: origins)\n      refute conn.halted\n\n      conn = check_origin(\"http://foo.example.com\", check_origin: origins)\n      refute conn.halted\n\n      conn = check_origin(\"http://bad.example.com\", check_origin: origins)\n      assert conn.halted\n\n      conn = check_origin(\"http://org1.prefix-foo.example.com\", check_origin: origins)\n      assert conn.halted\n\n      conn = check_origin(\"http://org1.bar.example.com\", check_origin: origins)\n      assert conn.halted\n      assert conn.status == 403\n    end\n\n    test \"subdomains do not match without a wildcard\" do\n      conn = check_origin(\"http://org1.ex.com\", check_origin: [\"//ex.com\"])\n      assert conn.halted\n    end\n\n    test \"halts invalid URIs when check origin is configured\" do\n      Logger.delete_process_level(self())\n      origins = [\"//example.com\", \"http://scheme.com\", \"//port.com:81\"]\n\n      logs =\n        capture_log(fn ->\n          for config <- [origins, true] do\n            assert check_origin(\"file://\", check_origin: config).halted\n            assert check_origin(\"null\", check_origin: config).halted\n            assert check_origin(\"\", check_origin: config).halted\n          end\n        end)\n\n      assert logs =~ \"Origin of the request: file://\"\n      assert logs =~ \"Origin of the request: null\"\n    end\n\n    def invalid_allowed?(%URI{host: nil}), do: true\n    def invalid_allowed?(%URI{host: \"\"}), do: true\n\n    test \"allows custom MFA check to handle invalid host\" do\n      mfa = {__MODULE__, :invalid_allowed?, []}\n\n      refute check_origin(\"file://\", check_origin: mfa).halted\n      refute check_origin(\"null\", check_origin: mfa).halted\n      refute check_origin(\"\", check_origin: mfa).halted\n    end\n\n    test \"checks origin against :conn\" do\n      conn = %{conn(:get, \"/\") | host: \"example.com\", scheme: :http, port: 80}\n      refute check_origin(conn, \"http://example.com\", check_origin: :conn).halted\n\n      assert check_origin(conn, \"https://example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"ws://example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"wss://example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"http://www.example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"http://www.another.com\", check_origin: :conn).halted\n\n      conn = %{conn(:get, \"/\") | host: \"example.com\", scheme: :https, port: 443}\n      refute check_origin(conn, \"https://example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"http://example.com\", check_origin: :conn).halted\n      assert check_origin(conn, \"https://example.com:4000\", check_origin: :conn).halted\n    end\n\n    test \"does not halt invalid URIs when check_origin is disabled\" do\n      refute check_origin(\"file://\", check_origin: false).halted\n      refute check_origin(\"null\", check_origin: false).halted\n      refute check_origin(\"\", check_origin: false).halted\n    end\n\n    test \"checks the origin of requests against allowed origins\" do\n      origins = [\"//example.com\", \"http://scheme.com\", \"//port.com:81\"]\n\n      # not allowed host\n      conn = check_origin(\"http://notallowed.com/\", check_origin: origins)\n      assert conn.halted\n      assert conn.status == 403\n\n      # Only host match\n      refute check_origin(\"http://example.com/\", check_origin: origins).halted\n      refute check_origin(\"https://example.com/\", check_origin: origins).halted\n\n      # Scheme + host match (checks port due to scheme)\n      refute check_origin(\"http://scheme.com/\", check_origin: origins).halted\n\n      conn = check_origin(\"https://scheme.com/\", check_origin: origins)\n      assert conn.halted\n      assert conn.status == 403\n\n      conn = check_origin(\"http://scheme.com:8080/\", check_origin: origins)\n      assert conn.halted\n      assert conn.status == 403\n\n      # Scheme + host + port match\n      refute check_origin(\"http://port.com:81/\", check_origin: origins).halted\n\n      conn = check_origin(\"http://port.com:82/\", check_origin: origins)\n      assert conn.halted\n      assert conn.status == 403\n    end\n\n    def check_origin_callback(%URI{host: \"example.com\"}), do: true\n    def check_origin_callback(%URI{host: _}), do: false\n\n    test \"checks the origin of requests against an MFA\" do\n      # callback without additional arguments\n      mfa = {__MODULE__, :check_origin_callback, []}\n\n      # a not allowed host\n      conn = check_origin(\"http://notallowed.com/\", check_origin: mfa)\n      assert conn.halted\n      assert conn.status == 403\n\n      # an allowed host\n      refute check_origin(\"http://example.com/\", check_origin: mfa).halted\n    end\n\n    def check_origin_additional(%URI{host: allowed}, allowed), do: true\n    def check_origin_additional(%URI{host: _}, _allowed), do: false\n\n    test \"checks the origin of requests against an MFA, passing additional arguments\" do\n      # callback with additional argument\n      mfa = {__MODULE__, :check_origin_additional, [\"host.com\"]}\n\n      # a not allowed host\n      conn = check_origin(\"http://notallowed.com/\", check_origin: mfa)\n      assert conn.halted\n      assert conn.status == 403\n\n      # an allowed host\n      refute check_origin(\"https://host.com/\", check_origin: mfa).halted\n    end\n  end\n\n  ## Check subprotocols\n\n  describe \"check_subprotocols/2\" do\n    defp check_subprotocols(expected, passed) do\n      conn = conn(:get, \"/\") |> put_req_header(\"sec-websocket-protocol\", Enum.join(passed, \", \"))\n      Transport.check_subprotocols(conn, expected)\n    end\n\n    test \"does not check subprotocols if not passed expected\" do\n      refute check_subprotocols(nil, [\"sip\"]).halted\n    end\n\n    test \"does not check subprotocols if conn is halted\" do\n      halted_conn = conn(:get, \"/\") |> halt()\n      conn = Transport.check_subprotocols(halted_conn, [\"sip\"])\n      assert conn == halted_conn\n    end\n\n    test \"returns first matched subprotocol\" do\n      conn = check_subprotocols([\"sip\", \"mqtt\"], [\"sip\", \"mqtt\"])\n      refute conn.halted\n      assert get_resp_header(conn, \"sec-websocket-protocol\") == [\"sip\"]\n    end\n\n    test \"halt if expected and passed subprotocols don't match\" do\n      conn = check_subprotocols([\"sip\"], [\"mqtt\"])\n      assert conn.halted\n      assert conn.status == 403\n    end\n\n    test \"halt if expected subprotocols passed in the wrong format\" do\n      conn = check_subprotocols(\"sip\", [\"mqtt\"])\n      assert conn.halted\n      assert conn.status == 403\n    end\n  end\n\n  describe \"connect_info/4\" do\n    defp load_connect_info(connect_info) do\n      [connect_info: connect_info] = Transport.load_config(connect_info: connect_info)\n      connect_info\n    end\n\n    test \"loads the session from MFA\" do\n      conn = conn(:get, \"https://foo.com/\") |> Endpoint.call([])\n      csrf_token = conn.resp_body\n      session_cookie = conn.cookies[\"_hello_key\"]\n\n      connect_info = load_connect_info(session: {Endpoint, :session_config, []})\n\n      assert %{session: %{\"from_session\" => \"123\"}} =\n               conn(:get, \"https://foo.com/\", _csrf_token: csrf_token)\n               |> put_req_cookie(\"_hello_key\", session_cookie)\n               |> fetch_query_params()\n               |> Transport.connect_info(Endpoint, connect_info)\n    end\n\n    test \"loads the session with custom :csrf_token_key\" do\n      conn = conn(:get, \"https://foo.com?session_key=_custom_csrf_token\") |> Endpoint.call([])\n      csrf_token = conn.resp_body\n      session_cookie = conn.cookies[\"_hello_key\"]\n\n      connect_info =\n        load_connect_info(\n          session: {\n            Endpoint,\n            :session_config,\n            [[csrf_token_key: \"_custom_csrf_token\"]]\n          }\n        )\n\n      assert %{session: %{\"from_session\" => \"123\"}} =\n               conn(:get, \"https://foo.com/\", _csrf_token: csrf_token)\n               |> put_req_cookie(\"_hello_key\", session_cookie)\n               |> fetch_query_params()\n               |> Transport.connect_info(Endpoint, connect_info)\n\n      connect_info =\n        load_connect_info(\n          session: {\n            Endpoint,\n            :session_config,\n            [[csrf_token_key: \"bad_key\"]]\n          }\n        )\n\n      assert %{session: nil} =\n               conn(:get, \"https://foo.com/\", _csrf_token: csrf_token)\n               |> put_req_cookie(\"_hello_key\", session_cookie)\n               |> fetch_query_params()\n               |> Transport.connect_info(Endpoint, connect_info)\n    end\n\n    test \"loads the session when CSRF is disabled despite CSRF token not being provided\" do\n      conn = conn(:get, \"https://foo.com/\") |> Endpoint.call([])\n      session_cookie = conn.cookies[\"_hello_key\"]\n\n      connect_info = load_connect_info(session: {Endpoint, :session_config, []})\n\n      assert %{session: %{\"from_session\" => \"123\"}} =\n               conn(:get, \"https://foo.com/\")\n               |> put_req_cookie(\"_hello_key\", session_cookie)\n               |> fetch_query_params()\n               |> Transport.connect_info(Endpoint, connect_info, check_csrf: false)\n    end\n\n    test \"doesn't load session when an invalid CSRF token is provided\" do\n      conn = conn(:get, \"https://foo.com/\") |> Endpoint.call([])\n      invalid_csrf_token = \"some invalid CSRF token\"\n      session_cookie = conn.cookies[\"_hello_key\"]\n\n      connect_info = load_connect_info(session: {Endpoint, :session_config, []})\n\n      assert %{session: nil} =\n               conn(:get, \"https://foo.com/\", _csrf_token: invalid_csrf_token)\n               |> put_req_cookie(\"_hello_key\", session_cookie)\n               |> fetch_query_params()\n               |> Transport.connect_info(Endpoint, connect_info)\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/socket/v1_json_serializer_test.exs",
    "content": "defmodule Phoenix.Socket.V1.JSONSerializerTest do\n  use ExUnit.Case, async: true\n\n  alias Phoenix.Socket.{Broadcast, Message, Reply, V1}\n\n  # v1 responses must not contain join_ref\n  @serializer V1.JSONSerializer\n  @v1_msg_json \"{\\\"event\\\":\\\"e\\\",\\\"payload\\\":\\\"m\\\",\\\"ref\\\":null,\\\"topic\\\":\\\"t\\\"}\"\n  @v1_bad_json \"[null,null,\\\"t\\\",\\\"e\\\",{\\\"m\\\":1}]\"\n\n  def encode!(serializer, msg) do\n    {:socket_push, :text, encoded} = serializer.encode!(msg)\n    IO.iodata_to_binary(encoded)\n  end\n\n  def decode!(serializer, msg, opts), do: serializer.decode!(msg, opts)\n\n  def fastlane!(serializer, msg) do\n    {:socket_push, :text, encoded} = serializer.fastlane!(msg)\n    IO.iodata_to_binary(encoded)\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Message` as JSON\" do\n    msg = %Message{topic: \"t\", event: \"e\", payload: \"m\"}\n    encoded = encode!(@serializer, msg)\n\n    assert Jason.decode!(encoded) == %{\n             \"event\" => \"e\",\n             \"payload\" => \"m\",\n             \"ref\" => nil,\n             \"topic\" => \"t\"\n           }\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Reply` as JSON\" do\n    msg = %Reply{topic: \"t\", ref: \"null\"}\n    encoded = encode!(@serializer, msg)\n\n    assert Jason.decode!(encoded) == %{\n             \"event\" => \"phx_reply\",\n             \"payload\" => %{\"response\" => nil, \"status\" => nil},\n             \"ref\" => \"null\",\n             \"topic\" => \"t\"\n           }\n  end\n\n  test \"decode!/2 decodes `Phoenix.Socket.Message` from JSON\" do\n    assert %Message{topic: \"t\", event: \"e\", payload: \"m\"} ==\n             decode!(@serializer, @v1_msg_json, opcode: :text)\n  end\n\n  test \"decode!/2 raise a PayloadFormatException if the JSON doesn't contain a map\" do\n    assert_raise(\n      RuntimeError,\n      \"V1 JSON Serializer expected a map, got [nil, nil, \\\"t\\\", \\\"e\\\", %{\\\"m\\\" => 1}]\",\n      fn -> decode!(@serializer, @v1_bad_json, opcode: :text) end\n    )\n  end\n\n  test \"fastlane!/1 encodes a broadcast into a message as JSON\" do\n    msg = %Broadcast{topic: \"t\", event: \"e\", payload: \"m\"}\n    encoded = fastlane!(@serializer, msg)\n\n    assert Jason.decode!(encoded) == %{\n             \"event\" => \"e\",\n             \"payload\" => \"m\",\n             \"ref\" => nil,\n             \"topic\" => \"t\"\n           }\n  end\nend\n"
  },
  {
    "path": "test/phoenix/socket/v2_json_serializer_test.exs",
    "content": "defmodule Phoenix.Socket.V2.JSONSerializerTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Socket.{Broadcast, Message, Reply, V2}\n\n  @serializer V2.JSONSerializer\n  @v2_fastlane_json \"[null,null,\\\"t\\\",\\\"e\\\",{\\\"m\\\":1}]\"\n  @v2_msg_json \"[null,null,\\\"t\\\",\\\"e\\\",{\\\"m\\\":1}]\"\n\n  @client_push <<\n    # push\n    0::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # event_size\n    5,\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"event\",\n    101,\n    102,\n    103\n  >>\n\n  @reply <<\n    # reply\n    1::size(8),\n    # join_ref_size\n    2,\n    # ref_size\n    3,\n    # topic_size\n    5,\n    # status_size\n    2,\n    \"12\",\n    \"123\",\n    \"topic\",\n    \"ok\",\n    101,\n    102,\n    103\n  >>\n\n  @broadcast <<\n    # broadcast\n    2::size(8),\n    # topic_size\n    5,\n    # event_size\n    5,\n    \"topic\",\n    \"event\",\n    101,\n    102,\n    103\n  >>\n\n  def encode!(serializer, msg) do\n    case serializer.encode!(msg) do\n      {:socket_push, :text, encoded} ->\n        assert is_list(encoded)\n        IO.iodata_to_binary(encoded)\n\n      {:socket_push, :binary, encoded} ->\n        assert is_binary(encoded)\n        encoded\n    end\n  end\n\n  def decode!(serializer, msg, opts \\\\ []) do\n    serializer.decode!(msg, opts)\n  end\n\n  def fastlane!(serializer, msg) do\n    case serializer.fastlane!(msg) do\n      {:socket_push, :text, encoded} ->\n        assert is_list(encoded)\n        IO.iodata_to_binary(encoded)\n\n      {:socket_push, :binary, encoded} ->\n        assert is_binary(encoded)\n        encoded\n    end\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Message` as JSON\" do\n    msg = %Message{topic: \"t\", event: \"e\", payload: %{m: 1}}\n    assert encode!(@serializer, msg) == @v2_msg_json\n  end\n\n  test \"encode!/1 raises when payload is not a map\" do\n    msg = %Message{topic: \"t\", event: \"e\", payload: \"invalid\"}\n    assert_raise ArgumentError, fn -> encode!(@serializer, msg) end\n  end\n\n  test \"encode!/1 encodes `Phoenix.Socket.Reply` as JSON\" do\n    msg = %Reply{topic: \"t\", payload: %{m: 1}}\n    encoded = encode!(@serializer, msg)\n\n    assert Jason.decode!(encoded) == [\n             nil,\n             nil,\n             \"t\",\n             \"phx_reply\",\n             %{\"response\" => %{\"m\" => 1}, \"status\" => nil}\n           ]\n  end\n\n  test \"decode!/2 decodes `Phoenix.Socket.Message` from JSON\" do\n    assert %Message{topic: \"t\", event: \"e\", payload: %{\"m\" => 1}} ==\n             decode!(@serializer, @v2_msg_json, opcode: :text)\n  end\n\n  test \"fastlane!/1 encodes a broadcast into a message as JSON\" do\n    msg = %Broadcast{topic: \"t\", event: \"e\", payload: %{m: 1}}\n    assert fastlane!(@serializer, msg) == @v2_fastlane_json\n  end\n\n  test \"fastlane!/1 raises when payload is not a map\" do\n    msg = %Broadcast{topic: \"t\", event: \"e\", payload: \"invalid\"}\n    assert_raise ArgumentError, fn -> fastlane!(@serializer, msg) end\n  end\n\n  describe \"binary encode\" do\n    test \"general pushed message\" do\n      push = <<\n        # push\n        0::size(8),\n        # join_ref_size\n        2,\n        # topic_size\n        5,\n        # event_size\n        5,\n        \"12\",\n        \"topic\",\n        \"event\",\n        101,\n        102,\n        103\n      >>\n\n      assert encode!(@serializer, %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: nil,\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }) == push\n    end\n\n    test \"encode with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert topic to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: \"12\",\n          ref: nil,\n          topic: String.duplicate(\"t\", 256),\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert event to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: \"12\",\n          ref: nil,\n          topic: \"topic\",\n          event: String.duplicate(\"e\", 256),\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert join_ref to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Message{\n          join_ref: String.duplicate(\"j\", 256),\n          ref: nil,\n          topic: \"topic\",\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n    end\n\n    test \"reply\" do\n      assert encode!(@serializer, %Phoenix.Socket.Reply{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               status: :ok,\n               payload: {:binary, <<101, 102, 103>>}\n             }) == @reply\n    end\n\n    test \"reply with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert ref to binary/, fn ->\n        encode!(@serializer, %Phoenix.Socket.Reply{\n          join_ref: \"12\",\n          ref: String.duplicate(\"r\", 256),\n          topic: \"topic\",\n          status: :ok,\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n    end\n\n    test \"fastlane\" do\n      assert fastlane!(@serializer, %Phoenix.Socket.Broadcast{\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }) == @broadcast\n    end\n\n    test \"fastlane with oversized headers\" do\n      assert_raise ArgumentError, ~r/unable to convert topic to binary/, fn ->\n        fastlane!(@serializer, %Phoenix.Socket.Broadcast{\n          topic: String.duplicate(\"t\", 256),\n          event: \"event\",\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n\n      assert_raise ArgumentError, ~r/unable to convert event to binary/, fn ->\n        fastlane!(@serializer, %Phoenix.Socket.Broadcast{\n          topic: \"topic\",\n          event: String.duplicate(\"e\", 256),\n          payload: {:binary, <<101, 102, 103>>}\n        })\n      end\n    end\n  end\n\n  describe \"binary decode\" do\n    test \"pushed message\" do\n      assert decode!(@serializer, @client_push, opcode: :binary) == %Phoenix.Socket.Message{\n               join_ref: \"12\",\n               ref: \"123\",\n               topic: \"topic\",\n               event: \"event\",\n               payload: {:binary, <<101, 102, 103>>}\n             }\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/test/channel_test.exs",
    "content": "defmodule Phoenix.Test.ChannelTest do\n  use ExUnit.Case, async: true\n\n  alias Phoenix.Socket\n  alias Phoenix.Socket.{Broadcast, Message}\n  alias __MODULE__.{UserSocket, Endpoint}\n\n  Application.put_env(:phoenix, Endpoint,\n    pubsub_server: Phoenix.Test.ChannelTest.PubSub,\n    server: false\n  )\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n\n    socket \"/socket\", UserSocket\n  end\n\n  defmodule EmptyChannel do\n    use Phoenix.Channel\n\n    def join(_, _, socket), do: {:ok, socket}\n\n    def handle_in(_event, _params, socket) do\n      {:reply, :ok, socket}\n    end\n  end\n\n  defmodule Channel do\n    use Phoenix.Channel\n\n    intercept [\"stop\"]\n\n    def join(\"foo:ok\", _, socket) do\n      {:ok, socket}\n    end\n\n    def join(\"foo:external\", _, socket) do\n      :ok = Phoenix.PubSub.subscribe(Phoenix.Test.ChannelTest.PubSub, \"external:topic\")\n      {:ok, socket}\n    end\n\n    def join(\"foo:timeout\", _, socket) do\n      Process.flag(:trap_exit, true)\n      {:ok, socket}\n    end\n\n    def join(\"foo:socket\", _, socket) do\n      socket = assign(socket, :hello, :world)\n      {:ok, socket, socket}\n    end\n\n    def join(\"foo:error\", %{\"error\" => reason}, _socket) do\n      {:error, %{reason: reason}}\n    end\n\n    def join(\"foo:crash\", %{}, _socket) do\n      :unknown\n    end\n\n    def join(\"foo:payload\", %{\"string\" => _payload}, socket) do\n      {:ok, socket}\n    end\n\n    def handle_in(\"broadcast\", broadcast, socket) do\n      broadcast_from!(socket, \"broadcast\", broadcast)\n      {:noreply, socket}\n    end\n\n    def handle_in(\"noreply\", %{\"req\" => arg}, socket) do\n      push(socket, \"noreply\", %{\"resp\" => arg})\n      {:noreply, socket}\n    end\n\n    def handle_in(\"reply\", %{\"req\" => arg}, socket) do\n      {:reply, {:ok, %{\"resp\" => arg}}, socket}\n    end\n\n    def handle_in(\"reply\", %{}, socket) do\n      {:reply, :ok, socket}\n    end\n\n    def handle_in(\"crash\", %{}, _socket) do\n      raise \"boom!\"\n    end\n\n    def handle_in(\"async_reply\", %{\"req\" => arg}, socket) do\n      ref = socket_ref(socket)\n      Task.start(fn -> reply(ref, {:ok, %{\"async_resp\" => arg}}) end)\n      {:noreply, socket}\n    end\n\n    def handle_in(\"stop\", %{\"reason\" => stop}, socket) do\n      {:stop, stop, socket}\n    end\n\n    def handle_in(\"stop_and_reply\", %{\"req\" => arg}, socket) do\n      {:stop, :shutdown, {:ok, %{\"resp\" => arg}}, socket}\n    end\n\n    def handle_in(\"stop_and_reply\", %{}, socket) do\n      {:stop, :shutdown, :ok, socket}\n    end\n\n    def handle_out(\"stop\", _payload, socket) do\n      {:stop, :shutdown, socket}\n    end\n\n    def handle_info(%Broadcast{event: event, payload: payload}, socket) do\n      push(socket, event, payload)\n      {:noreply, socket}\n    end\n\n    def handle_info(:stop, socket) do\n      {:stop, :shutdown, socket}\n    end\n\n    def handle_info(:push, socket) do\n      push(socket, \"info\", %{\"reason\" => \"push\"})\n      {:noreply, socket}\n    end\n\n    def handle_info(:broadcast, socket) do\n      broadcast_from(socket, \"info\", %{\"reason\" => \"broadcast\"})\n      {:noreply, socket}\n    end\n\n    def handle_call(:ping, _from, socket) do\n      {:reply, :pong, socket}\n    end\n\n    def handle_cast({:ping, ref, sender}, socket) do\n      send(sender, {ref, :pong})\n      {:noreply, socket}\n    end\n\n    def terminate(_reason, %{topic: \"foo:timeout\"}) do\n      :timer.sleep(:infinity)\n    end\n\n    def terminate(reason, socket) do\n      send(socket.transport_pid, {:terminate, reason})\n      :ok\n    end\n  end\n\n  defmodule CodeChangeChannel do\n    use Phoenix.Channel\n\n    def join(_topic, _params, socket), do: {:ok, socket}\n\n    def code_change(_old, _socket, _extra) do\n      {:error, :cant}\n    end\n  end\n\n  defmodule UserSocket do\n    use Phoenix.Socket\n\n    channel \"foo:*\", Channel, assigns: %{user_socket_assigns: true}\n\n    def connect(params, socket) do\n      if params[\"reject\"] == true do\n        :error\n      else\n        {:ok, socket}\n      end\n    end\n\n    def id(_), do: \"123\"\n  end\n\n  @endpoint Endpoint\n  @moduletag :capture_log\n  import Phoenix.ChannelTest\n\n  setup_all do\n    start_supervised! @endpoint\n    start_supervised! {Phoenix.PubSub, name: Phoenix.Test.ChannelTest.PubSub}\n    :ok\n  end\n\n  defp assert_graceful_exit(pid) do\n    assert_receive {:socket_close, ^pid, _}\n  end\n\n  ## socket\n\n  test \"socket/1\" do\n    assert %Socket{\n             endpoint: @endpoint,\n             handler: UserSocket,\n             pubsub_server: Phoenix.Test.ChannelTest.PubSub,\n             serializer: Phoenix.ChannelTest.NoopSerializer\n           } = socket(UserSocket)\n  end\n\n  test \"socket/3\" do\n    assert %Socket{\n             id: \"user:id\",\n             assigns: %{hello: :world},\n             endpoint: @endpoint,\n             pubsub_server: Phoenix.Test.ChannelTest.PubSub,\n             serializer: Phoenix.ChannelTest.NoopSerializer,\n             handler: UserSocket\n           } = socket(UserSocket, \"user:id\", %{hello: :world})\n  end\n\n  test \"socket/4\" do\n    pid = self()\n\n    task =\n      Task.async(fn ->\n        assert %Socket{\n                 id: \"user:id\",\n                 assigns: %{hello: :world},\n                 endpoint: @endpoint,\n                 pubsub_server: Phoenix.Test.ChannelTest.PubSub,\n                 serializer: Phoenix.ChannelTest.NoopSerializer,\n                 handler: UserSocket\n               } = socket(UserSocket, \"user:id\", %{hello: :world}, test_process: pid)\n      end)\n\n    Task.await(task)\n  end\n\n  ## join\n\n  test \"join/3 with success\" do\n    assert {:ok, socket, client} =\n             join(socket(UserSocket, \"id\", original: :assign), Channel, \"foo:socket\")\n\n    assert socket.channel == Channel\n    assert socket.endpoint == @endpoint\n    assert socket.pubsub_server == Phoenix.Test.ChannelTest.PubSub\n    assert socket.topic == \"foo:socket\"\n    assert {Phoenix.ChannelTest, _} = socket.transport\n    assert socket.transport_pid == self()\n    assert socket.serializer == Phoenix.ChannelTest.NoopSerializer\n    assert socket.assigns == %{hello: :world, original: :assign}\n    assert %{socket | joined: true} == client\n\n    {:links, links} = Process.info(self(), :links)\n    assert client.channel_pid in links\n\n    {:dictionary, dictionary} = Process.info(client.channel_pid, :dictionary)\n    assert dictionary[:\"$callers\"] == [self()]\n  end\n\n  test \"join/3 from another process\" do\n    socket = socket(UserSocket, \"id\", original: :assign)\n\n    assert {:ok, socket, client} =\n             Task.async(fn ->\n               join(socket, Channel, \"foo:socket\")\n              end)\n              |> Task.await()\n\n    assert %{socket | joined: true} == client\n  end\n\n  test \"join/3 with error reply\" do\n    assert {:error, %{reason: \"mybad\"}} =\n             join(socket(UserSocket), Channel, \"foo:error\", %{\"error\" => \"mybad\"})\n  end\n\n  test \"join/3 with crash\" do\n    Process.flag(:trap_exit, true)\n    Logger.put_process_level(self(), :none)\n    assert {:error, %{reason: \"join crashed\"}} = join(socket(UserSocket), Channel, \"foo:crash\")\n  end\n\n  ## handle_in\n\n  test \"assert_push with guards\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    push(socket, \"noreply\", %{\"req\" => \"foo\"})\n    assert_push \"noreply\", %{\"resp\" => resp} when is_binary(resp)\n  end\n\n  test \"pushes and receives pushed messages\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    ref = push(socket, \"noreply\", %{\"req\" => \"foo\"})\n    assert_push \"noreply\", %{\"resp\" => \"foo\"}\n    refute_reply ref, _status\n  end\n\n  test \"pushes and receives replies\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n\n    ref = push(socket, \"reply\", %{})\n    assert_reply ref, :ok\n    refute_push _status, _payload\n\n    ref = push(socket, \"reply\", %{\"req\" => \"foo\"})\n    assert_reply ref, :ok, %{\"resp\" => \"foo\"}\n  end\n\n  test \"works with list data structures\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    ref = push(socket, \"reply\", %{req: [%{bar: \"baz\"}, %{bar: \"foo\"}]})\n    assert_reply ref, :ok, %{\"resp\" => [%{\"bar\" => \"baz\"}, %{\"bar\" => \"foo\"}]}\n  end\n\n  test \"receives async replies\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n\n    ref = push(socket, \"async_reply\", %{\"req\" => \"foo\"})\n    assert_reply ref, :ok, %{\"async_resp\" => \"foo\"}\n  end\n\n  test \"crashed channel propagates exit\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    push(socket, \"crash\", %{})\n    pid = socket.channel_pid\n    assert_receive {:terminate, _}\n    assert_receive {:EXIT, ^pid, _}\n    refute_receive {:socket_close, _, _}\n  end\n\n  test \"pushes on stop\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    push(socket, \"stop\", %{\"reason\" => :normal})\n    pid = socket.channel_pid\n    assert_receive {:terminate, :normal}\n    assert_graceful_exit(pid)\n\n    # Pushing after stop doesn't crash the client/transport\n    Process.flag(:trap_exit, false)\n    push(socket, \"stop\", %{\"reason\" => :normal})\n  end\n\n  test \"pushes and receives replies on stop\" do\n    Process.flag(:trap_exit, true)\n\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    ref = push(socket, \"stop_and_reply\", %{})\n    assert_reply ref, :ok\n    pid = socket.channel_pid\n    assert_receive {:terminate, :shutdown}\n    assert_graceful_exit(pid)\n\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    ref = push(socket, \"stop_and_reply\", %{\"req\" => \"foo\"})\n    assert_reply ref, :ok, %{\"resp\" => \"foo\"}\n    pid = socket.channel_pid\n    assert_receive {:terminate, :shutdown}\n    assert_graceful_exit(pid)\n  end\n\n  test \"assert_broadcast with guards\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    push(socket, \"broadcast\", %{\"foo\" => \"bar\"})\n    assert_broadcast \"broadcast\", %{\"foo\" => resp} when is_binary(resp)\n  end\n\n  test \"pushes and broadcast messages\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    refute_broadcast \"broadcast\", _params\n    push(socket, \"broadcast\", %{\"foo\" => \"bar\"})\n    assert_broadcast \"broadcast\", %{\"foo\" => \"bar\"}\n  end\n\n  test \"connects and joins topics directly\" do\n    :error = connect(UserSocket, %{\"reject\" => true})\n    {:ok, socket} = connect(UserSocket, %{})\n    socket = subscribe_and_join!(socket, \"foo:ok\")\n    push(socket, \"broadcast\", %{\"foo\" => \"bar\"})\n    assert socket.id == \"123\"\n    assert_broadcast \"broadcast\", %{\"foo\" => \"bar\"}\n\n    {:ok, _, socket} = subscribe_and_join(socket, \"foo:ok\")\n    push(socket, \"broadcast\", %{\"foo\" => \"bar\"})\n    assert socket.id == \"123\"\n    assert_broadcast \"broadcast\", %{\"foo\" => \"bar\"}\n  end\n\n  test \"connects and joins topics directly, from another process\" do\n    pid = self()\n\n    task =\n      Task.async(fn ->\n        {:ok, socket} = connect(UserSocket, %{}, test_process: pid)\n        socket = subscribe_and_join!(socket, \"foo:ok\")\n        push(socket, \"broadcast\", %{\"foo\" => \"bar\"})\n        assert socket.id == \"123\"\n        assert_broadcast \"broadcast\", %{\"foo\" => \"bar\"}\n      end)\n\n    Task.await(task)\n  end\n\n  test \"pushes atom parameter keys as strings\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n\n    ref = push(socket, \"reply\", %{req: %{parameter: 1}})\n    assert_reply ref, :ok, %{\"resp\" => %{\"parameter\" => 1}}\n  end\n\n  test \"assert_reply with guards\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n\n    ref = push(socket, \"reply\", %{req: %{parameter: 1}})\n    assert_reply ref, :ok, %{\"resp\" => resp} when is_map(resp)\n  end\n\n  test \"pushes structs without modifying them\" do\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    date = ~D[2010-04-17]\n\n    ref = push(socket, \"reply\", %{req: date})\n    assert_reply ref, :ok, %{\"resp\" => ^date}\n  end\n\n  test \"connects with atom parameter keys as strings\" do\n    :error = connect(UserSocket, %{reject: true})\n  end\n\n  ## handle_out\n\n  test \"push broadcasts by default\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    broadcast_from!(socket, \"default\", %{\"foo\" => \"bar\"})\n    assert_push \"default\", %{\"foo\" => \"bar\"}\n  end\n\n  test \"push broadcasts by default, outside of test process\" do\n    pid = self()\n\n    task =\n      Task.async(fn ->\n        socket =\n          subscribe_and_join!(\n            socket(UserSocket, \"user_id\", %{some: :assign}, test_process: pid),\n            Channel,\n            \"foo:ok\"\n          )\n\n        broadcast_from!(socket, \"default\", %{\"foo\" => \"bar\"})\n        assert_push \"default\", %{\"foo\" => \"bar\"}\n      end)\n\n    Task.await(task)\n  end\n\n  test \"handles broadcasts and stops\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = subscribe_and_join(socket(UserSocket), Channel, \"foo:ok\")\n    broadcast_from!(socket, \"stop\", %{\"foo\" => \"bar\"})\n    pid = socket.channel_pid\n    assert_receive {:terminate, :shutdown}\n    assert_graceful_exit(pid)\n  end\n\n  ## handle_info\n\n  test \"handles messages and stops\" do\n    Process.flag(:trap_exit, true)\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    pid = socket.channel_pid\n    send(pid, :stop)\n    assert_receive {:terminate, :shutdown}\n    assert_graceful_exit(pid)\n  end\n\n  test \"handles messages and pushes\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    send(socket.channel_pid, :push)\n    assert_push \"info\", %{\"reason\" => \"push\"}\n  end\n\n  test \"handles messages and broadcasts\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    send(socket.channel_pid, :broadcast)\n    assert_broadcast \"info\", %{\"reason\" => \"broadcast\"}\n  end\n\n  ## handle_call/cast\n\n  test \"handles calls\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    assert GenServer.call(socket.channel_pid, :ping) == :pong\n  end\n\n  test \"handles casts\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:ok\")\n    ref = make_ref()\n    :ok = GenServer.cast(socket.channel_pid, {:ping, ref, self()})\n    assert_receive {^ref, :pong}\n  end\n\n  ## terminate\n\n  test \"leaves the channel\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    ref = leave(socket)\n    assert_reply ref, :ok\n\n    pid = socket.channel_pid\n    assert_receive {:terminate, {:shutdown, :left}}\n    assert_graceful_exit(pid)\n\n    # Leaving again doesn't crash\n    _ = leave(socket)\n  end\n\n  test \"closes the channel\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:ok\")\n    close(socket)\n\n    pid = socket.channel_pid\n    assert_receive {:terminate, {:shutdown, :closed}}\n    assert_receive {:EXIT, ^pid, {:shutdown, :closed}}\n\n    # Closing again doesn't crash\n    _ = close(socket)\n  end\n\n  test \"kills the channel when we reach timeout on close\" do\n    Process.flag(:trap_exit, true)\n    {:ok, _, socket} = join(socket(UserSocket), Channel, \"foo:timeout\")\n    close(socket, 0)\n\n    pid = socket.channel_pid\n    assert_receive {:EXIT, ^pid, :killed}\n    refute_received {:terminate, :killed}\n\n    # Closing again doesn't crash\n    _ = close(socket)\n  end\n\n  test \"code_change/3 proxies to channel\" do\n    socket = %Socket{channel: Channel}\n\n    assert Phoenix.Channel.Server.code_change(:old, socket, :extra) ==\n             {:ok, socket}\n  end\n\n  test \"code_change/3 is overridable\" do\n    socket = %Socket{channel: CodeChangeChannel}\n\n    assert Phoenix.Channel.Server.code_change(:old, socket, :extra) ==\n             {:error, :cant}\n  end\n\n  test \"external subscriptions\" do\n    socket = subscribe_and_join!(socket(UserSocket), Channel, \"foo:external\")\n    socket.endpoint.broadcast!(\"external:topic\", \"external_event\", %{one: 1})\n    assert_receive %Message{topic: \"foo:external\", event: \"external_event\", payload: %{one: 1}}\n  end\n\n  test \"warns on unhandled handle_info/2 messages\" do\n    socket = subscribe_and_join!(socket(UserSocket), EmptyChannel, \"topic\")\n\n    assert ExUnit.CaptureLog.capture_log(fn ->\n             send(socket.channel_pid, :unhandled)\n             ref = push(socket, \"hello\", %{})\n             assert_reply ref, :ok\n           end) =~ \"received unexpected message in handle_info/2: :unhandled\"\n  end\n\n  test \"subscribes to socket.id and receives disconnects\" do\n    {:ok, socket} = connect(UserSocket, %{})\n    socket.endpoint.broadcast!(socket.id, \"disconnect\", %{})\n    assert_broadcast \"disconnect\", %{}\n  end\n\n  test \"supports static assigns in user socket channel definition\" do\n    {:ok, socket} = connect(UserSocket, %{})\n    socket = subscribe_and_join!(socket, \"foo:ok\")\n    assert socket.assigns.user_socket_assigns\n  end\n\n  test \"converts payload on join to string keyed\" do\n    {:ok, socket} = connect(UserSocket, %{})\n    assert {:ok, _, _socket} = subscribe_and_join(socket, \"foo:payload\", %{string: nil})\n  end\nend\n"
  },
  {
    "path": "test/phoenix/test/conn_test.exs",
    "content": "defmodule Phoenix.Test.ConnTest.CatchAll do\n  defmodule ConnError do\n    defexception message: \"hello\", plug_status: 500\n  end\n\n  def init(opts), do: opts\n\n  def call(conn, :stat) do\n    case conn.params[\"action\"] do\n      \"conn_404\" -> raise ConnError, plug_status: 404\n      \"conn_400\" -> raise ConnError, plug_status: 400\n      \"runtime\" -> raise RuntimeError\n      \"send_400\" -> Plug.Conn.send_resp(conn, 400, \"\")\n    end\n  end\n\n  def call(conn, _opts), do: Plug.Conn.assign(conn, :catch_all, true)\nend\n\nalias Phoenix.Test.ConnTest.CatchAll\n\ndefmodule Phoenix.Test.ConnTest.RedirRouter do\n  use Phoenix.Router\n  get \"/\", CatchAll, :foo\n  get \"/posts/:id\", CatchAll, :some_action\nend\n\ndefmodule Phoenix.Test.ConnTest.Router do\n  use Phoenix.Router\n\n  pipeline :browser do\n    plug :put_bypass, :browser\n  end\n\n  pipeline :api do\n    plug :put_bypass, :api\n  end\n\n  scope \"/\" do\n    pipe_through :browser\n    get \"/stat\", CatchAll, :stat, private: %{route: :stat}\n    forward \"/redir\", Phoenix.Test.ConnTest.RedirRouter\n    forward \"/\", CatchAll\n  end\n\n  def put_bypass(conn, pipeline) do\n    bypassed = (conn.assigns[:bypassed] || []) ++ [pipeline]\n    Plug.Conn.assign(conn, :bypassed, bypassed)\n  end\nend\n\ndefmodule Phoenix.Test.ConnTest do\n  use ExUnit.Case, async: true\n  import Plug.Conn\n  import Phoenix.ConnTest\n  alias Phoenix.Test.ConnTest.{Router, RedirRouter}\n\n  @moduletag :capture_log\n\n  Application.put_env(:phoenix, Phoenix.Test.ConnTest.Endpoint, [])\n\n  defmodule Endpoint do\n    use Phoenix.Endpoint, otp_app: :phoenix\n    def init(opts), do: opts\n    def call(conn, :set), do: resp(conn, 200, \"ok\")\n\n    def call(conn, opts) do\n      put_in(super(conn, opts).private[:endpoint], opts)\n      |> Router.call(Router.init([]))\n    end\n  end\n\n  @endpoint Endpoint\n\n  setup_all do\n    Logger.put_process_level(self(), :none)\n    Endpoint.start_link()\n    :ok\n  end\n\n  test \"build_conn/0 returns a new connection\" do\n    conn = build_conn()\n    assert conn.method == \"GET\"\n    assert conn.path_info == []\n    assert conn.private.plug_skip_csrf_protection\n    assert conn.private.phoenix_recycled\n  end\n\n  test \"build_conn/2 returns a new connection\" do\n    conn = build_conn(:post, \"/hello\")\n    assert conn.method == \"POST\"\n    assert conn.path_info == [\"hello\"]\n    assert conn.private.plug_skip_csrf_protection\n    assert conn.private.phoenix_recycled\n  end\n\n  test \"dispatch/5 with path\" do\n    conn = post build_conn(), \"/hello\", foo: \"bar\"\n    assert conn.method == \"POST\"\n    assert conn.path_info == [\"hello\"]\n    assert conn.params == %{\"foo\" => \"bar\"}\n    assert conn.private.endpoint == []\n    refute conn.private.phoenix_recycled\n  end\n\n  test \"dispatch/5 with action\" do\n    conn = post build_conn(), :hello, %{foo: \"bar\"}\n    assert conn.method == \"POST\"\n    assert conn.path_info == []\n    assert conn.params == %{\"foo\" => \"bar\"}\n    assert conn.private.endpoint == :hello\n    refute conn.private.phoenix_recycled\n  end\n\n  test \"dispatch/5 with binary body\" do\n    assert_raise ArgumentError, fn ->\n      post build_conn(), :hello, \"foo=bar\"\n    end\n\n    conn =\n      build_conn()\n      |> put_req_header(\"content-type\", \"application/json\")\n      |> post(:hello, \"[1, 2, 3]\")\n      |> Plug.Parsers.call(\n        Plug.Parsers.init(parsers: [:json], json_decoder: Phoenix.json_library())\n      )\n\n    assert conn.method == \"POST\"\n    assert conn.path_info == []\n    assert conn.params == %{\"_json\" => [1, 2, 3]}\n  end\n\n  test \"dispatch/5 with recycling\" do\n    conn =\n      build_conn()\n      |> put_req_header(\"hello\", \"world\")\n      |> post(:hello)\n\n    assert get_req_header(conn, \"hello\") == [\"world\"]\n\n    conn =\n      conn\n      |> put_req_header(\"hello\", \"skipped\")\n      |> post(:hello)\n\n    assert get_req_header(conn, \"hello\") == []\n\n    conn =\n      conn\n      |> recycle()\n      |> put_req_header(\"hello\", \"world\")\n      |> post(:hello)\n\n    assert get_req_header(conn, \"hello\") == [\"world\"]\n  end\n\n  test \"dispatch/5 with :set state automatically sends\" do\n    conn = get(build_conn(), :set)\n    assert conn.state == :sent\n    assert conn.status == 200\n    assert conn.resp_body == \"ok\"\n    refute conn.private.phoenix_recycled\n  end\n\n  describe \"recycle/1\" do\n    test \"relevant request headers are persisted\" do\n      conn =\n        build_conn()\n        |> get(\"/\")\n        |> put_req_header(\"accept\", \"text/html\")\n        |> put_req_header(\"accept-language\", \"ja\")\n        |> put_req_header(\"authorization\", \"Bearer mytoken\")\n        |> put_req_header(\"hello\", \"world\")\n\n      conn = conn |> recycle()\n      assert get_req_header(conn, \"accept\") == [\"text/html\"]\n      assert get_req_header(conn, \"accept-language\") == [\"ja\"]\n      assert get_req_header(conn, \"authorization\") == [\"Bearer mytoken\"]\n      assert get_req_header(conn, \"hello\") == []\n    end\n\n    test \"host is persisted\" do\n      conn =\n        build_conn(:get, \"http://localhost/\", nil)\n        |> recycle()\n\n      assert conn.host == \"localhost\"\n    end\n\n    test \"remote_ip is persisted\" do\n      conn =\n        %{build_conn(:get, \"http://localhost/\", nil) | remote_ip: {192, 168, 0, 1}}\n        |> recycle()\n\n      assert conn.remote_ip == {192, 168, 0, 1}\n    end\n\n    test \"cookies are persisted\" do\n      conn =\n        build_conn()\n        |> get(\"/\")\n        |> put_req_cookie(\"req_cookie\", \"req_cookie\")\n        |> put_req_cookie(\"del_cookie\", \"del_cookie\")\n        |> put_req_cookie(\"over_cookie\", \"pre_cookie\")\n        |> put_resp_cookie(\"over_cookie\", \"pos_cookie\")\n        |> put_resp_cookie(\"resp_cookie\", \"resp_cookie\")\n        |> delete_resp_cookie(\"del_cookie\")\n\n      conn = conn |> recycle() |> fetch_cookies()\n\n      assert conn.cookies == %{\n               \"req_cookie\" => \"req_cookie\",\n               \"over_cookie\" => \"pos_cookie\",\n               \"resp_cookie\" => \"resp_cookie\"\n             }\n    end\n\n    test \"peer data is persisted\" do\n      peer_data = %{\n        address: {127, 0, 0, 1},\n        port: 111_317,\n        ssl_cert: <<1, 2, 3, 4>>\n      }\n\n      conn =\n        build_conn()\n        |> Plug.Test.put_peer_data(peer_data)\n\n      conn = conn |> recycle()\n      assert Plug.Conn.get_peer_data(conn) == peer_data\n    end\n  end\n\n  describe \"recycle/2\" do\n    test \"custom request headers are persisted\" do\n      conn =\n        build_conn()\n        |> get(\"/\")\n        |> put_req_header(\"accept\", \"text/html\")\n        |> put_req_header(\"hello\", \"world\")\n        |> put_req_header(\"foo\", \"bar\")\n\n      conn = conn |> recycle(~w(hello accept))\n      assert get_req_header(conn, \"accept\") == [\"text/html\"]\n      assert get_req_header(conn, \"hello\") == [\"world\"]\n      assert get_req_header(conn, \"foo\") == []\n    end\n  end\n\n  test \"ensure_recycled/1\" do\n    conn =\n      build_conn()\n      |> put_req_header(\"hello\", \"world\")\n      |> ensure_recycled()\n\n    assert get_req_header(conn, \"hello\") == [\"world\"]\n\n    conn =\n      put_in(conn.private.phoenix_recycled, false)\n      |> ensure_recycled()\n\n    assert get_req_header(conn, \"hello\") == []\n  end\n\n  test \"put_req_header/3 and delete_req_header/3\" do\n    conn = build_conn(:get, \"/\")\n    assert get_req_header(conn, \"foo\") == []\n\n    conn = put_req_header(conn, \"foo\", \"bar\")\n    assert get_req_header(conn, \"foo\") == [\"bar\"]\n\n    conn = put_req_header(conn, \"foo\", \"baz\")\n    assert get_req_header(conn, \"foo\") == [\"baz\"]\n\n    conn = delete_req_header(conn, \"foo\")\n    assert get_req_header(conn, \"foo\") == []\n  end\n\n  test \"put_req_cookie/3 and delete_req_cookie/2\" do\n    conn = build_conn(:get, \"/\")\n    assert get_req_header(conn, \"cookie\") == []\n\n    conn = conn |> put_req_cookie(\"foo\", \"bar\")\n    assert get_req_header(conn, \"cookie\") == [\"foo=bar\"]\n\n    conn = conn |> delete_req_cookie(\"foo\")\n    assert get_req_header(conn, \"cookie\") == []\n  end\n\n  test \"response/2\" do\n    conn = build_conn(:get, \"/\")\n\n    assert conn |> resp(200, \"ok\") |> response(200) == \"ok\"\n    assert conn |> send_resp(200, \"ok\") |> response(200) == \"ok\"\n    assert conn |> send_resp(200, \"ok\") |> response(:ok) == \"ok\"\n\n    assert_raise RuntimeError,\n                 ~r\"expected connection to have a response but no response was set/sent\",\n                 fn ->\n                   build_conn(:get, \"/\") |> response(200)\n                 end\n\n    assert_raise RuntimeError,\n                 \"expected response with status 200, got: 404, with body:\\n\\\"oops\\\"\",\n                 fn ->\n                   build_conn(:get, \"/\") |> resp(404, \"oops\") |> response(200)\n                 end\n\n    assert_raise RuntimeError,\n                 \"expected response with status 200, got: 404, with body:\\n<<192>>\",\n                 fn ->\n                   build_conn(:get, \"/\") |> resp(404, <<192>>) |> response(200)\n                 end\n  end\n\n  test \"html_response/2\" do\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"text/html\")\n           |> resp(200, \"ok\")\n           |> html_response(:ok) == \"ok\"\n\n    assert_raise RuntimeError, \"no content-type was set, expected a html response\", fn ->\n      build_conn(:get, \"/\") |> resp(200, \"ok\") |> html_response(200)\n    end\n  end\n\n  test \"json_response/2\" do\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"application/json\")\n           |> resp(200, \"{}\")\n           |> json_response(:ok) == %{}\n\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"application/vnd.api+json\")\n           |> resp(200, \"{}\")\n           |> json_response(:ok) == %{}\n\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"application/vnd.collection+json\")\n           |> resp(200, \"{}\")\n           |> json_response(:ok) == %{}\n\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"application/vnd.hal+json\")\n           |> resp(200, \"{}\")\n           |> json_response(:ok) == %{}\n\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"application/ld+json\")\n           |> resp(200, \"{}\")\n           |> json_response(:ok) == %{}\n\n    assert_raise RuntimeError, \"no content-type was set, expected a json response\", fn ->\n      build_conn(:get, \"/\") |> resp(200, \"ok\") |> json_response(200)\n    end\n\n    assert_raise Jason.DecodeError, \"unexpected byte at position 0: 0x6F (\\\"o\\\")\", fn ->\n      build_conn(:get, \"/\")\n      |> put_resp_content_type(\"application/json\")\n      |> resp(200, \"ok\")\n      |> json_response(200)\n    end\n\n    assert_raise Jason.DecodeError, ~r/unexpected end of input at position 0/, fn ->\n      build_conn(:get, \"/\")\n      |> put_resp_content_type(\"application/json\")\n      |> resp(200, \"\")\n      |> json_response(200)\n    end\n\n    assert_raise RuntimeError,\n                 ~s(expected response with status 200, got: 400, with body:\\n) <>\n                   inspect(~s({\"error\": \"oh oh\"})),\n                 fn ->\n                   build_conn(:get, \"/\")\n                   |> put_resp_content_type(\"application/json\")\n                   |> resp(400, ~s({\"error\": \"oh oh\"}))\n                   |> json_response(200)\n                 end\n  end\n\n  test \"text_response/2\" do\n    assert build_conn(:get, \"/\")\n           |> put_resp_content_type(\"text/plain\")\n           |> resp(200, \"ok\")\n           |> text_response(:ok) == \"ok\"\n\n    assert_raise RuntimeError, \"no content-type was set, expected a text response\", fn ->\n      build_conn(:get, \"/\") |> resp(200, \"ok\") |> text_response(200)\n    end\n  end\n\n  test \"response_content_type/2\" do\n    conn = build_conn(:get, \"/\")\n\n    assert put_resp_content_type(conn, \"text/html\") |> response_content_type(:html) ==\n             \"text/html; charset=utf-8\"\n\n    assert put_resp_content_type(conn, \"text/plain\") |> response_content_type(:text) ==\n             \"text/plain; charset=utf-8\"\n\n    assert put_resp_content_type(conn, \"application/json\") |> response_content_type(:json) ==\n             \"application/json; charset=utf-8\"\n\n    assert_raise RuntimeError, \"no content-type was set, expected a html response\", fn ->\n      conn |> response_content_type(:html)\n    end\n\n    assert_raise RuntimeError,\n                 \"expected content-type for html, got: \\\"text/plain; charset=utf-8\\\"\",\n                 fn ->\n                   put_resp_content_type(conn, \"text/plain\") |> response_content_type(:html)\n                 end\n  end\n\n  test \"redirected_to/1\" do\n    conn =\n      build_conn(:get, \"/\")\n      |> put_resp_header(\"location\", \"new location\")\n      |> send_resp(302, \"foo\")\n\n    assert redirected_to(conn) == \"new location\"\n  end\n\n  test \"redirected_to/2\" do\n    Enum.each(300..308, fn status ->\n      conn =\n        build_conn(:get, \"/\")\n        |> put_resp_header(\"location\", \"new location\")\n        |> send_resp(status, \"foo\")\n\n      assert redirected_to(conn, status) == \"new location\"\n    end)\n  end\n\n  test \"redirected_to/2 with status atom\" do\n    conn =\n      build_conn(:get, \"/\")\n      |> put_resp_header(\"location\", \"new location\")\n      |> send_resp(301, \"foo\")\n\n    assert redirected_to(conn, :moved_permanently) == \"new location\"\n  end\n\n  test \"redirected_to/2 without header\" do\n    assert_raise RuntimeError, \"no location header was set on redirected_to\", fn ->\n      assert build_conn(:get, \"/\")\n             |> send_resp(302, \"ok\")\n             |> redirected_to()\n    end\n  end\n\n  test \"redirected_to/2 without redirection\" do\n    assert_raise RuntimeError, \"expected redirection with status 302, got: 200\", fn ->\n      build_conn(:get, \"/\")\n      |> put_resp_header(\"location\", \"new location\")\n      |> send_resp(200, \"ok\")\n      |> redirected_to()\n    end\n  end\n\n  test \"redirected_to/2 without response\" do\n    assert_raise RuntimeError,\n                 ~r\"expected connection to have redirected but no response was set/sent\",\n                 fn ->\n                   build_conn(:get, \"/\")\n                   |> redirected_to()\n                 end\n  end\n\n  describe \"redirected_params/2\" do\n    test \"with matching route\" do\n      conn =\n        build_conn(:get, \"/\")\n        |> RedirRouter.call(RedirRouter.init([]))\n        |> put_resp_header(\"location\", \"/posts/123\")\n        |> send_resp(302, \"foo\")\n\n      assert redirected_params(conn) == %{id: \"123\"}\n    end\n\n    test \"with matching forwarded route\" do\n      conn =\n        build_conn(:get, \"/redir\")\n        |> Router.call(Router.init([]))\n        |> put_resp_header(\"location\", \"/redir/posts/123\")\n        |> send_resp(302, \"foo\")\n\n      assert redirected_params(conn) == %{id: \"123\"}\n    end\n\n    test \"raises Phoenix.Router.NoRouteError for unmatched location\" do\n      conn =\n        build_conn(:get, \"/\")\n        |> RedirRouter.call(RedirRouter.init([]))\n        |> put_resp_header(\"location\", \"/unmatched\")\n        |> send_resp(302, \"foo\")\n\n      assert_raise Phoenix.Router.NoRouteError, fn ->\n        redirected_params(conn)\n      end\n    end\n\n    test \"without redirection\" do\n      assert_raise RuntimeError, \"expected redirection with status 302, got: 200\", fn ->\n        build_conn(:get, \"/\")\n        |> RedirRouter.call(RedirRouter.init([]))\n        |> put_resp_header(\"location\", \"new location\")\n        |> send_resp(200, \"ok\")\n        |> redirected_params()\n      end\n    end\n\n    test \"with custom status code\" do\n      Enum.each(300..308, fn status ->\n        conn =\n          build_conn(:get, \"/\")\n          |> RedirRouter.call(RedirRouter.init([]))\n          |> put_resp_header(\"location\", \"/posts/123\")\n          |> send_resp(status, \"foo\")\n\n        assert redirected_params(conn, status) == %{id: \"123\"}\n      end)\n    end\n  end\n\n  describe \"path_params/1\" do\n    test \"with matching route\" do\n      conn = RedirRouter.call(build_conn(:get, \"/\"), RedirRouter.init([]))\n\n      assert path_params(conn, \"/posts/123\") == %{id: \"123\"}\n    end\n\n    test \"raises Phoenix.Router.NoRouteError for unmatched location\" do\n      conn = RedirRouter.call(build_conn(:get, \"/\"), RedirRouter.init([]))\n\n      assert_raise Phoenix.Router.NoRouteError, fn ->\n        path_params(conn, \"/unmatched\")\n      end\n    end\n  end\n\n  test \"bypass_through/3 bypasses route match and invokes pipeline\" do\n    conn = get(build_conn(), \"/\")\n    assert conn.assigns[:catch_all]\n\n    conn =\n      build_conn()\n      |> bypass_through(Router, :browser)\n      |> get(\"/stat\")\n\n    assert conn.assigns[:bypassed] == [:browser]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n\n    conn =\n      build_conn()\n      |> bypass_through(Router, [:api])\n      |> get(\"/stat\")\n\n    assert conn.assigns[:bypassed] == [:api]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n\n    conn =\n      build_conn()\n      |> bypass_through(Router, [:browser, :api])\n      |> get(\"/stat\")\n\n    assert conn.assigns[:bypassed] == [:browser, :api]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n  end\n\n  test \"bypass_through/3 with empty pipeline\" do\n    conn =\n      build_conn()\n      |> bypass_through(Router, [])\n      |> get(\"/stat\")\n\n    refute conn.assigns[:bypassed]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n  end\n\n  test \"bypass_through/2 with route pipeline\" do\n    conn =\n      build_conn()\n      |> bypass_through(Router)\n      |> get(\"/stat\")\n\n    assert conn.assigns[:bypassed] == [:browser]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n  end\n\n  test \"bypass_through/1 without router\" do\n    conn =\n      build_conn()\n      |> bypass_through()\n      |> get(\"/stat\")\n\n    refute conn.assigns[:bypassed]\n    assert conn.private[:route] == :stat\n    refute conn.assigns[:catch_all]\n  end\n\n  test \"assert_error_sent/2 with expected error response\" do\n    response =\n      assert_error_sent :not_found, fn ->\n        get(build_conn(), \"/stat\", action: \"conn_404\")\n      end\n\n    assert {404, [_h | _t], \"404.html from Phoenix.ErrorView\"} = response\n\n    response =\n      assert_error_sent 400, fn ->\n        get(build_conn(), \"/stat\", action: \"conn_400\")\n      end\n\n    assert {400, [_h | _t], \"400.html from Phoenix.ErrorView\"} = response\n  end\n\n  test \"assert_error_sent/2 with status mismatch assertion\" do\n    assert_raise ExUnit.AssertionError,\n                 ~r/expected error to be sent as 400 status, but got 500 from.*RuntimeError/s,\n                 fn ->\n                   assert_error_sent 400, fn ->\n                     get(build_conn(), \"/stat\", action: \"runtime\")\n                   end\n                 end\n  end\n\n  test \"assert_error_sent/2 with no error\" do\n    assert_raise ExUnit.AssertionError,\n                 ~r/expected error to be sent as 404 status, but no error happened/,\n                 fn ->\n                   assert_error_sent 404, fn -> get(build_conn(), \"/\") end\n                 end\n  end\n\n  test \"assert_error_sent/2 with error but no response\" do\n    assert_raise ExUnit.AssertionError,\n                 ~r/expected error to be sent as 404 status, but got an error with no response from.*RuntimeError/s,\n                 fn ->\n                   assert_error_sent 404, fn -> raise \"oops\" end\n                 end\n  end\n\n  test \"assert_error_sent/2 with response but no error\" do\n    assert_raise ExUnit.AssertionError,\n                 ~r/expected error to be sent as 400 status, but response sent 400 without error/,\n                 fn ->\n                   assert_error_sent :bad_request, fn ->\n                     get(build_conn(), \"/stat\", action: \"send_400\")\n                   end\n                 end\n  end\n\n  for method <- [:get, :post, :put, :delete] do\n    @method method\n    test \"#{method} helper raises ArgumentError for mismatched conn\" do\n      assert_raise ArgumentError, ~r/expected first argument to #{@method} to be/, fn ->\n        unquote(@method)(\"/foo/bar\", %{baz: \"baz\"})\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "test/phoenix/token_test.exs",
    "content": "defmodule Phoenix.TokenTest do\n  use ExUnit.Case, async: true\n  alias Phoenix.Token\n\n  setup do\n    Logger.put_process_level(self(), :none)\n    :ok\n  end\n\n  defstruct [:endpoint]\n\n  defmodule TokenEndpoint do\n    def config(:secret_key_base), do: \"abc123\"\n  end\n\n  describe \"sign and verify\" do\n    test \"token with string\" do\n      id = 1\n      key = String.duplicate(\"abc123\", 5)\n      token = Token.sign(key, \"id\", id)\n      assert Token.verify(key, \"id\", token) == {:ok, id}\n    end\n\n    test \"token with connection\" do\n      id = 1\n      token = Token.sign(conn(), \"id\", id)\n      assert Token.verify(conn(), \"id\", token) == {:ok, id}\n    end\n\n    test \"token with socket\" do\n      id = 1\n      token = Token.sign(socket(), \"id\", id)\n      assert Token.verify(socket(), \"id\", token) == {:ok, id}\n\n      id = 1\n      token = Token.sign(%__MODULE__{endpoint: TokenEndpoint}, \"id\", id)\n      assert Token.verify(%__MODULE__{endpoint: TokenEndpoint}, \"id\", token) == {:ok, id}\n    end\n\n    test \"fails on missing token\" do\n      assert Token.verify(TokenEndpoint, \"id\", nil) == {:error, :missing}\n    end\n\n    test \"fails on invalid token\" do\n      token = Token.sign(TokenEndpoint, \"id\", 1)\n\n      assert Token.verify(TokenEndpoint, \"id\", \"garbage\") ==\n               {:error, :invalid}\n\n      assert Token.verify(TokenEndpoint, \"not_id\", token) ==\n               {:error, :invalid}\n    end\n\n    test \"supports max age in seconds\" do\n      token = Token.sign(conn(), \"id\", 1)\n      assert Token.verify(conn(), \"id\", token, max_age: 1000) == {:ok, 1}\n      assert Token.verify(conn(), \"id\", token, max_age: -1000) == {:error, :expired}\n      assert Token.verify(conn(), \"id\", token, max_age: 100) == {:ok, 1}\n      assert Token.verify(conn(), \"id\", token, max_age: -100) == {:error, :expired}\n      assert Token.verify(conn(), \"id\", token, max_age: 0) == {:error, :expired}\n\n      token = Token.sign(conn(), \"id\", 1)\n      assert Token.verify(conn(), \"id\", token, max_age: 0.1) == {:ok, 1}\n      :timer.sleep(150)\n      assert Token.verify(conn(), \"id\", token, max_age: 0.1) == {:error, :expired}\n    end\n\n    test \"supports :infinity for max age\" do\n      token = Token.sign(conn(), \"id\", 1)\n      assert Token.verify(conn(), \"id\", token, max_age: :infinity) == {:ok, 1}\n    end\n\n    test \"supports signed_at in seconds\" do\n      seconds_in_day = 24 * 60 * 60\n      day_ago_seconds = System.system_time(:second) - seconds_in_day\n      token = Token.sign(conn(), \"id\", 1, signed_at: day_ago_seconds)\n      assert Token.verify(conn(), \"id\", token, max_age: seconds_in_day + 1) == {:ok, 1}\n      assert Token.verify(conn(), \"id\", token, max_age: seconds_in_day - 1) == {:error, :expired}\n    end\n\n    test \"passes key_iterations options to key generator\" do\n      signed1 = Token.sign(conn(), \"id\", 1, signed_at: 0, key_iterations: 1)\n      signed2 = Token.sign(conn(), \"id\", 1, signed_at: 0, key_iterations: 2)\n      assert signed1 != signed2\n    end\n\n    test \"passes key_digest options to key generator\" do\n      signed1 = Token.sign(conn(), \"id\", 1, signed_at: 0, key_digest: :sha256)\n      signed2 = Token.sign(conn(), \"id\", 1, signed_at: 0, key_digest: :sha512)\n      assert signed1 != signed2\n    end\n\n    test \"key defaults\" do\n      signed1 = Token.sign(conn(), \"id\", 1, signed_at: 0)\n\n      signed2 =\n        Token.sign(conn(), \"id\", 1,\n          signed_at: 0,\n          key_length: 32,\n          key_digest: :sha256,\n          key_iterations: 1000\n        )\n\n      assert signed1 == signed2\n    end\n  end\n\n  describe \"encrypt and decrypt\" do\n    test \"token with string\" do\n      id = 1\n      key = String.duplicate(\"abc123\", 5)\n      token = Token.encrypt(key, \"secret\", id)\n      assert Token.decrypt(key, \"secret\", token) == {:ok, id}\n    end\n\n    test \"token with connection\" do\n      id = 1\n      token = Token.encrypt(conn(), \"secret\", id)\n      assert Token.decrypt(conn(), \"secret\", token) == {:ok, id}\n    end\n\n    test \"token with socket\" do\n      id = 1\n      token = Token.encrypt(socket(), \"secret\", id)\n      assert Token.decrypt(socket(), \"secret\", token) == {:ok, id}\n    end\n\n    test \"fails on missing token\" do\n      assert Token.decrypt(TokenEndpoint, \"secret\", nil) == {:error, :missing}\n    end\n\n    test \"fails on invalid token\" do\n      token = Token.encrypt(TokenEndpoint, \"secret\", 1)\n\n      assert Token.decrypt(TokenEndpoint, \"secret\", \"garbage\") ==\n               {:error, :invalid}\n\n      assert Token.decrypt(TokenEndpoint, \"not_secret\", token) ==\n               {:error, :invalid}\n    end\n\n    test \"supports max age in seconds\" do\n      token = Token.encrypt(conn(), \"secret\", 1)\n      assert Token.decrypt(conn(), \"secret\", token, max_age: 1000) == {:ok, 1}\n      assert Token.decrypt(conn(), \"secret\", token, max_age: -1000) == {:error, :expired}\n      assert Token.decrypt(conn(), \"secret\", token, max_age: 100) == {:ok, 1}\n      assert Token.decrypt(conn(), \"secret\", token, max_age: -100) == {:error, :expired}\n      assert Token.decrypt(conn(), \"secret\", token, max_age: 0) == {:error, :expired}\n\n      token = Token.encrypt(conn(), \"secret\", 1)\n      assert Token.decrypt(conn(), \"secret\", token, max_age: 0.1) == {:ok, 1}\n      :timer.sleep(150)\n      assert Token.decrypt(conn(), \"secret\", token, max_age: 0.1) == {:error, :expired}\n    end\n\n    test \"supports :infinity for max age\" do\n      token = Token.encrypt(conn(), \"secret\", 1)\n      assert Token.decrypt(conn(), \"secret\", token, max_age: :infinity) == {:ok, 1}\n    end\n\n    test \"supports signed_at in seconds\" do\n      seconds_in_day = 24 * 60 * 60\n      day_ago_seconds = System.system_time(:second) - seconds_in_day\n      token = Token.encrypt(conn(), \"secret\", 1, signed_at: day_ago_seconds)\n      assert Token.decrypt(conn(), \"secret\", token, max_age: seconds_in_day + 1) == {:ok, 1}\n\n      assert Token.decrypt(conn(), \"secret\", token, max_age: seconds_in_day - 1) ==\n               {:error, :expired}\n    end\n\n    test \"passes key_iterations options to key generator\" do\n      signed1 = Token.encrypt(conn(), \"secret\", 1, signed_at: 0, key_iterations: 1)\n      signed2 = Token.encrypt(conn(), \"secret\", 1, signed_at: 0, key_iterations: 2)\n      assert signed1 != signed2\n    end\n\n    test \"passes key_digest options to key generator\" do\n      signed1 = Token.encrypt(conn(), \"secret\", 1, signed_at: 0, key_digest: :sha256)\n      signed2 = Token.encrypt(conn(), \"secret\", 1, signed_at: 0, key_digest: :sha512)\n      assert signed1 != signed2\n    end\n  end\n\n  defp socket() do\n    %Phoenix.Socket{endpoint: TokenEndpoint}\n  end\n\n  defp conn() do\n    %Plug.Conn{} |> Plug.Conn.put_private(:phoenix_endpoint, TokenEndpoint)\n  end\nend\n"
  },
  {
    "path": "test/phoenix/verified_routes_test.exs",
    "content": "modules = [\n  Phoenix.VerifiedRoutesTest.PostController,\n  Phoenix.VerifiedRoutesTest.UserController\n]\n\nfor module <- modules do\n  defmodule module do\n    def init(opts), do: opts\n    def call(conn, _opts), do: conn\n  end\nend\n\ndefmodule PlugRouterWithVerifiedRoutes do\n  use Plug.Router\n\n  @behaviour Phoenix.VerifiedRoutes\n\n  get \"/foo\" do\n    send_resp(conn, 200, \"ok\")\n  end\n\n  @impl Phoenix.VerifiedRoutes\n  def formatted_routes(_plug_opts) do\n    [\n      %{verb: \"GET\", path: \"/foo\", label: \"Hello\"}\n    ]\n  end\n\n  @impl Phoenix.VerifiedRoutes\n  def verified_route?(_plug_opts, path) do\n    path == [\"foo\"]\n  end\nend\n\ndefmodule Phoenix.VerifiedRoutesTest do\n  use ExUnit.Case, async: true\n  import Plug.Test\n\n  @derive {Phoenix.Param, key: :slug}\n  defstruct [:id, :slug]\n\n  defmodule AdminRouter do\n    use Phoenix.Router\n\n    get \"/dashboard\", Phoenix.VerifiedRoutesTest.UserController, :index\n  end\n\n  defmodule Router do\n    use Phoenix.Router\n    alias Phoenix.VerifiedRoutesTest.{PostController, UserController}\n\n    get \"/posts/top\", PostController, :top\n    get \"/posts/bottom/:order/:count\", PostController, :bottom\n    get \"/posts/:id\", PostController, :show\n    get \"/posts/:id/info\", PostController, :show\n    get \"/posts/file/*file\", PostController, :file\n    get \"/posts/skip\", PostController, :skip\n    get \"/should-warn/*all\", PostController, :all, warn_on_verify: true\n    get \"/ø\", PostController, :unicode\n\n    scope \"/\", host: \"users.\" do\n      post \"/host_users/:id/info\", UserController, :create\n    end\n\n    scope \"/admin/new\" do\n      resources \"/messages\", PostController\n    end\n\n    get \"/\", PostController, :root\n\n    forward \"/router_forward\", AdminRouter\n    forward \"/plug_forward\", UserController\n\n    scope \"/:locale\" do\n      get \"/foo\", PostController, :show\n      get \"/bar\", PostController, :show\n    end\n  end\n\n  defmodule CatchAllWarningRouter do\n    use Phoenix.Router\n    alias Phoenix.VerifiedRoutesTest.PostController\n\n    get \"/\", PostController, :root\n    get \"/*path\", PostController, :root, warn_on_verify: true\n  end\n\n  defmodule ForwardedRouter do\n    use Phoenix.Router\n\n    forward \"/\", PlugRouterWithVerifiedRoutes\n  end\n\n  # Emulate regular endpoint functions\n\n  defmodule Endpoint do\n    def url, do: \"https://example.com\"\n    def static_url, do: \"https://static.example.com\"\n    def path(path), do: path\n    def static_path(path), do: path\n    def static_integrity(_path), do: nil\n  end\n\n  defmodule ScriptName do\n    def url, do: \"https://example.com\"\n    def static_url, do: \"https://static.example.com\"\n    def path(path), do: \"/api\" <> path\n    def static_path(path), do: \"/api\" <> path\n  end\n\n  defmodule StaticPath do\n    def url, do: \"https://example.com\"\n    def static_url, do: \"https://example.com\"\n    def path(path), do: path\n    def static_path(path), do: \"/static\" <> path\n    def static_integrity(_path), do: nil\n  end\n\n  defp conn_with_endpoint(endpoint \\\\ Endpoint) do\n    conn(:get, \"/\") |> Plug.Conn.put_private(:phoenix_endpoint, endpoint)\n  end\n\n  defp socket_with_endpoint(endpoint \\\\ Endpoint), do: %Phoenix.Socket{endpoint: endpoint}\n\n  def conn_with_script_name(script_name \\\\ ~w(api)) do\n    conn = Plug.Conn.put_private(conn(:get, \"/\"), :phoenix_endpoint, ScriptName)\n\n    put_in(conn.script_name, script_name)\n  end\n\n  defp uri_with_script_name do\n    %URI{scheme: \"https\", host: \"example.com\", port: 123, path: \"/api\"}\n  end\n\n  use Phoenix.VerifiedRoutes, statics: ~w(images), endpoint: Endpoint, router: Router\n\n  test \"~p with static string\" do\n    assert ~p\"/posts/1\" == \"/posts/1\"\n    assert ~p\"/posts/1?foo=bar\" == \"/posts/1?foo=bar\"\n  end\n\n  test \"~p with dynamic string uses Phoenix.Param\" do\n    struct = %__MODULE__{id: 123, slug: \"post-123\"}\n    assert ~p\"/posts/#{struct}\" == \"/posts/post-123\"\n    assert ~p\"/posts/#{123}\" == \"/posts/123\"\n    assert ~p|/posts/#{\"a b\"}| == \"/posts/a%20b\"\n  end\n\n  test \"~p with static and dynamic string and query params\" do\n    struct = %__MODULE__{id: 123, slug: \"post-123\"}\n\n    # static segments\n    assert ~p\"/posts/1?foo=bar\" == \"/posts/1?foo=bar\"\n    assert ~p\"/posts/bottom/asc/10?foo=bar\" == \"/posts/bottom/asc/10?foo=bar\"\n    assert path(conn_with_endpoint(), ~p\"/posts/1?foo=bar\") == \"/posts/1?foo=bar\"\n    assert path(@endpoint, @router, ~p\"/posts/1?foo=bar\") == \"/posts/1?foo=bar\"\n\n    assert path(conn_with_endpoint(), ~p\"/posts/bottom/asc/10?foo=bar\") ==\n             \"/posts/bottom/asc/10?foo=bar\"\n\n    assert path(@endpoint, @router, ~p\"/posts/bottom/asc/10?foo=bar\") ==\n             \"/posts/bottom/asc/10?foo=bar\"\n\n    # dynamic segments\n    assert ~p\"/posts/#{struct}?#{[page: 1, spaced: \"a b\"]}\" == \"/posts/post-123?page=1&spaced=a+b\"\n    assert ~p\"/posts/#{struct}?#{[page: 1, spaced: \"a b\"]}\" == \"/posts/post-123?page=1&spaced=a+b\"\n    id = 123\n    dir = \"asc\"\n    assert ~p\"/posts/#{id}?foo=bar\" == \"/posts/123?foo=bar\"\n    assert ~p\"/posts/post-#{id}?foo=bar\" == \"/posts/post-123?foo=bar\"\n    assert path(conn_with_endpoint(), ~p\"/posts/#{id}?foo=bar\") == \"/posts/123?foo=bar\"\n    assert path(@endpoint, @router, ~p\"/posts/#{id}?foo=bar\") == \"/posts/123?foo=bar\"\n\n    assert path(conn_with_endpoint(), ~p\"/posts/bottom/#{dir}/#{id}?foo=bar\") ==\n             \"/posts/bottom/asc/123?foo=bar\"\n\n    assert path(@endpoint, @router, ~p\"/posts/bottom/#{dir}/#{id}?foo=bar\") ==\n             \"/posts/bottom/asc/123?foo=bar\"\n\n    # dynamic query params\n    assert ~p\"/posts/1?other_post=#{id}\" == \"/posts/1?other_post=123\"\n    assert ~p\"/posts/1?other_post=#{struct}\" == \"/posts/1?other_post=post-123\"\n  end\n\n  test \"~p with dynamic string and static query params\" do\n    struct = %__MODULE__{id: 123, slug: \"post-123\"}\n    assert ~p\"/posts/#{struct}?foo=bar\" == \"/posts/post-123?foo=bar\"\n  end\n\n  test \"~p with scoped host\" do\n    assert ~p\"/host_users/1/info\" == \"/host_users/1/info\"\n  end\n\n  test \"~p on splat segments\" do\n    assert ~p|/posts/file/#{1}/#{\"2.jpg\"}| == \"/posts/file/1/2.jpg\"\n\n    location = [\"folder\", \"file.jpg\"]\n    assert ~p|/posts/file/#{location}| == \"/posts/file/folder/file.jpg\"\n  end\n\n  test \"~p URI encodes interpolated segments and query params\" do\n    assert ~p\"/posts/my path?#{[foo: \"my param\"]}\" == \"/posts/my%20path?foo=my+param\"\n    slug = \"my path\"\n    assert ~p\"/posts/#{slug}?#{[foo: \"my param\"]}\" == \"/posts/my%20path?foo=my+param\"\n  end\n\n  test \"~p with empty query string drops ?\" do\n    assert ~p\"/posts/5?#{%{}}\" == \"/posts/5\"\n  end\n\n  test \"~p with hash\" do\n    assert ~p\"/posts/123/info#bar\" == \"/posts/123/info#bar\"\n\n    warnings =\n      ExUnit.CaptureIO.capture_io(:stderr, fn ->\n        defmodule Hash do\n          use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n\n          def test, do: ~p\"/posts/123/info#bar\"\n        end\n      end)\n\n    refute warnings =~ \"no route path\"\n  after\n    :code.purge(__MODULE__.Hash)\n    :code.delete(__MODULE__.Hash)\n  end\n\n  test \":path_prefixes\" do\n    defmodule PathPrefixes do\n      use Phoenix.VerifiedRoutes,\n        endpoint: unquote(@endpoint),\n        router: unquote(@router),\n        path_prefixes: [{__MODULE__, :locale, [{1, 2, 3}]}],\n        statics: [\"images\"]\n\n      def locale({1, 2, 3}), do: \"en\"\n      def foo, do: ~p\"/foo\"\n      def bar, do: ~p\"/bar\"\n      def images_baz, do: ~p\"/images/baz\"\n    end\n\n    assert PathPrefixes.foo() == \"/en/foo\"\n    assert PathPrefixes.bar() == \"/en/bar\"\n    assert PathPrefixes.images_baz() == \"/images/baz\"\n  end\n\n  test \"unverified_path\" do\n    assert unverified_path(conn_with_script_name(), @router, \"/posts\") == \"/api/posts\"\n    assert unverified_path(@endpoint, @router, \"/posts\") == \"/posts\"\n    assert unverified_path(@endpoint, @router, \"/posts\", %{}) == \"/posts\"\n    assert unverified_path(@endpoint, @router, \"/posts\", a: \"b\") == \"/posts?a=b\"\n  end\n\n  test \"unverified_url\" do\n    assert unverified_url(conn_with_script_name(), \"/posts\") == \"https://example.com/posts\"\n    assert unverified_url(@endpoint, \"/posts\") == \"https://example.com/posts\"\n    assert unverified_url(@endpoint, \"/posts\", %{}) == \"https://example.com/posts\"\n    assert unverified_url(@endpoint, \"/posts\", a: \"b\") == \"https://example.com/posts?a=b\"\n  end\n\n  test \"~p raises on leftover sigil\" do\n    assert_raise ArgumentError, \"~p does not support modifiers after closing, got: foo\", fn ->\n      defmodule LeftOver do\n        use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n        def test, do: ~p\"/posts/1\"foo\n      end\n    end\n  after\n    :code.purge(__MODULE__.LeftOver)\n    :code.delete(__MODULE__.LeftOver)\n  end\n\n  test \"~p raises on dynamic interpolation\" do\n    msg = ~S|a dynamic ~p interpolation must follow a static segment, got: \"/posts/#{1}#{2}\"|\n\n    assert_raise ArgumentError, msg, fn ->\n      defmodule DynamicDynamic do\n        use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n        def test, do: ~p\"/posts/#{1}#{2}\"\n      end\n    end\n  after\n    :code.purge(__MODULE__.DynamicDynamic)\n    :code.delete(__MODULE__.DynamicDynamic)\n  end\n\n  test \"~p raises when not prefixed by /\" do\n    assert_raise ArgumentError,\n                 ~s|paths must begin with /, got: \"posts/1\"|,\n                 fn ->\n                   defmodule SigilPPrefix do\n                     use Phoenix.VerifiedRoutes,\n                       endpoint: unquote(@endpoint),\n                       router: unquote(@router)\n\n                     def test, do: ~p\"posts/1\"\n                   end\n                 end\n  after\n    :code.purge(__MODULE__.SigilPPrefix)\n    :code.delete(__MODULE__.SigilPPrefix)\n  end\n\n  test \"path arities\" do\n    assert path(Endpoint, ~p\"/posts/1\") == \"/posts/1\"\n    assert path(conn_with_endpoint(), ~p\"/posts/1\") == \"/posts/1\"\n    assert path(conn_with_script_name(), ~p\"/posts/1\") == \"/api/posts/1\"\n  end\n\n  test \"url arities\" do\n    assert url(~p\"/posts/1\") == \"https://example.com/posts/1\"\n    assert url(~p\"/posts/#{123}\") == \"https://example.com/posts/123\"\n    assert url(Endpoint, ~p\"/posts/1\") == \"https://example.com/posts/1\"\n    assert url(conn_with_endpoint(), ~p\"/posts/1\") == \"https://example.com/posts/1\"\n    assert url(conn_with_script_name(), ~p\"/posts/1\") == \"https://example.com/api/posts/1\"\n\n    assert url(Endpoint, Router, ~p\"/posts/1\") == \"https://example.com/posts/1\"\n    assert url(conn_with_endpoint(), Router, ~p\"/posts/1\") == \"https://example.com/posts/1\"\n    assert url(conn_with_script_name(), Router, ~p\"/posts/1\") == \"https://example.com/api/posts/1\"\n  end\n\n  test \"path raises when non ~p is passed\" do\n    assert_raise ArgumentError, ~r|expected compile-time ~p path string, got: \"/posts/1\"|, fn ->\n      defmodule MissingPathPrefix do\n        use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n        def test, do: path(%URI{}, \"/posts/1\")\n      end\n    end\n  after\n    :code.purge(__MODULE__.MissingPathPrefix)\n    :code.delete(__MODULE__.MissingPathPrefix)\n  end\n\n  test \"url raises when non ~p is passed\" do\n    assert_raise ArgumentError, ~r|expected compile-time ~p path string, got: \"/posts/1\"|, fn ->\n      defmodule MissingURLPrefix do\n        use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n        def test, do: url(\"/posts/1\")\n      end\n    end\n  after\n    :code.purge(__MODULE__.MissingURLPrefix)\n    :code.delete(__MODULE__.MissingURLPrefix)\n  end\n\n  test \"static_integrity\" do\n    assert is_nil(static_integrity(Endpoint, \"/images/foo.png\"))\n    assert is_nil(static_integrity(conn_with_endpoint(), \"/images/foo.png\"))\n    assert is_nil(static_integrity(socket_with_endpoint(), \"/images/foo.png\"))\n  end\n\n  test \"static paths\" do\n    assert path(Endpoint, ~p\"/images/foo.png\") == \"/images/foo.png\"\n    assert path(conn_with_endpoint(), ~p\"/images/foo.png\") == \"/images/foo.png\"\n    assert path(socket_with_endpoint(), ~p\"/images/foo.png\") == \"/images/foo.png\"\n  end\n\n  test \"~p dict query strings\" do\n    assert ~p\"/posts/5?#{[id: 3]}\" == \"/posts/5?id=3\"\n    assert ~p\"/posts/5?#{[foo: \"bar\"]}\" == \"/posts/5?foo=bar\"\n    assert ~p\"/posts/5?#{[foo: :bar]}\" == \"/posts/5?foo=bar\"\n    assert ~p\"/posts/5?#{[foo: true]}\" == \"/posts/5?foo=true\"\n    assert ~p\"/posts/5?#{[foo: false]}\" == \"/posts/5?foo=false\"\n    assert ~p\"/posts/5?#{[foo: nil]}\" == \"/posts/5?foo=\"\n\n    assert ~p\"/posts/5?#{[foo: ~w(bar baz)]}\" ==\n             \"/posts/5?foo[]=bar&foo[]=baz\"\n\n    assert ~p\"/posts/5?#{[foo: %{id: 5}]}\" ==\n             \"/posts/5?foo[id]=5\"\n\n    assert ~p\"/posts/5?#{[foo: %{__struct__: Foo, id: 5}]}\" ==\n             \"/posts/5?foo=5\"\n\n    # {key, value} params pairs are sorted\n    assert ~p\"/posts/5?#{[b: 2, a: 1, c: 3]}\" == \"/posts/5?a=1&b=2&c=3\"\n    assert ~p\"/posts/5?#{%{b: 2, a: 1, c: 3}}\" == \"/posts/5?a=1&b=2&c=3\"\n    # array values are sorted\n    assert ~p\"/posts/5?#{[foo: ~w(b a)]}\" == \"/posts/5?foo[]=a&foo[]=b\"\n    # ampersands are escaped and won't mess with splitting query at '&'\n    assert ~p\"/posts/5?#{[foo: \"bar\", \"a&b\": \"e&f\"]}\" == \"/posts/5?a%26b=e%26f&foo=bar\"\n  end\n\n  test \"~p mixed query string interpolation\" do\n    dir = \"asc\"\n    page = \"pg\"\n\n    assert ~p\"/posts/5?page=#{3}\" == \"/posts/5?page=3\"\n    assert ~p\"/posts/5?page=#{3}&dir=#{dir}\" == \"/posts/5?page=3&dir=asc\"\n    assert ~p\"/posts/5?#{page}=#{3}&dir=#{dir}\" == \"/posts/5?pg=3&dir=asc\"\n    assert ~p\"/posts/5?#{\"a b\"}=#{3}&dir=#{\"a b\"}\" == \"/posts/5?a+b=3&dir=a+b\"\n\n    assert ~p\"/posts/post?foo=bar&#{\"key\"}=#{\"val\"}&baz=bat\" ==\n             \"/posts/post?foo=bar&key=val&baz=bat\"\n\n    assert ~p\"/posts/#{page}?foo=bar&#{\"key\"}=#{\"val\"}&baz=bat\" ==\n             \"/posts/pg?foo=bar&key=val&baz=bat\"\n  end\n\n  test \"invalid mixed interpolation query string raises\" do\n    msg =\n      ~S|interpolated query string params must be separated by &, got: \"/posts/5?page=#{3}#{dir}\"|\n\n    assert_raise ArgumentError, msg, fn ->\n      defmodule InvalidQuery do\n        use Phoenix.VerifiedRoutes,\n          endpoint: unquote(@endpoint),\n          router: unquote(@router)\n\n        def test do\n          ~p\"/posts/5?page=#{3}#{dir}\" == \"/posts/5?page=3\"\n        end\n      end\n    end\n  after\n    :code.purge(__MODULE__.InvalidQuery)\n    :code.delete(__MODULE__.InvalidQuery)\n  end\n\n  test \"~p with complex ids\" do\n    assert ~p|/posts/#{\"==d--+\"}| == \"/posts/%3D%3Dd--%2B\"\n    assert ~p|/posts/top?#{[id: \"==d--+\"]}| == \"/posts/top?id=%3D%3Dd--%2B\"\n\n    assert ~p|/posts/file/#{\"==d--+\"}/#{\":O.jpg\"}| == \"/posts/file/%3D%3Dd--%2B/%3AO.jpg\"\n\n    assert ~p|/posts/file/#{\"==d--+\"}/#{\":O.jpg\"}?#{[xx: \"/=+/\"]}| ==\n             \"/posts/file/%3D%3Dd--%2B/%3AO.jpg?xx=%2F%3D%2B%2F\"\n  end\n\n  test \"~p with trailing slashes\" do\n    assert ~p\"/posts/5/\" == \"/posts/5/\"\n    assert ~p\"/posts/5/?#{[id: 5]}\" == \"/posts/5/?id=5\"\n    assert ~p\"/posts/5/?#{%{\"id\" => \"foo\"}}\" == \"/posts/5/?id=foo\"\n    assert ~p\"/posts/5/?#{%{\"id\" => \"foo bar\"}}\" == \"/posts/5/?id=foo+bar\"\n  end\n\n  test \"~p with unicode characters\" do\n    assert ~p\"/ø\" == \"/%C3%B8\"\n  end\n\n  describe \"with static path\" do\n    @endpoint StaticPath\n    @router Router\n    test \"paths use static prefix\" do\n      assert ~p\"/images/foo.png\" == \"/static/images/foo.png\"\n\n      assert path(conn_with_endpoint(StaticPath), ~p\"/images/foo.png\") ==\n               \"/static/images/foo.png\"\n\n      assert path(socket_with_endpoint(StaticPath), ~p\"/images/foo.png\") ==\n               \"/static/images/foo.png\"\n\n      assert url(conn_with_endpoint(StaticPath), ~p\"/images/foo.png\") ==\n               \"https://example.com/static/images/foo.png\"\n\n      assert url(socket_with_endpoint(StaticPath), ~p\"/images/foo.png\") ==\n               \"https://example.com/static/images/foo.png\"\n    end\n  end\n\n  describe \"with script name\" do\n    @endpoint ScriptName\n    @router Router\n\n    test \"paths use script name\" do\n      assert ~p\"/\" == \"/api/\"\n      assert path(ScriptName, Router, ~p\"/\") == \"/api/\"\n      assert path(conn_with_script_name(), ~p\"/\") == \"/api/\"\n      assert path(uri_with_script_name(), ~p\"/\") == \"/api/\"\n      assert path(ScriptName, Router, ~p\"/posts/5\") == \"/api/posts/5\"\n      assert ~p\"/posts/5\" == \"/api/posts/5\"\n      assert path(ScriptName, Router, ~p\"/posts/#{123}\") == \"/api/posts/123\"\n      assert ~p\"/posts/#{123}\" == \"/api/posts/123\"\n      assert path(uri_with_script_name(), ~p\"/posts/5\") == \"/api/posts/5\"\n      assert path(uri_with_script_name(), ~p\"/posts/#{123}\") == \"/api/posts/123\"\n    end\n\n    test \"urls use script name\" do\n      assert url(ScriptName, ~p\"/\") == \"https://example.com/api/\"\n      assert url(conn_with_script_name(~w(foo)), ~p\"/\") == \"https://example.com/foo/\"\n      assert url(uri_with_script_name(), ~p\"/\") == \"https://example.com:123/api/\"\n      assert url(ScriptName, ~p\"/posts/5\") == \"https://example.com/api/posts/5\"\n      assert url(ScriptName, ~p\"/posts/#{123}\") == \"https://example.com/api/posts/123\"\n      assert url(conn_with_script_name(), ~p\"/posts/5\") == \"https://example.com/api/posts/5\"\n\n      assert url(conn_with_script_name(~w(foo)), ~p\"/posts/5\") ==\n               \"https://example.com/foo/posts/5\"\n\n      assert url(uri_with_script_name(), ~p\"/posts/5\") == \"https://example.com:123/api/posts/5\"\n    end\n\n    test \"static use endpoint script name only\" do\n      assert path(conn_with_script_name(~w(foo)), ~p\"/images/foo.png\") ==\n               \"/api/images/foo.png\"\n\n      assert url(conn_with_script_name(~w(foo)), ~p\"/images/foo.png\") ==\n               \"https://static.example.com/api/images/foo.png\"\n    end\n\n    test \"phoenix_router_url with string takes precedence over endpoint\" do\n      url = \"https://phoenixframework.org\"\n      conn = Phoenix.Controller.put_router_url(conn_with_endpoint(), url)\n\n      assert url(conn, ~p\"/\") == url <> \"/\"\n      assert url(conn, ~p\"/admin/new/messages/1\") == url <> \"/admin/new/messages/1\"\n      assert url(conn, ~p\"/admin/new/messages/#{123}\") == url <> \"/admin/new/messages/123\"\n    end\n\n    test \"phoenix_router_url with URI takes precedence over endpoint\" do\n      uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123, path: \"/path\"}\n      conn = Phoenix.Controller.put_router_url(conn_with_endpoint(), uri)\n\n      assert url(conn, ~p\"/\") == \"https://phoenixframework.org:123/path/\"\n\n      assert url(conn, ~p\"/admin/new/messages/1\") ==\n               \"https://phoenixframework.org:123/path/admin/new/messages/1\"\n    end\n\n    test \"phoenix_static_url with string takes precedence over endpoint\" do\n      url = \"https://phoenixframework.org\"\n\n      conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), url)\n      assert url(conn, ~p\"/images/foo.png\") == url <> \"/images/foo.png\"\n\n      conn = Phoenix.Controller.put_static_url(conn_with_script_name(), url)\n      assert url(conn, ~p\"/images/foo.png\") == url <> \"/images/foo.png\"\n    end\n\n    test \"phoenix_static_url set to string with path results in static url with that path\" do\n      url = \"https://phoenixframework.org/path\"\n      conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), url)\n      assert url(conn, ~p\"/images/foo.png\") == url <> \"/images/foo.png\"\n\n      conn = Phoenix.Controller.put_static_url(conn_with_script_name(), url)\n      assert url(conn, ~p\"/images/foo.png\") == url <> \"/images/foo.png\"\n    end\n\n    test \"phoenix_static_url with URI takes precedence over endpoint\" do\n      uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123}\n\n      conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), uri)\n      assert url(conn, ~p\"/images/foo.png\") == \"https://phoenixframework.org:123/images/foo.png\"\n\n      conn = Phoenix.Controller.put_static_url(conn_with_script_name(), uri)\n      assert url(conn, ~p\"/images/foo.png\") == \"https://phoenixframework.org:123/images/foo.png\"\n    end\n\n    test \"phoenix_static_url set to URI with path results in static url with that path\" do\n      uri = %URI{scheme: \"https\", host: \"phoenixframework.org\", port: 123, path: \"/path\"}\n\n      conn = Phoenix.Controller.put_static_url(conn_with_endpoint(), uri)\n\n      assert url(conn, ~p\"/images/foo.png\") ==\n               \"https://phoenixframework.org:123/path/images/foo.png\"\n\n      conn = Phoenix.Controller.put_static_url(conn_with_script_name(), uri)\n\n      assert url(conn, ~p\"/images/foo.png\") ==\n               \"https://phoenixframework.org:123/path/images/foo.png\"\n    end\n  end\n\n  describe \"warnings\" do\n    test \"forwards\" do\n      warnings =\n        ExUnit.CaptureIO.capture_io(:stderr, fn ->\n          defmodule Forwards do\n            use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n\n            def test do\n              \"/router_forward/dashboard\" = ~p\"/router_forward/dashboard\"\n              \"/router_forward/warn\" = ~p\"/router_forward/warn\"\n              \"/plug_forward/home\" = ~p\"/plug_forward/home\"\n            end\n          end\n        end)\n\n      line = __ENV__.line - 6\n\n      warnings = String.replace(warnings, ~r/(\\x9B|\\x1B\\[)[0-?]*[ -\\/]*[@-~]/, \"\")\n\n      assert warnings =~\n               \"warning: no route path for Phoenix.VerifiedRoutesTest.Router matches \\\"/router_forward/warn\\\"\"\n\n      assert warnings =~\n               ~r\"test/phoenix/verified_routes_test.exs:#{line}:(\\d+:)? Phoenix.VerifiedRoutesTest.Forwards.test/0\"\n    after\n      :code.purge(__MODULE__.Forwards)\n      :code.delete(__MODULE__.Forwards)\n    end\n\n    test \"~p warns on unmatched path\" do\n      warnings =\n        ExUnit.CaptureIO.capture_io(:stderr, fn ->\n          defmodule Unmatched do\n            use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n\n            def test do\n              ~p\"/unknown\"\n              ~p\"/unknown/123\"\n              ~p\"/unknown/#{123}\"\n            end\n          end\n        end)\n\n      assert warnings =~\n               ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches \"/unknown\"|\n\n      assert warnings =~\n               ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches \"/unknown/123\"|\n\n      assert warnings =~\n               ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches \"/unknown/#{123}\"|\n    after\n      :code.purge(__MODULE__.Unmatched)\n      :code.delete(__MODULE__.Unmatched)\n    end\n\n    test \"~p warns on warn_on_verify: true route\" do\n      warnings =\n        ExUnit.CaptureIO.capture_io(:stderr, fn ->\n          defmodule VerifyFalse do\n            use Phoenix.VerifiedRoutes, endpoint: unquote(@endpoint), router: unquote(@router)\n\n            def test, do: ~p\"/should-warn/foobar\"\n          end\n        end)\n\n      assert warnings =~\n               ~s|no route path for Phoenix.VerifiedRoutesTest.Router matches \"/should-warn/foobar\"|\n    after\n      :code.purge(__MODULE__.VerifyFalse)\n      :code.delete(__MODULE__.VerifyFalse)\n    end\n\n    test \"~p does not warn if route without warn_on_verify: true matches first\" do\n      warnings =\n        ExUnit.CaptureIO.capture_io(:stderr, fn ->\n          defmodule VerifyFalseTrueMatchesFirst do\n            use Phoenix.VerifiedRoutes,\n              endpoint: unquote(@endpoint),\n              router: CatchAllWarningRouter\n\n            def test, do: ~p\"/\"\n          end\n        end)\n\n      refute warnings =~\n               \"no route path for Phoenix.VerifiedRoutesTest.CatchAllWarningRouter matches\"\n    after\n      :code.purge(__MODULE__.VerifyFalseTrueMatchesFirst)\n      :code.delete(__MODULE__.VerifyFalseTrueMatchesFirst)\n    end\n\n    test \"routers implementing verified routes behavior warn as expected\" do\n      warnings =\n        ExUnit.CaptureIO.capture_io(:stderr, fn ->\n          defmodule VerifyForwardedRouter do\n            use Phoenix.VerifiedRoutes,\n              endpoint: unquote(@endpoint),\n              router: PlugRouterWithVerifiedRoutes\n\n            def test, do: ~p\"/bar\"\n            def test2, do: ~p\"/foo\"\n          end\n        end)\n\n      assert warnings =~\n               \"no route path for PlugRouterWithVerifiedRoutes matches \\\"/bar\\\"\"\n\n      refute warnings =~\n               \"no route path for PlugRouterWithVerifiedRoutes matches \\\"/foo\\\"\"\n    after\n      :code.purge(__MODULE__.VerifyForwardedRouter)\n      :code.delete(__MODULE__.VerifyForwardedRouter)\n    end\n  end\nend\n"
  },
  {
    "path": "test/support/endpoint_helper.exs",
    "content": "defmodule Phoenix.Integration.EndpointHelper do\n  @moduledoc \"\"\"\n  Utility functions for integration testing endpoints.\n  \"\"\"\n\n  @doc \"\"\"\n  Finds `n` unused network port numbers.\n  \"\"\"\n  def get_unused_port_numbers(n) when is_integer(n) and n > 1 do\n    (1..n)\n    # Open up `n` sockets at the same time, so we don't get\n    # duplicate port numbers\n    |> Enum.map(&listen_on_os_assigned_port/1)\n    |> Enum.map(&get_port_number_and_close/1)\n  end\n\n  defp listen_on_os_assigned_port(_) do\n    {:ok, socket} = :gen_tcp.listen(0, [])\n    socket\n  end\n\n  defp get_port_number_and_close(socket) do\n    {:ok, port_number} = :inet.port(socket)\n    :gen_tcp.close(socket)\n    port_number\n  end\nend\n"
  },
  {
    "path": "test/support/http_client.exs",
    "content": "defmodule Phoenix.Integration.HTTPClient do\n  @doc \"\"\"\n  Performs HTTP Request and returns Response\n\n    * method - The http method, for example :get, :post, :put, etc\n    * url - The string url, for example \"http://example.com\"\n    * headers - The map of headers\n    * body - The optional string body. If the body is a map, it is converted\n      to a URI encoded string of parameters\n\n  ## Examples\n\n      iex> HTTPClient.request(:get, \"http://127.0.0.1\", %{})\n      {:ok, %Response{..})\n\n      iex> HTTPClient.request(:post, \"http://127.0.0.1\", %{}, param1: \"val1\")\n      {:ok, %Response{..})\n\n      iex> HTTPClient.request(:get, \"http://unknownhost\", %{}, param1: \"val1\")\n      {:error, ...}\n\n  \"\"\"\n  def request(method, url, headers, body \\\\ \"\")\n  def request(method, url, headers, body) when is_map body do\n    request(method, url, headers, URI.encode_query(body))\n  end\n  def request(method, url, headers, body) do\n    url     = String.to_charlist(url)\n    headers = headers |> Map.put_new(\"content-type\", \"text/html\")\n    ct_type = headers[\"content-type\"] |> String.to_charlist\n\n    header = Enum.map headers, fn {k, v} ->\n      {String.to_charlist(k), String.to_charlist(v)}\n    end\n\n    # Generate a random profile per request to avoid reuse\n    profile = :crypto.strong_rand_bytes(4) |> Base.encode16 |> String.to_atom\n    {:ok, pid} = :inets.start(:httpc, profile: profile)\n\n    resp =\n      case method do\n        :get -> :httpc.request(:get, {url, header}, [], [body_format: :binary], pid)\n        _    -> :httpc.request(method, {url, header, ct_type, body}, [], [body_format: :binary], pid)\n      end\n\n    :inets.stop(:httpc, pid)\n    format_resp(resp)\n  end\n\n  defp format_resp({:ok, {{_http, status, _status_phrase}, headers, body}}) do\n    {:ok, %{status: status, headers: headers, body: body}}\n  end\n  defp format_resp({:error, reason}), do: {:error, reason}\nend\n"
  },
  {
    "path": "test/support/router_helper.exs",
    "content": "defmodule RouterHelper do\n  @moduledoc \"\"\"\n  Conveniences for testing routers and controllers.\n\n  Must not be used to test endpoints as it does some\n  pre-processing (like fetching params) which could\n  skew endpoint tests.\n  \"\"\"\n\n  import Plug.Test\n\n  defmacro __using__(_) do\n    quote do\n      import Plug.Test\n      import Plug.Conn\n      import RouterHelper\n    end\n  end\n\n  def call(router, verb, path, params \\\\ nil, script_name \\\\ []) do\n    verb\n    |> conn(path, params)\n    |> Plug.Conn.fetch_query_params()\n    |> Map.put(:script_name, script_name)\n    |> router.call(router.init([]))\n  end\n\n  def action(controller, verb, action, params \\\\ nil) do\n    conn = conn(verb, \"/\", params) |> Plug.Conn.fetch_query_params\n    controller.call(conn, controller.init(action))\n  end\nend\n"
  },
  {
    "path": "test/support/websocket_client.exs",
    "content": "defmodule Phoenix.Integration.WebsocketClient do\n  @moduledoc \"\"\"\n  A WebSocket client used to test Phoenix.Channel\n  \"\"\"\n\n  use GenServer\n  import Kernel, except: [send: 2]\n\n  defstruct [\n    :conn,\n    :request_ref,\n    :websocket,\n    :caller,\n    :status,\n    :resp_headers,\n    :sender,\n    :serializer,\n    closing?: false,\n    topics: %{},\n    # Use different initial join_ref from ref to\n    # make sure the server is not coupling them.\n    join_ref: 11,\n    ref: 1\n  ]\n\n  alias Phoenix.Socket.Message\n\n  @doc \"\"\"\n  Starts the WebSocket client for given ws URL. `Phoenix.Socket.Message`s\n  received from the server are forwarded to the sender pid.\n  \"\"\"\n  def connect(sender, url, serializer, headers \\\\ []) do\n    with {:ok, socket} <- GenServer.start_link(__MODULE__, {sender, serializer}),\n         {:ok, :connected} <- GenServer.call(socket, {:connect, url, headers}) do\n      {:ok, socket}\n    end\n  end\n\n  @doc \"\"\"\n  Closes the socket\n  \"\"\"\n  def close(socket) do\n    GenServer.cast(socket, :close)\n  end\n\n  @doc \"\"\"\n  Sends an event to the WebSocket server per the message protocol.\n  \"\"\"\n  def send_event(socket, topic, event, msg) do\n    GenServer.call(socket, {:send, %Message{topic: topic, event: event, payload: msg}})\n  end\n\n  @doc \"\"\"\n  Sends a low-level text message to the client.\n  \"\"\"\n  def send(socket, msg) do\n    GenServer.call(socket, {:send, msg})\n  end\n\n  @doc \"\"\"\n  Sends a heartbeat event\n  \"\"\"\n  def send_heartbeat(socket) do\n    send_event(socket, \"phoenix\", \"heartbeat\", %{})\n  end\n\n  @doc \"\"\"\n  Sends join event to the WebSocket server per the Message protocol\n  \"\"\"\n  def join(socket, topic, msg) do\n    send_event(socket, topic, \"phx_join\", msg)\n  end\n\n  @doc \"\"\"\n  Sends leave event to the WebSocket server per the Message protocol\n  \"\"\"\n  def leave(socket, topic, msg) do\n    send_event(socket, topic, \"phx_leave\", msg)\n  end\n\n  ## GenServer implementation\n\n  @doc false\n  def init({sender, serializer}) do\n    state = %__MODULE__{sender: sender, serializer: serializer}\n\n    {:ok, state}\n  end\n\n  @doc false\n  def handle_call({:connect, url, headers}, from, state) do\n    uri = URI.parse(url)\n\n    http_scheme =\n      case uri.scheme do\n        \"ws\" -> :http\n        \"wss\" -> :https\n      end\n\n    ws_scheme =\n      case uri.scheme do\n        \"ws\" -> :ws\n        \"wss\" -> :wss\n      end\n\n    path =\n      case uri.query do\n        nil -> uri.path\n        query -> uri.path <> \"?\" <> query\n      end\n\n    with {:ok, conn} <- Mint.HTTP.connect(http_scheme, uri.host, uri.port),\n         {:ok, conn, ref} <- Mint.WebSocket.upgrade(ws_scheme, conn, path, headers) do\n      state = %{state | conn: conn, request_ref: ref, caller: from}\n      {:noreply, state}\n    else\n      {:error, reason} ->\n        {:reply, {:error, reason}, state}\n\n      {:error, conn, reason} ->\n        {:reply, {:error, reason}, put_in(state.conn, conn)}\n    end\n  end\n\n  def handle_call({:send, msg}, _from, state) do\n    {frame, state} = serialize_msg(msg, state)\n\n    case stream_frame(state, frame) do\n      {:ok, state} -> {:reply, :ok, state}\n      {:error, state, reason} -> {:reply, {:error, reason}, state}\n    end\n  end\n\n  @doc false\n  def handle_cast(:close, state) do\n    do_close(state)\n  end\n\n  defp do_close(state) do\n    # Streaming a close frame may fail if the server has already closed\n    # for writing.\n    _ = stream_frame(state, :close)\n    Mint.HTTP.close(state.conn)\n    {:stop, :normal, state}\n  end\n\n  @doc false\n  def handle_info(message, state) do\n    case Mint.WebSocket.stream(state.conn, message) do\n      {:ok, conn, responses} ->\n        state = put_in(state.conn, conn) |> handle_responses(responses)\n        if state.closing?, do: do_close(state), else: {:noreply, state}\n\n      {:error, conn, reason, _responses} ->\n        state = put_in(state.conn, conn) |> reply({:error, reason})\n        {:noreply, state}\n\n      :unknown ->\n        {:noreply, state}\n    end\n  end\n\n  defp handle_responses(state, responses)\n\n  defp handle_responses(%{request_ref: ref} = state, [{:status, ref, status} | rest]) do\n    put_in(state.status, status)\n    |> handle_responses(rest)\n  end\n\n  defp handle_responses(%{request_ref: ref} = state, [{:headers, ref, resp_headers} | rest]) do\n    put_in(state.resp_headers, resp_headers)\n    |> handle_responses(rest)\n  end\n\n  defp handle_responses(%{request_ref: ref} = state, [{:done, ref} | rest]) do\n    case Mint.WebSocket.new(state.conn, ref, state.status, state.resp_headers) do\n      {:ok, conn, websocket} ->\n        %{state | conn: conn, websocket: websocket, status: nil, resp_headers: nil}\n        |> reply({:ok, :connected})\n        |> handle_responses(rest)\n\n      {:error, conn, reason} ->\n        put_in(state.conn, conn)\n        |> reply({:error, reason})\n    end\n  end\n\n  defp handle_responses(%{request_ref: ref, websocket: websocket} = state, [\n         {:data, ref, data} | rest\n       ])\n       when websocket != nil do\n    case Mint.WebSocket.decode(websocket, data) do\n      {:ok, websocket, frames} ->\n        put_in(state.websocket, websocket)\n        |> handle_frames(frames)\n        |> handle_responses(rest)\n\n      {:error, websocket, reason} ->\n        put_in(state.websocket, websocket)\n        |> reply({:error, reason})\n    end\n  end\n\n  defp handle_responses(state, [_response | rest]) do\n    handle_responses(state, rest)\n  end\n\n  defp handle_responses(state, []), do: state\n\n  defp handle_frames(state, frames) do\n    {frames, state} =\n      Enum.flat_map_reduce(frames, state, fn\n        # reply to ping with pong\n        {:ping, data} = frame, state ->\n          {:ok, state} = stream_frame(state, {:pong, data})\n\n          {[frame], state}\n\n        # deserialize text and binary frames\n        {:text, text}, state ->\n          frame =\n            case state.serializer do\n              :noop -> {:text, text}\n              serializer -> serializer.decode!(text, opcode: :text)\n            end\n\n          {[frame], state}\n\n        {:binary, data}, state ->\n          {[binary_decode(data)], state}\n\n        # prepare to close the connection when a close frame is received\n        {:close, _code, _data}, state ->\n          {[], put_in(state.closing?, true)}\n\n        frame, state ->\n          {[frame], state}\n      end)\n\n    Enum.each(frames, &Kernel.send(state.sender, &1))\n\n    state\n  end\n\n  # Encodes a frame as a binary and sends it along the wire, keeping `conn`\n  # and `websocket` up to date in `state`.\n  defp stream_frame(state, frame) do\n    with {:ok, websocket, data} <- Mint.WebSocket.encode(state.websocket, frame),\n         state = put_in(state.websocket, websocket),\n         {:ok, conn} <- Mint.WebSocket.stream_request_body(state.conn, state.request_ref, data) do\n      {:ok, put_in(state.conn, conn)}\n    else\n      {:error, %Mint.WebSocket{} = websocket, reason} ->\n        {:error, put_in(state.websocket, websocket), reason}\n\n      {:error, conn, reason} ->\n        {:error, put_in(state.conn, conn), reason}\n    end\n  end\n\n  # reply to an open GenServer call request if there is one\n  defp reply(state, response) do\n    if state.caller, do: GenServer.reply(state.caller, response)\n    put_in(state.caller, nil)\n  end\n\n  defp serialize_msg(msg, %{serializer: :noop} = state), do: {msg, state}\n\n  defp serialize_msg(%Message{payload: {:binary, _}} = msg, %{ref: ref} = state) do\n    {join_ref, state} = join_ref_for(msg, state)\n    msg = Map.merge(msg, %{ref: to_string(ref), join_ref: to_string(join_ref)})\n    {{:binary, binary_encode_push!(msg)}, put_in(state.ref, ref + 1)}\n  end\n\n  defp serialize_msg(%Message{} = msg, %{ref: ref} = state) do\n    {join_ref, state} = join_ref_for(msg, state)\n    msg = Map.merge(msg, %{ref: to_string(ref), join_ref: to_string(join_ref)})\n    {{:text, encode!(msg, state)}, put_in(state.ref, ref + 1)}\n  end\n\n  defp serialize_msg(msg, state), do: {msg, state}\n\n  defp join_ref_for(\n         %{topic: topic, event: \"phx_join\"},\n         %{topics: topics, join_ref: join_ref} = state\n       ) do\n    topics = Map.put(topics, topic, join_ref)\n    {join_ref, %{state | topics: topics, join_ref: join_ref + 1}}\n  end\n\n  defp join_ref_for(%{topic: topic}, %{topics: topics} = state) do\n    {Map.get(topics, topic), state}\n  end\n\n  defp encode!(map, state) do\n    {:socket_push, :text, chardata} = state.serializer.encode!(map)\n    IO.chardata_to_string(chardata)\n  end\n\n  defp binary_encode_push!(%Message{payload: {:binary, data}} = msg) do\n    ref = to_string(msg.ref)\n    join_ref = to_string(msg.join_ref)\n    join_ref_size = byte_size(join_ref)\n    ref_size = byte_size(ref)\n    topic_size = byte_size(msg.topic)\n    event_size = byte_size(msg.event)\n\n    <<\n      0::size(8),\n      join_ref_size::size(8),\n      ref_size::size(8),\n      topic_size::size(8),\n      event_size::size(8),\n      join_ref::binary-size(join_ref_size),\n      ref::binary-size(ref_size),\n      msg.topic::binary-size(topic_size),\n      msg.event::binary-size(event_size),\n      data::binary\n    >>\n  end\n\n  # push\n  defp binary_decode(<<\n         0::size(8),\n         join_ref_size::size(8),\n         topic_size::size(8),\n         event_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         topic::binary-size(topic_size),\n         event::binary-size(event_size),\n         data::binary\n       >>) do\n    %Message{join_ref: join_ref, topic: topic, event: event, payload: {:binary, data}}\n  end\n\n  # reply\n  defp binary_decode(<<\n         1::size(8),\n         join_ref_size::size(8),\n         ref_size::size(8),\n         topic_size::size(8),\n         status_size::size(8),\n         join_ref::binary-size(join_ref_size),\n         ref::binary-size(ref_size),\n         topic::binary-size(topic_size),\n         status::binary-size(status_size),\n         data::binary\n       >>) do\n    payload = %{\"status\" => status, \"response\" => {:binary, data}}\n    %Message{join_ref: join_ref, ref: ref, topic: topic, event: \"phx_reply\", payload: payload}\n  end\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "Code.require_file(\"support/router_helper.exs\", __DIR__)\n\n# Starts web server applications\nApplication.ensure_all_started(:plug_cowboy)\n\n# Used whenever a router fails. We default to simply\n# rendering a short string.\ndefmodule Phoenix.ErrorView do\n  def render(\"404.json\", %{kind: kind, reason: _reason, stack: _stack, conn: conn}) do\n    %{error: \"Got 404 from #{kind} with #{conn.method}\"}\n  end\n\n  def render(template, %{conn: conn}) do\n    unless conn.private.phoenix_endpoint do\n      raise \"no endpoint in error view\"\n    end\n    \"#{template} from Phoenix.ErrorView\"\n  end\nend\n\n# For mix tests\nMix.shell(Mix.Shell.Process)\n\nassert_timeout = String.to_integer(\n  System.get_env(\"ELIXIR_ASSERT_TIMEOUT\") || \"200\"\n)\n\nexcludes =\n  if Version.match?(System.version(), \"~> 1.15\") do\n    []\n  else\n    [:mix_phx_new]\n  end\n\nExUnit.start(assert_receive_timeout: assert_timeout, exclude: excludes)\n"
  },
  {
    "path": "usage-rules/ecto.md",
    "content": "## Ecto Guidelines\n\n- **Always** preload Ecto associations in queries when they'll be accessed in templates, ie a message that needs to reference the `message.user.email`\n- Remember `import Ecto.Query` and other supporting modules when you write `seeds.exs`\n- `Ecto.Schema` fields always use the `:string` type, even for `:text`, columns, ie: `field :name, :string`\n- `Ecto.Changeset.validate_number/2` **DOES NOT SUPPORT the `:allow_nil` option**. By default, Ecto validations only run if a change for the given field exists and the change value is not nil, so such as option is never needed\n- You **must** use `Ecto.Changeset.get_field(changeset, :field)` to access changeset fields\n- Fields which are set programmatically, such as `user_id`, must not be listed in `cast` calls or similar for security purposes. Instead they must be explicitly set when creating the struct\n- **Always** invoke `mix ecto.gen.migration migration_name_using_underscores` when generating migration files, so the correct timestamp and conventions are applied\n"
  },
  {
    "path": "usage-rules/elixir.md",
    "content": "## Elixir guidelines\n\n- Elixir lists **do not support index based access via the access syntax**\n\n  **Never do this (invalid)**:\n\n      i = 0\n      mylist = [\"blue\", \"green\"]\n      mylist[i]\n\n  Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie:\n\n      i = 0\n      mylist = [\"blue\", \"green\"]\n      Enum.at(mylist, i)\n\n- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc\n  you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:\n\n      # INVALID: we are rebinding inside the `if` and the result never gets assigned\n      if connected?(socket) do\n        socket = assign(socket, :val, val)\n      end\n\n      # VALID: we rebind the result of the `if` to a new variable\n      socket =\n        if connected?(socket) do\n          assign(socket, :val, val)\n        end\n\n- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors\n- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets\n- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package)\n- Don't use `String.to_atom/1` on user input (memory leak risk)\n- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards\n- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)`\n- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option\n\n## Mix guidelines\n\n- Read the docs and options before using tasks (by using `mix help task_name`)\n- To debug test failures, run tests in a specific file with `mix test test/my_test.exs` or run all previously failed tests with `mix test --failed`\n- `mix deps.clean --all` is **almost never needed**. **Avoid** using it unless you have good reason\n\n## Test guidelines\n\n- **Always use `start_supervised!/1`** to start processes in tests as it guarantees cleanup between tests\n- **Avoid** `Process.sleep/1` and `Process.alive?/1` in tests\n  - Instead of sleeping to wait for a process to finish, **always** use `Process.monitor/1` and assert on the DOWN message:\n\n      ref = Process.monitor(pid)\n      assert_receive {:DOWN, ^ref, :process, ^pid, :normal}\n\n   - Instead of sleeping to synchronize before the next call, **always** use `_ = :sys.get_state/1` to ensure the process has handled prior messages\n\n"
  },
  {
    "path": "usage-rules/html.md",
    "content": "## Phoenix HTML guidelines\n\n- Phoenix templates **always** use `~H` or .html.heex files (known as HEEx), **never** use `~E`\n- **Always** use the imported `Phoenix.Component.form/1` and `Phoenix.Component.inputs_for/1` function to build forms. **Never** use `Phoenix.HTML.form_for` or `Phoenix.HTML.inputs_for` as they are outdated\n- When building forms **always** use the already imported `Phoenix.Component.to_form/2` (`assign(socket, form: to_form(...))` and `<.form for={@form} id=\"msg-form\">`), then access those forms in the template via `@form[:field]`\n- **Always** add unique DOM IDs to key elements (like forms, buttons, etc) when writing templates, these IDs can later be used in tests (`<.form for={@form} id=\"product-form\">`)\n- For \"app wide\" template imports, you can import/alias into the `my_app_web.ex`'s `html_helpers` block, so they will be available to all LiveViews, LiveComponent's, and all modules that do `use MyAppWeb, :html` (replace \"my_app\" by the actual app name)\n\n- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`**. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals.\n\n  **Never do this (invalid)**:\n\n      <%= if condition do %>\n        ...\n      <% else if other_condition %>\n        ...\n      <% end %>\n\n  Instead **always** do this:\n\n      <%= cond do %>\n        <% condition -> %>\n          ...\n        <% condition2 -> %>\n          ...\n        <% true -> %>\n          ...\n      <% end %>\n\n- HEEx require special tag annotation if you want to insert literal curly's like `{` or `}`. If you want to show a textual code snippet on the page in a `<pre>` or `<code>` block you *must* annotate the parent tag with `phx-no-curly-interpolation`:\n\n      <code phx-no-curly-interpolation>\n        let obj = {key: \"val\"}\n      </code>\n\n  Within `phx-no-curly-interpolation` annotated tags, you can use `{` and `}` without escaping them, and dynamic Elixir expressions can still be used with `<%= ... %>` syntax\n\n- HEEx class attrs support lists, but you must **always** use list `[...]` syntax. You can use the class list syntax to conditionally add classes, **always do this for multiple class values**:\n\n      <a class={[\n        \"px-2 text-white\",\n        @some_flag && \"py-5\",\n        if(@other_condition, do: \"border-red-500\", else: \"border-blue-100\"),\n        ...\n      ]}>Text</a>\n\n  and **always** wrap `if`'s inside `{...}` expressions with parens, like done above (`if(@other_condition, do: \"...\", else: \"...\")`)\n\n  and **never** do this, since it's invalid (note the missing `[` and `]`):\n\n      <a class={\n        \"px-2 text-white\",\n        @some_flag && \"py-5\"\n      }> ...\n      => Raises compile syntax error on invalid HEEx attr syntax\n\n- **Never** use `<% Enum.each %>` or non-for comprehensions for generating template content, instead **always** use `<%= for item <- @collection do %>`\n- HEEx HTML comments use `<%!-- comment --%>`. **Always** use the HEEx HTML comment syntax for template comments (`<%!-- comment --%>`)\n- HEEx allows interpolation via `{...}` and `<%= ... %>`, but the `<%= %>` **only** works within tag bodies. **Always** use the `{...}` syntax for interpolation within tag attributes, and for interpolation of values within tag bodies. **Always** interpolate block constructs (if, cond, case, for) within tag bodies using `<%= ... %>`.\n\n  **Always** do this:\n\n      <div id={@id}>\n        {@my_assign}\n        <%= if @some_block_condition do %>\n          {@another_assign}\n        <% end %>\n      </div>\n\n  and **Never** do this – the program will terminate with a syntax error:\n\n      <%!-- THIS IS INVALID NEVER EVER DO THIS --%>\n      <div id=\"<%= @invalid_interpolation %>\">\n        {if @invalid_block_construct do}\n        {end}\n      </div>\n"
  },
  {
    "path": "usage-rules/liveview.md",
    "content": "## Phoenix LiveView guidelines\n\n- **Never** use the deprecated `live_redirect` and `live_patch` functions, instead **always** use the `<.link navigate={href}>` and  `<.link patch={href}>` in templates, and `push_navigate` and `push_patch` functions LiveViews\n- **Avoid LiveComponent's** unless you have a strong, specific need for them\n- LiveViews should be named like `AppWeb.WeatherLive`, with a `Live` suffix. When you go to add LiveView routes to the router, the default `:browser` scope is **already aliased** with the `AppWeb` module, so you can just do `live \"/weather\", WeatherLive`\n\n### LiveView streams\n\n- **Always** use LiveView streams for collections for assigning regular lists to avoid memory ballooning and runtime termination with the following operations:\n  - basic append of N items - `stream(socket, :messages, [new_msg])`\n  - resetting stream with new items - `stream(socket, :messages, [new_msg], reset: true)` (e.g. for filtering items)\n  - prepend to stream - `stream(socket, :messages, [new_msg], at: -1)`\n  - deleting items - `stream_delete(socket, :messages, msg)`\n\n- When using the `stream/3` interfaces in the LiveView, the LiveView template must 1) always set `phx-update=\"stream\"` on the parent element, with a DOM id on the parent element like `id=\"messages\"` and 2) consume the `@streams.stream_name` collection and use the id as the DOM id for each child. For a call like `stream(socket, :messages, [new_msg])` in the LiveView, the template would be:\n\n      <div id=\"messages\" phx-update=\"stream\">\n        <div :for={{id, msg} <- @streams.messages} id={id}>\n          {msg.text}\n        </div>\n      </div>\n\n- LiveView streams are *not* enumerable, so you cannot use `Enum.filter/2` or `Enum.reject/2` on them. Instead, if you want to filter, prune, or refresh a list of items on the UI, you **must refetch the data and re-stream the entire stream collection, passing reset: true**:\n\n      def handle_event(\"filter\", %{\"filter\" => filter}, socket) do\n        # re-fetch the messages based on the filter\n        messages = list_messages(filter)\n\n        {:noreply,\n         socket\n         |> assign(:messages_empty?, messages == [])\n         # reset the stream with the new messages\n         |> stream(:messages, messages, reset: true)}\n      end\n\n- LiveView streams *do not support counting or empty states*. If you need to display a count, you must track it using a separate assign. For empty states, you can use Tailwind classes:\n\n      <div id=\"tasks\" phx-update=\"stream\">\n        <div class=\"hidden only:block\">No tasks yet</div>\n        <div :for={{id, task} <- @streams.tasks} id={id}>\n          {task.name}\n        </div>\n      </div>\n\n  The above only works if the empty state is the only HTML block alongside the stream for-comprehension.\n\n- When updating an assign that should change content inside any streamed item(s), you MUST re-stream the items\n  along with the updated assign:\n\n      def handle_event(\"edit_message\", %{\"message_id\" => message_id}, socket) do\n        message = Chat.get_message!(message_id)\n        edit_form = to_form(Chat.change_message(message, %{content: message.content}))\n\n        # re-insert message so @editing_message_id toggle logic takes effect for that stream item\n        {:noreply,\n         socket\n         |> stream_insert(:messages, message)\n         |> assign(:editing_message_id, String.to_integer(message_id))\n         |> assign(:edit_form, edit_form)}\n      end\n\n  And in the template:\n\n      <div id=\"messages\" phx-update=\"stream\">\n        <div :for={{id, message} <- @streams.messages} id={id} class=\"flex group\">\n          {message.username}\n          <%= if @editing_message_id == message.id do %>\n            <%!-- Edit mode --%>\n            <.form for={@edit_form} id=\"edit-form-#{message.id}\" phx-submit=\"save_edit\">\n              ...\n            </.form>\n          <% end %>\n        </div>\n      </div>\n\n- **Never** use the deprecated `phx-update=\"append\"` or `phx-update=\"prepend\"` for collections\n\n### LiveView JavaScript interop\n\n- Remember anytime you use `phx-hook=\"MyHook\"` and that JS hook manages its own DOM, you **must** also set the `phx-update=\"ignore\"` attribute\n- **Always** provide an unique DOM id alongside `phx-hook` otherwise a compiler error will be raised\n\nLiveView hooks come in two flavors, 1) colocated js hooks for \"inline\" scripts defined inside HEEx,\nand 2) external `phx-hook` annotations where JavaScript object literals are defined and passed to the `LiveSocket` constructor.\n\n#### Inline colocated js hooks\n\n**Never** write raw embedded `<script>` tags in heex as they are incompatible with LiveView.\nInstead, **always use a colocated js hook script tag (`:type={Phoenix.LiveView.ColocatedHook}`)\nwhen writing scripts inside the template**:\n\n    <input type=\"text\" name=\"user[phone_number]\" id=\"user-phone-number\" phx-hook=\".PhoneNumber\" />\n    <script :type={Phoenix.LiveView.ColocatedHook} name=\".PhoneNumber\">\n      export default {\n        mounted() {\n          this.el.addEventListener(\"input\", e => {\n            let match = this.el.value.replace(/\\D/g, \"\").match(/^(\\d{3})(\\d{3})(\\d{4})$/)\n            if(match) {\n              this.el.value = `${match[1]}-${match[2]}-${match[3]}`\n            }\n          })\n        }\n      }\n    </script>\n\n- colocated hooks are automatically integrated into the app.js bundle\n- colocated hooks names **MUST ALWAYS** start with a `.` prefix, i.e. `.PhoneNumber`\n\n#### External phx-hook\n\nExternal JS hooks (`<div id=\"myhook\" phx-hook=\"MyHook\">`) must be placed in `assets/js/` and passed to the\nLiveSocket constructor:\n\n    const MyHook = {\n      mounted() { ... }\n    }\n    let liveSocket = new LiveSocket(\"/live\", Socket, {\n      hooks: { MyHook }\n    });\n\n#### Pushing events between client and server\n\nUse LiveView's `push_event/3` when you need to push events/data to the client for a phx-hook to handle.\n**Always** return or rebind the socket on `push_event/3` when pushing events:\n\n    # re-bind socket so we maintain event state to be pushed\n    socket = push_event(socket, \"my_event\", %{...})\n\n    # or return the modified socket directly:\n    def handle_event(\"some_event\", _, socket) do\n      {:noreply, push_event(socket, \"my_event\", %{...})}\n    end\n\nPushed events can then be picked up in a JS hook with `this.handleEvent`:\n\n    mounted() {\n      this.handleEvent(\"my_event\", data => console.log(\"from server:\", data));\n    }\n\nClients can also push an event to the server and receive a reply with `this.pushEvent`:\n\n    mounted() {\n      this.el.addEventListener(\"click\", e => {\n        this.pushEvent(\"my_event\", { one: 1 }, reply => console.log(\"got reply from server:\", reply));\n      })\n    }\n\nWhere the server handled it via:\n\n    def handle_event(\"my_event\", %{\"one\" => 1}, socket) do\n      {:reply, %{two: 2}, socket}\n    end\n\n### LiveView tests\n\n- `Phoenix.LiveViewTest` module and `LazyHTML` (included) for making your assertions\n- Form tests are driven by `Phoenix.LiveViewTest`'s `render_submit/2` and `render_change/2` functions\n- Come up with a step-by-step test plan that splits major test cases into small, isolated files. You may start with simpler tests that verify content exists, gradually add interaction tests\n- **Always reference the key element IDs you added in the LiveView templates in your tests** for `Phoenix.LiveViewTest` functions like `element/2`, `has_element/2`, selectors, etc\n- **Never** tests again raw HTML, **always** use `element/2`, `has_element/2`, and similar: `assert has_element?(view, \"#my-form\")`\n- Instead of relying on testing text content, which can change, favor testing for the presence of key elements\n- Focus on testing outcomes rather than implementation details\n- Be aware that `Phoenix.Component` functions like `<.form>` might produce different HTML than expected. Test against the output HTML structure, not your mental model of what you expect it to be\n- When facing test failures with element selectors, add debug statements to print the actual HTML, but use `LazyHTML` selectors to limit the output, ie:\n\n      html = render(view)\n      document = LazyHTML.from_fragment(html)\n      matches = LazyHTML.filter(document, \"your-complex-selector\")\n      IO.inspect(matches, label: \"Matches\")\n\n### Form handling\n\n#### Creating a form from params\n\nIf you want to create a form based on `handle_event` params:\n\n    def handle_event(\"submitted\", params, socket) do\n      {:noreply, assign(socket, form: to_form(params))}\n    end\n\nWhen you pass a map to `to_form/1`, it assumes said map contains the form params, which are expected to have string keys.\n\nYou can also specify a name to nest the params:\n\n    def handle_event(\"submitted\", %{\"user\" => user_params}, socket) do\n      {:noreply, assign(socket, form: to_form(user_params, as: :user))}\n    end\n\n#### Creating a form from changesets\n\nWhen using changesets, the underlying data, form params, and errors are retrieved from it. The `:as` option is automatically computed too. E.g. if you have a user schema:\n\n    defmodule MyApp.Users.User do\n      use Ecto.Schema\n      ...\n    end\n\nAnd then you create a changeset that you pass to `to_form`:\n\n    %MyApp.Users.User{}\n    |> Ecto.Changeset.change()\n    |> to_form()\n\nOnce the form is submitted, the params will be available under `%{\"user\" => user_params}`.\n\nIn the template, the form form assign can be passed to the `<.form>` function component:\n\n    <.form for={@form} id=\"todo-form\" phx-change=\"validate\" phx-submit=\"save\">\n      <.input field={@form[:field]} type=\"text\" />\n    </.form>\n\nAlways give the form an explicit, unique DOM ID, like `id=\"todo-form\"`.\n\n#### Avoiding form errors\n\n**Always** use a form assigned via `to_form/2` in the LiveView, and the `<.input>` component in the template. In the template **always access forms this**:\n\n    <%!-- ALWAYS do this (valid) --%>\n    <.form for={@form} id=\"my-form\">\n      <.input field={@form[:field]} type=\"text\" />\n    </.form>\n\nAnd **never** do this:\n\n    <%!-- NEVER do this (invalid) --%>\n    <.form for={@changeset} id=\"my-form\">\n      <.input field={@changeset[:field]} type=\"text\" />\n    </.form>\n\n- You are FORBIDDEN from accessing the changeset in the template as it will cause errors\n- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset\n"
  },
  {
    "path": "usage-rules/phoenix.md",
    "content": "## Phoenix guidelines\n\n- Remember Phoenix router `scope` blocks include an optional alias which is prefixed for all routes within the scope. **Always** be mindful of this when creating routes within a scope to avoid duplicate module prefixes.\n\n- You **never** need to create your own `alias` for route definitions! The `scope` provides the alias, ie:\n\n      scope \"/admin\", AppWeb.Admin do\n        pipe_through :browser\n\n        live \"/users\", UserLive, :index\n      end\n\n  the UserLive route would point to the `AppWeb.Admin.UserLive` module\n\n- `Phoenix.View` no longer is needed or included with Phoenix, don't use it\n"
  }
]