[
  {
    "path": ".formatter.exs",
    "content": "# Used by \"mix format\"\n[\n  import_deps: [:phoenix],\n  inputs: [\"{mix,.formatter}.exs\", \"{config,examples,lib,test}/**/*.{ex,exs}\"]\n]\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-22.04\n    env:\n      MIX_ENV: test\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - pair:\n              elixir: \"1.15\"\n              otp: \"24.3.4.10\"\n          - pair:\n              elixir: \"1.18\"\n              otp: \"27.3.4.2\"\n            lint: lint\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: erlef/setup-beam@main\n        with:\n          otp-version: ${{ matrix.pair.otp }}\n          elixir-version: ${{ matrix.pair.elixir }}\n          version-type: strict\n\n      - uses: actions/cache@v4\n        with:\n          path: deps\n          key: mix-deps-${{ hashFiles('**/mix.lock') }}\n\n      - run: mix deps.get --check-locked\n\n      - run: mix format --check-formatted\n        if: ${{ matrix.lint }}\n\n      - run: mix deps.unlock --check-unused\n        if: ${{ matrix.lint }}\n\n      - run: mix deps.compile\n\n      - run: mix compile --warnings-as-errors\n        if: ${{ matrix.lint }}\n\n      - run: mix test\n        if: ${{ ! matrix.lint }}\n\n      - run: mix test --warnings-as-errors\n        if: ${{ matrix.lint }}\n"
  },
  {
    "path": ".gitignore",
    "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 third-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# Ignore package tarball (built via \"mix hex.build\").\nphoenix_playground-*.tar\n\n# Temporary files, for example, from tests.\n/tmp/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## v0.1.8 (2025-07-30)\n\n  * Use Phoenix LiveView v1.1.\n\n## v0.1.7 (2024-09-24)\n\n  * Delegate LiveView `handle_async/3`.\n\n## v0.1.6 (2024-09-03)\n\n  * Always use PhoenixPlayground.CodeReloader\n\n  * Add blank favicon to layout\n\n  * Add `:endpoint` option\n\n## v0.1.5 (2024-08-14)\n\n  * Fix live reloading.\n\n  * Add `:debug_errors` option.\n\n  * Document `PhoenixPlayground.Layout`.\n\n  * Add `examples/demo_router.exs`.\n\n## v0.1.4 (2024-07-18)\n\n  * Delegate LiveView `handle_params/3`.\n\n  * Add support for LiveView hooks.\n\n  * Add support for LiveView uploaders.\n\n  * Add support for `:page_title` assign.\n\n  * Add `:endpoint_options` option\n\n## v0.1.3 (2024-05-16)\n\n  * Use state-preserving live reload. (Requires Phoenix LiveView 1.0.0-rc.1+.)\n\n  * Display errors using `Plug.Debugger`.\n\n## v0.1.2 (2024-04-29)\n\n  * Deprecate setting `:router` in favour of `:plug`.\n\n## v0.1.1 (2024-04-23)\n\n  * Use local javascript assets.\n\n  * Update secret key base.\n\n  * Replace `PhoenixPlayground.start_link/1` with `start/1`.\n\n  * Fix opening up component definition/caller: set `:debug_heex_annotations` on application boot.\n\n  * Add `:child_specs` option to specify additional processes to run in the supervision tree.\n\n  * Prevent VM from halting when playground is started.\n\n## v0.1.0 (2024-04-18)\n\n  * Initial release.\n"
  },
  {
    "path": "README.md",
    "content": "# Phoenix Playground\n\n[![CI](https://github.com/phoenix-playground/phoenix_playground/actions/workflows/ci.yml/badge.svg)](https://github.com/phoenix-playground/phoenix_playground/actions/workflows/ci.yml)\n[![Version](https://img.shields.io/hexpm/v/phoenix_playground.svg)](https://hex.pm/packages/phoenix_playground)\n[![Hex Docs](https://img.shields.io/badge/documentation-gray.svg)](https://hexdocs.pm/phoenix_playground)\n\nPhoenix Playground makes it easy to create single-file [Phoenix](https://www.phoenixframework.org) applications.\n\n## Examples\n\n[![Run in Livebook](https://livebook.dev/badge/v1/blue.svg)](https://livebook.dev/run?url=https%3A%2F%2Fgithub.com%2Fphoenix-playground%2Fphoenix_playground%2Fblob%2Fmain%2Fexamples%2Fdemo_live.livemd)\n\nCreate a `demo_live.exs` file:\n\n```elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span>{@count}</span>\n    <button phx-click=\"inc\">+</button>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n    </style>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, assign(socket, count: socket.assigns.count + 1)}\n  end\nend\n\nPhoenixPlayground.start(live: DemoLive)\n```\n\nand run it:\n\n```\n$ iex demo_live.exs\n```\n\n<img width=\"1195\" alt=\"image\" src=\"assets/demo.png\">\n\n\nSee more examples below:\n\n  * [`examples/demo_live.exs`]\n  * [`examples/demo_live_test.exs`]\n  * [`examples/demo_controller.exs`]\n  * [`examples/demo_controller_test.exs`]\n  * [`examples/demo_plug.exs`]\n  * [`examples/demo_router.exs`]\n  * [`examples/demo_hooks.exs`]\n  * [`examples/demo_endpoint.exs`]\n  * [`examples/demo_live_pubsub.exs`]\n\n## License\n\nCopyright (c) 2024 Wojtek Mach\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n[`examples/demo_live.exs`]: examples/demo_live.exs\n[`examples/demo_live_test.exs`]: examples/demo_live_test.exs\n[`examples/demo_controller.exs`]: examples/demo_controller.exs\n[`examples/demo_controller_test.exs`]: examples/demo_controller_test.exs\n[`examples/demo_plug.exs`]: examples/demo_plug.exs\n[`examples/demo_router.exs`]: examples/demo_router.exs\n[`examples/demo_hooks.exs`]: examples/demo_hooks.exs\n[`examples/demo_endpoint.exs`]: examples/demo_endpoint.exs\n[`examples/demo_live_pubsub.exs`]: examples/demo_live_pubsub.exs\n"
  },
  {
    "path": "examples/demo_controller.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoController do\n  use Phoenix.Controller, formats: [:html]\n  use Phoenix.Component\n  plug :put_layout, false\n  plug :put_view, __MODULE__\n\n  def index(conn, params) do\n    count =\n      case Integer.parse(params[\"count\"] || \"\") do\n        {n, \"\"} -> n\n        _ -> 0\n      end\n\n    render(conn, :index, count: count)\n  end\n\n  def index(assigns) do\n    ~H\"\"\"\n    <span>{@count}</span>\n    <button onclick={\"window.location.href=\\\"/?count=#{@count + 1}\\\"\"}>+</button>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n    </style>\n    \"\"\"\n  end\nend\n\nPhoenixPlayground.start(controller: DemoController)\n"
  },
  {
    "path": "examples/demo_controller_test.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoController do\n  use Phoenix.Controller, formats: [:html]\n  use Phoenix.Component\n  plug :put_layout, false\n  plug :put_view, __MODULE__\n\n  def index(conn, params) do\n    count =\n      case Integer.parse(params[\"count\"] || \"\") do\n        {n, \"\"} -> n\n        _ -> 0\n      end\n\n    render(conn, :index, count: count)\n  end\n\n  def index(assigns) do\n    ~H\"\"\"\n    <span>Count: {@count}</span>\n    <button onclick={\"window.location.href='/?count=#{@count + 1}'\"}>+</button>\n    \"\"\"\n  end\nend\n\nLogger.configure(level: :warning)\nExUnit.start()\n\ndefmodule DemoControllerTest do\n  use ExUnit.Case\n  use PhoenixPlayground.Test, controller: DemoController\n\n  test \"it works\" do\n    conn = get(build_conn(), \"/\")\n    assert html_response(conn, 200) =~ \"Count: 0\"\n\n    conn = get(build_conn(), \"/?count=1\")\n    assert html_response(conn, 200) =~ \"Count: 1\"\n  end\nend\n"
  },
  {
    "path": "examples/demo_endpoint.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <script>\n      let liveSocket = new window.LiveView.LiveSocket(\"/live\", window.Phoenix.Socket, {})\n      liveSocket.connect()\n    </script>\n\n    <span>Count: {@count}</span>\n    <button phx-click=\"inc\">+</button>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, update(socket, :count, &(&1 + 1))}\n  end\nend\n\ndefmodule Demo.Router do\n  use Phoenix.Router\n  import Phoenix.LiveView.Router\n\n  pipeline :browser do\n    plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}\n  end\n\n  scope \"/\" do\n    pipe_through :browser\n    live \"/\", DemoLive\n  end\nend\n\ndefmodule Demo.Endpoint do\n  use Phoenix.Endpoint, otp_app: :phoenix_playground\n  plug Plug.Logger\n  socket \"/live\", Phoenix.LiveView.Socket\n  plug Plug.Static, from: {:phoenix, \"priv/static\"}, at: \"/assets/phoenix\"\n  plug Plug.Static, from: {:phoenix_live_view, \"priv/static\"}, at: \"/assets/phoenix_live_view\"\n  socket \"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket\n  plug Phoenix.LiveReloader\n  plug Phoenix.CodeReloader, reloader: &PhoenixPlayground.CodeReloader.reload/2\n  plug Demo.Router\nend\n\nPhoenixPlayground.start(endpoint: Demo.Endpoint)\n"
  },
  {
    "path": "examples/demo_hooks.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoHooks do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, socket}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span id=\"hook\" phx-hook=\"MyHook\">LiveView</span>\n\n    <script>\n    window.hooks.MyHook = {\n      mounted() {\n        this.el.innerHTML += \" rocks\";\n        setInterval(() => this.el.innerHTML += \"!\", 1000);\n      }\n    }\n    </script>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n    </style>\n    \"\"\"\n  end\nend\n\nPhoenixPlayground.start(live: DemoHooks)\n"
  },
  {
    "path": "examples/demo_live.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span>{@count}</span>\n    <button phx-click=\"inc\">+</button>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n    </style>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, assign(socket, count: socket.assigns.count + 1)}\n  end\nend\n\nPhoenixPlayground.start(live: DemoLive)\n"
  },
  {
    "path": "examples/demo_live.livemd",
    "content": "# Demo\n\n```elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n```\n\n## Section\n\n```elixir\ndefmodule DemoLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span>{@count}</span>\n    <button phx-click=\"inc\">+</button>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n      span { font-family: monospace; }\n    </style>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, assign(socket, count: socket.assigns.count + 1)}\n  end\nend\n\nPhoenixPlayground.start(live: DemoLive)\n```\n"
  },
  {
    "path": "examples/demo_live_pubsub.exs",
    "content": "Mix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule TimelineLive do\n  use Phoenix.LiveView\n\n  @topic \"timeline\"\n\n  def mount(_params, _session, socket) do\n    if connected?(socket) do\n      Phoenix.PubSub.subscribe(PhoenixPlayground.PubSub, @topic)\n    end\n\n    socket =\n      socket\n      |> assign(temporary_assigns: [form: nil])\n      |> stream(:posts, [])\n      |> assign(:form, to_form(%{\"content\" => \"\"}))\n\n    {:ok, socket}\n  end\n\n  def handle_event(\"create_post\", %{\"content\" => content}, socket) do\n    post = %{\n      id: System.unique_integer([:positive]),\n      content: content,\n      inserted_at: DateTime.utc_now()\n    }\n\n    Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:new_post, post})\n\n    {:noreply,\n     socket\n     |> assign(:form, to_form(%{\"content\" => \"\"}))}\n  end\n\n  def handle_event(\"delete_post\", %{\"dom_id\" => dom_id}, socket) do\n    Phoenix.PubSub.broadcast(PhoenixPlayground.PubSub, @topic, {:delete_post, dom_id})\n\n    {:noreply, socket}\n  end\n\n  def handle_info({:new_post, post}, socket) do\n    {:noreply, stream_insert(socket, :posts, post, at: 0)}\n  end\n\n  def handle_info({:delete_post, dom_id}, socket) do\n    {:noreply, stream_delete_by_dom_id(socket, :posts, dom_id)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <div class=\"timeline\">\n      <h1>Timeline</h1>\n\n      <.form for={@form} phx-submit=\"create_post\" id=\"post-form\">\n        <textarea name=\"content\" placeholder=\"What's on your mind?\" id=\"content\" phx-hook=\"CmdEnterSubmit\"><%= @form.params[\"content\"] %></textarea>\n        <button type=\"submit\">Post</button>\n      </.form>\n\n      <div class=\"posts\" phx-update=\"stream\" id=\"posts\">\n        <div :for={{dom_id, post} <- @streams.posts} id={dom_id} class=\"post\">\n          <div class=\"post-content\">\n            <p><%= post.content %></p>\n            <small>{DateTime.truncate(post.inserted_at, :second)}</small>\n          </div>\n          <button phx-click=\"delete_post\" phx-value-dom_id={dom_id} class=\"delete-btn\">Delete</button>\n        </div>\n      </div>\n    </div>\n\n    <script>\n      window.hooks.CmdEnterSubmit = {\n        mounted() {\n        this.el.addEventListener(\"keydown\", (e) => {\n          if (e.metaKey && e.key === 'Enter') {\n            this.el.form.dispatchEvent(\n              new Event('submit', {bubbles: true, cancelable: true}));\n          }\n        })\n        }\n      }\n    </script>\n\n    <style>\n      * {\n        box-sizing: border-box;\n      }\n\n      .timeline {\n        max-width: 800px;\n        margin: 0 auto;\n        padding: 20px;\n        font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif;\n      }\n\n      h1 {\n        color: #2c3e50;\n        font-size: 2.5em;\n        margin-bottom: 1em;\n        text-align: center;\n      }\n\n      .post {\n        border: 1px solid #e1e8ed;\n        border-radius: 12px;\n        padding: 16px;\n        margin: 16px 0;\n        display: flex;\n        justify-content: space-between;\n        align-items: flex-start;\n        background: white;\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n        transition: all 0.2s ease;\n      }\n\n      .post:hover {\n        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);\n        transform: translateY(-2px);\n      }\n\n      .post-content {\n        flex: 1;\n        margin-right: 16px;\n      }\n\n      .post-content p {\n        color: #2c3e50;\n        font-size: 1.1em;\n        line-height: 1.5;\n        margin: 0 0 8px 0;\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n      }\n\n      .post-content small {\n        color: #8795a1;\n        font-size: 0.9em;\n        display: block;\n        word-wrap: break-word;\n        overflow-wrap: break-word;\n      }\n\n      form {\n        margin: 20px 0;\n        background: white;\n        padding: 20px;\n        border-radius: 12px;\n        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);\n        width: 100%;\n      }\n\n      textarea {\n        width: 100%;\n        min-height: 3ch;\n        margin-bottom: 16px;\n        padding: 12px;\n        border: 2px solid #e1e8ed;\n        border-radius: 8px;\n        font-size: 1em;\n        resize: vertical;\n        transition: border-color 0.2s ease;\n      }\n\n      textarea:focus {\n        outline: none;\n        border-color: #4a9eff;\n      }\n\n      button {\n        padding: 10px 20px;\n        background: #4a9eff;\n        color: white;\n        border: none;\n        border-radius: 8px;\n        cursor: pointer;\n        font-size: 1em;\n        font-weight: 500;\n        transition: all 0.2s ease;\n      }\n\n      button:hover {\n        background: #357abd;\n        transform: translateY(-1px);\n      }\n\n      .delete-btn {\n        background: #ff4a4a;\n        margin-left: 10px;\n        padding: 8px 16px;\n        font-size: 0.9em;\n        opacity: 0.8;\n      }\n\n      .delete-btn:hover {\n        background: #bd3535;\n        opacity: 1;\n      }\n\n      body {\n        background: #f8fafc;\n        margin: 0;\n        padding: 20px;\n      }\n    </style>\n    \"\"\"\n  end\nend\n\nPhoenixPlayground.start(live: TimelineLive)\n"
  },
  {
    "path": "examples/demo_live_test.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule DemoLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span>Count: {@count}</span>\n    <button phx-click=\"inc\">+</button>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, update(socket, :count, &(&1 + 1))}\n  end\nend\n\nLogger.configure(level: :warning)\nExUnit.start()\n\ndefmodule DemoLiveTest do\n  use ExUnit.Case\n  use PhoenixPlayground.Test, live: DemoLive\n\n  test \"it works\" do\n    {:ok, view, html} = live(build_conn(), \"/\")\n\n    assert html =~ \"Count: 0\"\n    assert render_click(view, :inc, %{}) =~ \"Count: 1\"\n    assert render_click(view, :inc, %{}) =~ \"Count: 2\"\n  end\nend\n"
  },
  {
    "path": "examples/demo_plug.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\nPhoenixPlayground.start(\n  plug: fn conn ->\n    Plug.Conn.send_resp(conn, 200, \"Hello!\")\n  end\n)\n"
  },
  {
    "path": "examples/demo_router.exs",
    "content": "#!/usr/bin/env elixir\nMix.install([\n  {:phoenix_playground, \"~> 0.1.8\"}\n])\n\ndefmodule CounterLive do\n  use Phoenix.LiveView\n\n  def mount(_params, _session, socket) do\n    {:ok, assign(socket, count: 0)}\n  end\n\n  def render(assigns) do\n    ~H\"\"\"\n    <span>{@count}</span>\n    <button phx-click=\"inc\">+</button>\n\n    <style type=\"text/css\">\n      body { padding: 1em; }\n    </style>\n    \"\"\"\n  end\n\n  def handle_event(\"inc\", _params, socket) do\n    {:noreply, assign(socket, count: socket.assigns.count + 1)}\n  end\nend\n\ndefmodule DemoRouter do\n  use Phoenix.Router\n  import Phoenix.LiveView.Router\n\n  pipeline :browser do\n    plug :accepts, [\"html\"]\n    plug :fetch_session\n    plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}\n    plug :put_secure_browser_headers\n  end\n\n  scope \"/\" do\n    pipe_through :browser\n\n    live \"/\", CounterLive\n  end\nend\n\nPhoenixPlayground.start(plug: DemoRouter)\n"
  },
  {
    "path": "lib/phoenix_playground/application.ex",
    "content": "defmodule PhoenixPlayground.Application do\n  @moduledoc false\n\n  use Application\n\n  @impl true\n  def start(_type, _args) do\n    # suppress Phoenix warning about no endpoint configuration\n    Application.put_env(:phoenix_playground, PhoenixPlayground.Endpoint, [])\n\n    Application.put_env(:phoenix_live_view, :debug_heex_annotations, true)\n\n    Supervisor.start_link([], strategy: :one_for_one, name: __MODULE__)\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground/code_reloader.ex",
    "content": "defmodule PhoenixPlayground.CodeReloader do\n  @moduledoc false\n\n  def reload(_endpoint, _options \\\\ []) do\n    if path = Application.get_env(:phoenix_playground, :file) do\n      case File.read(path) do\n        {:ok, contents} ->\n          old = Code.get_compiler_option(:ignore_module_conflict) == true\n          Code.put_compiler_option(:ignore_module_conflict, true)\n          Code.eval_string(contents, [], file: path)\n          Code.put_compiler_option(:ignore_module_conflict, old)\n\n        # ignore fs errors. (Seems like saving file in vim sometimes make it temp dissapear?)\n        {:error, _reason} ->\n          :ok\n      end\n    else\n      # in Livebook, path is nil\n      :ok\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground/endpoint.ex",
    "content": "defmodule PhoenixPlayground.Endpoint do\n  @moduledoc false\n\n  use Phoenix.Endpoint, otp_app: :phoenix_playground\n\n  @signing_salt \"ll+Leuc4\"\n\n  @session_options [\n    store: :cookie,\n    key: \"_phoenix_playground_key\",\n    signing_salt: @signing_salt,\n    same_site: \"Lax\",\n    # 14 days\n    max_age: 14 * 24 * 60 * 60\n  ]\n\n  socket \"/live\", Phoenix.LiveView.Socket\n\n  plug Plug.Static, from: {:phoenix, \"priv/static\"}, at: \"/assets/phoenix\"\n  plug Plug.Static, from: {:phoenix_live_view, \"priv/static\"}, at: \"/assets/phoenix_live_view\"\n\n  socket \"/phoenix/live_reload/socket\", Phoenix.LiveReloader.Socket\n  plug :run_live_reload\n\n  # TODO:\n  # plug Phoenix.Ecto.CheckRepoStatus, otp_app: :phoenix_playground\n\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.Session, @session_options\n  plug PhoenixPlayground.Router\n\n  defp run_live_reload(conn, _options) do\n    if Application.get_env(:phoenix_playground, :live_reload) do\n      conn\n      |> Phoenix.LiveReloader.call([])\n      |> Phoenix.CodeReloader.call(reloader: &PhoenixPlayground.CodeReloader.reload/2)\n    else\n      conn\n    end\n  end\n\n  # See https://github.com/phoenixframework/phoenix/blob/v1.7.14/lib/phoenix/endpoint.ex#L484:L490\n  @plug_debugger [\n    banner: {Phoenix.Endpoint.RenderErrors, :__debugger_banner__, []},\n    style: [\n      primary: \"#EB532D\",\n      logo:\n        \"data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzEgNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPgoJPHBhdGggZD0ibTI2LjM3MSAzMy40NzctLjU1Mi0uMWMtMy45Mi0uNzI5LTYuMzk3LTMuMS03LjU3LTYuODI5LS43MzMtMi4zMjQuNTk3LTQuMDM1IDMuMDM1LTQuMTQ4IDEuOTk1LS4wOTIgMy4zNjIgMS4wNTUgNC41NyAyLjM5IDEuNTU3IDEuNzIgMi45ODQgMy41NTggNC41MTQgNS4zMDUgMi4yMDIgMi41MTUgNC43OTcgNC4xMzQgOC4zNDcgMy42MzQgMy4xODMtLjQ0OCA1Ljk1OC0xLjcyNSA4LjM3MS0zLjgyOC4zNjMtLjMxNi43NjEtLjU5MiAxLjE0NC0uODg2bC0uMjQxLS4yODRjLTIuMDI3LjYzLTQuMDkzLjg0MS02LjIwNS43MzUtMy4xOTUtLjE2LTYuMjQtLjgyOC04Ljk2NC0yLjU4Mi0yLjQ4Ni0xLjYwMS00LjMxOS0zLjc0Ni01LjE5LTYuNjExLS43MDQtMi4zMTUuNzM2LTMuOTM0IDMuMTM1LTMuNi45NDguMTMzIDEuNzQ2LjU2IDIuNDYzIDEuMTY1LjU4My40OTMgMS4xNDMgMS4wMTUgMS43MzggMS40OTMgMi44IDIuMjUgNi43MTIgMi4zNzUgMTAuMjY1LS4wNjgtNS44NDItLjAyNi05LjgxNy0zLjI0LTEzLjMwOC03LjMxMy0xLjM2Ni0xLjU5NC0yLjctMy4yMTYtNC4wOTUtNC43ODUtMi42OTgtMy4wMzYtNS42OTItNS43MS05Ljc5LTYuNjIzQzEyLjgtLjYyMyA3Ljc0NS4xNCAyLjg5MyAyLjM2MSAxLjkyNiAyLjgwNC45OTcgMy4zMTkgMCA0LjE0OWMuNDk0IDAgLjc2My4wMDYgMS4wMzIgMCAyLjQ0Ni0uMDY0IDQuMjggMS4wMjMgNS42MDIgMy4wMjQuOTYyIDEuNDU3IDEuNDE1IDMuMTA0IDEuNzYxIDQuNzk4LjUxMyAyLjUxNS4yNDcgNS4wNzguNTQ0IDcuNjA1Ljc2MSA2LjQ5NCA0LjA4IDExLjAyNiAxMC4yNiAxMy4zNDYgMi4yNjcuODUyIDQuNTkxIDEuMTM1IDcuMTcyLjU1NVpNMTAuNzUxIDMuODUyYy0uOTc2LjI0Ni0xLjc1Ni0uMTQ4LTIuNTYtLjk2MiAxLjM3Ny0uMzQzIDIuNTkyLS40NzYgMy44OTctLjUyOC0uMTA3Ljg0OC0uNjA3IDEuMzA2LTEuMzM2IDEuNDlabTMyLjAwMiAzNy45MjRjLS4wODUtLjYyNi0uNjItLjkwMS0xLjA0LTEuMjI4LTEuODU3LTEuNDQ2LTQuMDMtMS45NTgtNi4zMzMtMi0xLjM3NS0uMDI2LTIuNzM1LS4xMjgtNC4wMzEtLjYxLS41OTUtLjIyLTEuMjYtLjUwNS0xLjI0NC0xLjI3Mi4wMTUtLjc4LjY5My0xIDEuMzEtMS4xODQuNTA1LS4xNSAxLjAyNi0uMjQ3IDEuNi0uMzgyLTEuNDYtLjkzNi0yLjg4Ni0xLjA2NS00Ljc4Ny0uMy0yLjk5MyAxLjIwMi01Ljk0MyAxLjA2LTguOTI2LS4wMTctMS42ODQtLjYwOC0zLjE3OS0xLjU2My00LjczNS0yLjQwOGwtLjA0My4wM2EyLjk2IDIuOTYgMCAwIDAgLjA0LS4wMjljLS4wMzgtLjExNy0uMTA3LS4xMi0uMTk3LS4wNTRsLjEyMi4xMDdjMS4yOSAyLjExNSAzLjAzNCAzLjgxNyA1LjAwNCA1LjI3MSAzLjc5MyAyLjggNy45MzYgNC40NzEgMTIuNzg0IDMuNzNBNjYuNzE0IDY2LjcxNCAwIDAgMSAzNyA0MC44NzdjMS45OC0uMTYgMy44NjYuMzk4IDUuNzUzLjg5OVptLTkuMTQtMzAuMzQ1Yy0uMTA1LS4wNzYtLjIwNi0uMjY2LS40Mi0uMDY5IDEuNzQ1IDIuMzYgMy45ODUgNC4wOTggNi42ODMgNS4xOTMgNC4zNTQgMS43NjcgOC43NzMgMi4wNyAxMy4yOTMuNTEgMy41MS0xLjIxIDYuMDMzLS4wMjggNy4zNDMgMy4zOC4xOS0zLjk1NS0yLjEzNy02LjgzNy01Ljg0My03LjQwMS0yLjA4NC0uMzE4LTQuMDEuMzczLTUuOTYyLjk0LTUuNDM0IDEuNTc1LTEwLjQ4NS43OTgtMTUuMDk0LTIuNTUzWm0yNy4wODUgMTUuNDI1Yy43MDguMDU5IDEuNDE2LjEyMyAyLjEyNC4xODUtMS42LTEuNDA1LTMuNTUtMS41MTctNS41MjMtMS40MDQtMy4wMDMuMTctNS4xNjcgMS45MDMtNy4xNCAzLjk3Mi0xLjczOSAxLjgyNC0zLjMxIDMuODctNS45MDMgNC42MDQuMDQzLjA3OC4wNTQuMTE3LjA2Ni4xMTcuMzUuMDA1LjY5OS4wMjEgMS4wNDcuMDA1IDMuNzY4LS4xNyA3LjMxNy0uOTY1IDEwLjE0LTMuNy44OS0uODYgMS42ODUtMS44MTcgMi41NDQtMi43MS43MTYtLjc0NiAxLjU4NC0xLjE1OSAyLjY0NS0xLjA3Wm0tOC43NTMtNC42N2MtMi44MTIuMjQ2LTUuMjU0IDEuNDA5LTcuNTQ4IDIuOTQzLTEuNzY2IDEuMTgtMy42NTQgMS43MzgtNS43NzYgMS4zNy0uMzc0LS4wNjYtLjc1LS4xMTQtMS4xMjQtLjE3bC0uMDEzLjE1NmMuMTM1LjA3LjI2NS4xNTEuNDA1LjIwNy4zNTQuMTQuNzAyLjMwOCAxLjA3LjM5NSA0LjA4My45NzEgNy45OTIuNDc0IDExLjUxNi0xLjgwMyAyLjIyMS0xLjQzNSA0LjUyMS0xLjcwNyA3LjAxMy0xLjMzNi4yNTIuMDM4LjUwMy4wODMuNzU2LjEwNy4yMzQuMDIyLjQ3OS4yNTUuNzk1LjAwMy0yLjE3OS0xLjU3NC00LjUyNi0yLjA5Ni03LjA5NC0xLjg3MlptLTEwLjA0OS05LjU0NGMxLjQ3NS4wNTEgMi45NDMtLjE0MiA0LjQ4Ni0xLjA1OS0uNDUyLjA0LS42NDMuMDQtLjgyNy4wNzYtMi4xMjYuNDI0LTQuMDMzLS4wNC01LjczMy0xLjM4My0uNjIzLS40OTMtMS4yNTctLjk3NC0xLjg4OS0xLjQ1Ny0yLjUwMy0xLjkxNC01LjM3NC0yLjU1NS04LjUxNC0yLjUuMDUuMTU0LjA1NC4yNi4xMDguMzE1IDMuNDE3IDMuNDU1IDcuMzcxIDUuODM2IDEyLjM2OSA2LjAwOFptMjQuNzI3IDE3LjczMWMtMi4xMTQtMi4wOTctNC45NTItMi4zNjctNy41NzgtLjUzNyAxLjczOC4wNzggMy4wNDMuNjMyIDQuMTAxIDEuNzI4LjM3NC4zODguNzYzLjc2OCAxLjE4MiAxLjEwNiAxLjYgMS4yOSA0LjMxMSAxLjM1MiA1Ljg5Ni4xNTUtMS44NjEtLjcyNi0xLjg2MS0uNzI2LTMuNjAxLTIuNDUyWm0tMjEuMDU4IDE2LjA2Yy0xLjg1OC0zLjQ2LTQuOTgxLTQuMjQtOC41OS00LjAwOGE5LjY2NyA5LjY2NyAwIDAgMSAyLjk3NyAxLjM5Yy44NC41ODYgMS41NDcgMS4zMTEgMi4yNDMgMi4wNTUgMS4zOCAxLjQ3MyAzLjUzNCAyLjM3NiA0Ljk2MiAyLjA3LS42NTYtLjQxMi0xLjIzOC0uODQ4LTEuNTkyLTEuNTA3Wm0xNy4yOS0xOS4zMmMwLS4wMjMuMDAxLS4wNDUuMDAzLS4wNjhsLS4wMDYuMDA2LjAwNi0uMDA2LS4wMzYtLjAwNC4wMjEuMDE4LjAxMi4wNTNabS0yMCAxNC43NDRhNy42MSA3LjYxIDAgMCAwLS4wNzItLjA0MS4xMjcuMTI3IDAgMCAwIC4wMTUuMDQzYy4wMDUuMDA4LjAzOCAwIC4wNTgtLjAwMlptLS4wNzItLjA0MS0uMDA4LS4wMzQtLjAwOC4wMS4wMDgtLjAxLS4wMjItLjAwNi4wMDUuMDI2LjAyNC4wMTRaIgogICAgICAgICAgICBmaWxsPSIjRkQ0RjAwIiAvPgo8L3N2Zz4K\"\n    ]\n  ]\n\n  # See https://github.com/elixir-plug/plug/blob/v1.16.1/lib/plug/debugger.ex#L129:L146\n  def call(conn, opts) do\n    if Application.get_env(:phoenix_playground, :debug_errors, false) do\n      try do\n        case conn do\n          %Plug.Conn{path_info: [\"__plug__\", \"debugger\", \"action\"], method: \"POST\"} ->\n            Plug.Debugger.run_action(conn)\n\n          %Plug.Conn{} ->\n            super(conn, opts)\n        end\n      rescue\n        e in Plug.Conn.WrapperError ->\n          %{conn: conn, kind: kind, reason: reason, stack: stack} = e\n          Plug.Debugger.__catch__(conn, kind, reason, stack, @plug_debugger)\n      catch\n        kind, reason ->\n          Plug.Debugger.__catch__(conn, kind, reason, __STACKTRACE__, @plug_debugger)\n      end\n    else\n      super(conn, opts)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground/error_view.ex",
    "content": "defmodule PhoenixPlayground.ErrorView do\n  @moduledoc false\n\n  def render(template, _), do: Phoenix.Controller.status_message_from_template(template)\nend\n"
  },
  {
    "path": "lib/phoenix_playground/layout.ex",
    "content": "defmodule PhoenixPlayground.Layout do\n  @moduledoc \"\"\"\n  Built-in layout.\n\n  This is the layout used by `PhoenixPlayground.start/1` with option `:live` or `:controller`\n\n  To customize page title, set `:page_title` assign.\n\n  You can customize LiveSocket hooks and uploaders by changing `window.hooks` and `window.uploaders`.\n  \"\"\"\n\n  use Phoenix.Component\n\n  @doc false\n  def render(template, assigns)\n\n  def render(\"root.html\", assigns) do\n    ~H\"\"\"\n    <!DOCTYPE html>\n    <html lang=\"en\" class=\"h-full\">\n      <head>\n        <meta charset=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <.live_title>\n          <%= assigns[:page_title] || \"Phoenix Playground\" %>\n        </.live_title>\n        <link rel=\"icon\" href=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAgAB/ajN8ioAAAAASUVORK5CYII=\">\n      </head>\n      <body>\n        <script src=\"/assets/phoenix/phoenix.js\"></script>\n        <script src=\"/assets/phoenix_live_view/phoenix_live_view.js\"></script>\n        <script>\n          // Set global hooks and uploaders objects to be used by the LiveSocket,\n          // so they can be overwritten in user provided templates.\n          window.hooks = {}\n          window.uploaders = {}\n\n          let liveSocket =\n            new window.LiveView.LiveSocket(\n              \"/live\",\n              window.Phoenix.Socket,\n              { hooks, uploaders }\n            )\n          liveSocket.connect()\n\n          window.addEventListener(\"phx:live_reload:attached\", ({detail: reloader}) => {\n            // Enable server log streaming to client. Disable with reloader.disableServerLogs()\n            reloader.enableServerLogs()\n\n            // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component\n            //\n            //   * click with \"c\" key pressed to open at caller location\n            //   * click with \"d\" key pressed to open at function component definition location\n            let keyDown\n            window.addEventListener(\"keydown\", e => keyDown = e.key)\n            window.addEventListener(\"keyup\", e => keyDown = null)\n            window.addEventListener(\"click\", e => {\n              if(keyDown === \"c\"){\n                e.preventDefault()\n                e.stopImmediatePropagation()\n                reloader.openEditorAtCaller(e.target)\n              } else if(keyDown === \"d\"){\n                e.preventDefault()\n                e.stopImmediatePropagation()\n                reloader.openEditorAtDef(e.target)\n              }\n            }, true)\n\n            window.liveReloader = reloader\n          })\n        </script>\n\n        <%= @inner_content %>\n      </body>\n    </html>\n    \"\"\"\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground/router.ex",
    "content": "defmodule PhoenixPlayground.Router do\n  @moduledoc false\n\n  @behaviour Plug\n\n  @impl true\n  def init([]) do\n    []\n  end\n\n  @impl true\n  def call(conn, []) do\n    endpoint = conn.private.phoenix_endpoint\n\n    options = endpoint.config(:phoenix_playground)\n\n    cond do\n      options[:live] ->\n        PhoenixPlayground.Router.LiveRouter.call(conn, [])\n\n      controller = options[:controller] ->\n        conn = put_in(conn.private[:phoenix_playground_controller], controller)\n        PhoenixPlayground.Router.ControllerRouter.call(conn, [])\n\n      options[:plug] ->\n        # always fetch plug from app env to allow code reloading anonymous functions\n        plug = Application.fetch_env!(:phoenix_playground, :plug)\n\n        case plug do\n          module when is_atom(module) ->\n            module.call(conn, module.init([]))\n\n          {module, options} when is_atom(module) ->\n            module.call(conn, module.init(options))\n\n          fun when is_function(fun, 1) ->\n            fun.(conn)\n\n          fun when is_function(fun, 2) ->\n            fun.(conn, [])\n        end\n\n      true ->\n        raise ArgumentError, \"expected :live, :controller, or :plug, got: #{inspect(options)}\"\n    end\n  end\n\n  defmodule ControllerRouter do\n    @moduledoc false\n\n    use Phoenix.Router\n    import Phoenix.LiveView.Router\n\n    pipeline :browser do\n      plug :accepts, [\"html\", \"json\"]\n      plug :fetch_session\n      plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}\n      plug :protect_from_forgery\n      plug :put_secure_browser_headers\n    end\n\n    scope \"/\" do\n      pipe_through :browser\n\n      get \"/\", PhoenixPlayground.Router.DelegateController, :index\n    end\n  end\n\n  defmodule DelegateController do\n    @moduledoc false\n\n    def init(options) do\n      options\n    end\n\n    def call(conn, options) do\n      controller = conn.private.phoenix_playground_controller\n      controller.call(conn, controller.init(options))\n    end\n  end\n\n  defmodule LiveRouter do\n    @moduledoc false\n\n    use Phoenix.Router\n    import Phoenix.LiveView.Router\n\n    pipeline :browser do\n      plug :accepts, [\"html\"]\n      plug :fetch_session\n      plug :put_root_layout, html: {PhoenixPlayground.Layout, :root}\n      plug :put_secure_browser_headers\n    end\n\n    scope \"/\" do\n      pipe_through :browser\n\n      live \"/\", PhoenixPlayground.Router.DelegateLive, :index\n    end\n  end\n\n  defmodule DelegateLive do\n    @moduledoc false\n    use Phoenix.LiveView\n\n    @impl true\n    def mount(params, session, socket) do\n      module().mount(params, session, socket)\n    end\n\n    @impl true\n    def render(assigns) do\n      module().render(assigns)\n    end\n\n    @impl true\n    def handle_params(unsigned_params, uri, socket) do\n      if function_exported?(module(), :handle_params, 3) do\n        module().handle_params(unsigned_params, uri, socket)\n      else\n        {:noreply, socket}\n      end\n    end\n\n    @impl true\n    def handle_event(event, params, socket) do\n      module().handle_event(event, params, socket)\n    end\n\n    @impl true\n    def handle_info(message, socket) do\n      module().handle_info(message, socket)\n    end\n\n    @impl true\n    def handle_async(message, result, socket) do\n      module().handle_async(message, result, socket)\n    end\n\n    def module do\n      Application.fetch_env!(:phoenix_playground, :live)\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground/test.ex",
    "content": "defmodule PhoenixPlayground.Test do\n  @moduledoc ~S'''\n  Conveniences for testing single-file Phoenix apps.\n\n  ## Examples\n\n      Mix.install([\n        {:phoenix_playground, \"~> 0.1.0\"}\n      ])\n\n      defmodule DemoLive do\n        use Phoenix.LiveView\n\n        def mount(_params, _session, socket) do\n          {:ok, assign(socket, count: 0)}\n        end\n\n        def render(assigns) do\n          ~H\"\"\"\n          <span>Count: <%= @count %></span> <button phx-click=\"inc\">+</button>\n          \"\"\"\n        end\n\n        def handle_event(\"inc\", _params, socket) do\n          {:noreply, update(socket, :count, &(&1 + 1))}\n        end\n      end\n\n      Logger.configure(level: :info)\n      ExUnit.start()\n\n      defmodule DemoLiveTest do\n        use ExUnit.Case\n        use PhoenixPlayground.Test, live: DemoLive\n\n        test \"it works\" do\n          {:ok, view, html} = live(build_conn(), \"/\")\n\n          assert html =~ \"Count: 0\"\n          assert render_click(view, :inc, %{}) =~ \"Count: 1\"\n          assert render_click(view, :inc, %{}) =~ \"Count: 2\"\n        end\n      end\n  '''\n\n  @secret_key_base String.duplicate(\"a\", 32)\n  @signing_salt \"ll+Leuc4\"\n\n  defmacro __using__(options) do\n    options =\n      Keyword.validate!(options, [\n        :live,\n        :controller,\n        endpoint: PhoenixPlayground.Endpoint\n      ])\n\n    imports =\n      if options[:live] do\n        quote do\n          import(Phoenix.LiveViewTest)\n        end\n      end\n\n    quote do\n      import Phoenix.ConnTest\n      unquote(imports)\n\n      @endpoint unquote(options[:endpoint])\n\n      setup do\n        options = unquote(options)\n\n        if live = options[:live] do\n          Application.put_env(:phoenix_playground, :live, live)\n        end\n\n        start_supervised!(\n          {@endpoint,\n           secret_key_base: unquote(@secret_key_base),\n           live_view: [signing_salt: unquote(@signing_salt)],\n           phoenix_playground: options}\n        )\n\n        :ok\n      end\n    end\n  end\nend\n"
  },
  {
    "path": "lib/phoenix_playground.ex",
    "content": "defmodule PhoenixPlayground do\n  @moduledoc \"\"\"\n  Phoenix Playground makes it easy to create single-file Phoenix applications.\n  \"\"\"\n\n  require Logger\n\n  @secret_key_base [\n                     then(:inet.gethostname(), fn {:ok, host} -> host end),\n                     System.get_env(\"USER\", \"\"),\n                     System.version(),\n                     :erlang.system_info(:version),\n                     :erlang.system_info(:system_architecture)\n                   ]\n                   |> :erlang.md5()\n                   |> Base.url_encode64(padding: false)\n\n  @signing_salt \"ll+Leuc4\"\n\n  @doc \"\"\"\n  Starts Phoenix Playground.\n\n  This functions starts Phoenix with a LiveView (`:live`), a controller (`:controller`),\n  or a router (`:router`).\n\n  ## Options\n\n    * `:live` - a LiveView module.\n\n      Phoenix Playground adds the following conveniences to the given LiveView:\n\n        * a `:page_title` assign can be used to customise `<head>` `<title>` tag.\n\n        * a `window.hooks` object can be used to register hooks. See `examples/demo_hooks.exs`.\n\n      This LiveView will automatically use `PhoenixPlayground.Layout`.\n\n    * `:controller` - a controller module.\n\n      This controller will automatically use `PhoenixPlayground.Layout`.\n\n    * `:plug` - a plug.\n\n    * `:port` - port to listen on, defaults to: `4000`.\n\n    * `:endpoint_options` - additional Phoenix endpoint options, defaults to `[]`.\n\n    * `:debug_errors` - whether to use Phoenix error debugger, defaults to `true`.\n\n    * `:open_browser` - whether to open the browser on start, defaults to `true`.\n\n    * `:child_specs` - child specs to run in Phoenix Playground supervision tree. The playground\n      Phoenix endpoint is automatically added and is always the last child spec. Defaults to `[]`.\n  \"\"\"\n  def start(options) do\n    options =\n      case Application.fetch_env(:phoenix_playground, :file) do\n        {:ok, file} ->\n          Keyword.put(options, :file, file)\n\n        :error ->\n          file = get_file()\n          Application.put_env(:phoenix_playground, :file, file)\n          Keyword.put(options, :file, file)\n      end\n\n    options =\n      if router = options[:router] do\n        IO.warn(\"setting :router is deprecated in favour of setting :plug\")\n\n        options\n        |> Keyword.delete(:router)\n        |> Keyword.put(:plug, router)\n      else\n        options\n      end\n\n    if plug = options[:plug] do\n      Application.put_env(:phoenix_playground, :plug, plug)\n    end\n\n    case Supervisor.start_child(PhoenixPlayground.Application, {PhoenixPlayground, options}) do\n      {:ok, pid} ->\n        {:ok, pid}\n\n      {:error, {:already_started, pid}} ->\n        # In Livebook, path is nil. Livebook does its own code reloading, this just refreshes LV.\n        unless options[:file] do\n          Phoenix.PubSub.broadcast(\n            PhoenixPlayground.PubSub,\n            \"live_view\",\n            {:phoenix_live_reload, :live_view, nil}\n          )\n        end\n\n        {:ok, pid}\n\n      {:error, {{:EXIT, {exception, _trace}}, _child_info}} ->\n        raise exception\n\n      other ->\n        other\n    end\n  end\n\n  defp get_file do\n    {:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)\n    get_file(stacktrace)\n  end\n\n  defp get_file([\n         {PhoenixPlayground, :start, 1, _},\n         {_, :__FILE__, 1, meta} | _\n       ]) do\n    Path.expand(Keyword.fetch!(meta, :file))\n  end\n\n  defp get_file([_ | rest]) do\n    get_file(rest)\n  end\n\n  defp get_file([]) do\n    nil\n  end\n\n  @doc false\n  def child_spec(options) do\n    %{\n      id: __MODULE__,\n      start: {__MODULE__, :start_link, [options]},\n      type: :supervisor\n    }\n  end\n\n  @doc false\n  def start_link(options) do\n    options =\n      Keyword.validate!(options, [\n        :live,\n        :controller,\n        :plug,\n        :file,\n        port: 4000,\n        host: \"localhost\",\n        live_reload: true,\n        ip: {127, 0, 0, 1},\n        endpoint: PhoenixPlayground.Endpoint,\n        endpoint_options: [],\n        debug_errors: true,\n        open_browser: true,\n        child_specs: []\n      ])\n\n    child_specs = Keyword.fetch!(options, :child_specs)\n\n    path = options[:file]\n    Application.put_env(:phoenix_playground, :file, path)\n\n    if options[:debug_errors] do\n      Application.put_env(:phoenix_playground, :debug_errors, true)\n    end\n\n    if options[:open_browser] do\n      Application.put_env(:phoenix, :browser_open, true)\n    end\n\n    if live = options[:live] do\n      Application.put_env(:phoenix_playground, :live, live)\n    end\n\n    # in Livebook, path is nil\n    if path do\n      Application.put_env(:phoenix_live_reload, :dirs, [\n        Path.dirname(path)\n      ])\n    end\n\n    Application.put_env(:phoenix_playground, :live_reload, options[:live_reload])\n\n    if options[:live_reload] do\n      # PhoenixLiveReload requires Hex\n      {:ok, _} = Application.ensure_all_started(:hex)\n      {:ok, _} = Application.ensure_all_started(:phoenix_live_reload)\n    end\n\n    live_reload_options =\n      if options[:live] &&\n           unquote(\n             # TODO: remove when depending on LV 1.0.\n             Version.match?(\n               to_string(Application.spec(:phoenix_live_view, :vsn)),\n               \">= 1.0.0-rc.1\"\n             )\n           ) do\n        [\n          # In Livebook, path is nil\n          patterns:\n            if path do\n              # TODO: this should not be needed given we set :phoenix_live_reload :dirs\n              [~r/^does_not_matter$/]\n            else\n              []\n            end,\n          notify: [\n            live_view: [\n              ~r/^#{path}$/\n            ]\n          ]\n        ]\n      else\n        [\n          patterns: [\n            ~r/^#{path}$/\n          ]\n        ]\n      end\n\n    lr_options =\n      if options[:live_reload] do\n        [\n          web_console_logger: true,\n          debounce: 100,\n          reloader: {PhoenixPlayground.CodeReloader, :reload, []}\n        ] ++ live_reload_options\n      else\n        [\n          notify: [\n            live_view: []\n          ],\n          reloader: {PhoenixPlayground.CodeReloader, :reload, []}\n        ]\n      end\n\n    # Some compile-time options are defined at the top of lib/phoenix_playground/endpoint.ex\n    endpoint_options =\n      [\n        adapter: Bandit.PhoenixAdapter,\n        http: [ip: options[:ip], port: options[:port]],\n        url: [host: options[:host]],\n        server: !!options[:port],\n        live_view: [signing_salt: @signing_salt],\n        secret_key_base: @secret_key_base,\n        pubsub_server: PhoenixPlayground.PubSub,\n        live_reload: lr_options,\n        phoenix_playground: Keyword.take(options, [:live, :controller, :plug])\n      ]\n      |> Keyword.merge(Keyword.get(options, :endpoint_options, []))\n\n    children =\n      child_specs ++\n        [\n          {Phoenix.PubSub, name: PhoenixPlayground.PubSub},\n          {Keyword.fetch!(options, :endpoint), endpoint_options}\n        ]\n\n    System.no_halt(true)\n\n    case Supervisor.start_link(children, strategy: :one_for_one) do\n      {:ok, pid} ->\n        {:ok, pid}\n\n      {:error, reason} ->\n        Logger.error(Exception.format_exit(reason))\n        {:error, reason}\n    end\n  end\nend\n"
  },
  {
    "path": "mix.exs",
    "content": "defmodule PhoenixPlayground.MixProject do\n  use Mix.Project\n\n  @source_url \"https://github.com/phoenix-playground/phoenix_playground\"\n  @version \"0.1.8\"\n\n  def project do\n    [\n      app: :phoenix_playground,\n      version: @version,\n      elixir: \"~> 1.15\",\n      start_permanent: Mix.env() == :prod,\n      package: package(),\n      aliases: aliases(),\n      docs: docs(),\n      deps: deps()\n    ]\n  end\n\n  def application do\n    [\n      extra_applications: [:logger],\n      mod: {PhoenixPlayground.Application, []}\n    ]\n  end\n\n  defp package do\n    [\n      description: \"Phoenix Playground makes it easy to create single-file Phoenix applications.\",\n      licenses: [\"Apache-2.0\"],\n      links: %{\n        \"GitHub\" => @source_url,\n        \"Changelog\" => \"https://hexdocs.pm/phoenix_playground/changelog.html\"\n      }\n    ]\n  end\n\n  defp aliases do\n    [\n      docs: [\"docs.setup\", \"docs\", \"docs.teardown\"],\n      \"docs.setup\": &docs_setup/1,\n      \"docs.teardown\": &docs_teardown/1\n    ]\n  end\n\n  defp docs do\n    [\n      main: \"readme\",\n      source_url: @source_url,\n      source_ref: \"v#{@version}\",\n      extras: [\n        \"README.md\",\n        \"CHANGELOG.md\"\n      ],\n      assets: %{\"assets\" => \"assets\"},\n      skip_code_autolink_to: [\n        \"PhoenixPlayground.start_link/1\"\n      ]\n    ]\n  end\n\n  defp docs_setup(_) do\n    tmp = \"#{__DIR__}/tmp\"\n    File.mkdir_p!(tmp)\n    File.cp!(\"#{__DIR__}/README.md\", \"#{tmp}/README.md\")\n\n    File.write!(\"#{__DIR__}/README.md\", [\n      File.read!(\"#{__DIR__}/README.md\"),\n      \"\\n\\n\",\n      for path <- Path.wildcard(\"examples/*.exs\") do\n        \"[`#{path}`]: https://github.com/phoenix-playground/phoenix_playground/blob/v#{@version}/#{path}\\n\"\n      end\n    ])\n  end\n\n  defp docs_teardown(_) do\n    tmp = \"#{__DIR__}/tmp\"\n    File.rename!(\"#{tmp}/README.md\", \"#{__DIR__}/README.md\")\n  end\n\n  defp deps do\n    [\n      {:phoenix, \"~> 1.0\"},\n      {:phoenix_live_view, \"~> 1.1\"},\n      {:bandit, \"~> 1.0\"},\n      {:jason, \"~> 1.0\"},\n      {:lazy_html, \"~> 0.1.3\"},\n\n      # Don't start phoenix_live_reload in case someone just wants PhoenixPlayground.Test.\n      # Instead, manually start it in PhoenixPlayground.start_link/1.\n      {:phoenix_live_reload, \"~> 1.5\", runtime: false},\n\n      # dev-only\n      {:ex_doc, \">= 0.0.0\", only: :dev}\n    ]\n  end\nend\n"
  },
  {
    "path": "test/examples_test.exs",
    "content": "defmodule Examples do\n  def run(path) do\n    {:ok, quoted} =\n      path\n      |> File.read!()\n      |> Code.string_to_quoted()\n\n    quoted\n    |> remove_mix_install()\n    |> Code.eval_quoted([], file: path)\n  end\n\n  defp remove_mix_install(quoted) do\n    Macro.prewalk(quoted, fn\n      {{:., _, [{:__aliases__, _, [:Mix]}, :install]}, _, _} ->\n        nil\n\n      ast ->\n        ast\n    end)\n  end\nend\n\nExamples.run(\"#{__DIR__}/../examples/demo_controller_test.exs\")\nExamples.run(\"#{__DIR__}/../examples/demo_live_test.exs\")\n"
  },
  {
    "path": "test/phoenix_playground_test.exs",
    "content": "defmodule PhoenixPlaygroundTest do\n  use ExUnit.Case, async: true\nend\n"
  },
  {
    "path": "test/test_helper.exs",
    "content": "ExUnit.start()\n"
  }
]