From e57471bef84e3ab79d91f952d6868a79bcd48490 Mon Sep 17 00:00:00 2001 From: Denis Donici Date: Wed, 4 Mar 2026 09:30:26 +0200 Subject: [PATCH] chore: added igniter installer --- assets/copy/build.js | 71 ---- assets/copy/js/app.js | 42 --- assets/copy/js/server.js | 4 - example_project/mix.lock | 1 + lib/logger.ex | 15 - lib/mix/tasks/configure_esbuild.ex | 23 -- lib/mix/tasks/configure_phoenix.ex | 116 ------- lib/mix/tasks/install_npm_deps.ex | 17 - lib/mix/tasks/live_svelte.install.ex | 497 +++++++++++++++++++++++++++ lib/mix/tasks/setup.ex | 18 - mix.exs | 6 +- mix.lock | 40 ++- 12 files changed, 529 insertions(+), 321 deletions(-) delete mode 100644 assets/copy/build.js delete mode 100644 assets/copy/js/app.js delete mode 100644 assets/copy/js/server.js delete mode 100644 lib/logger.ex delete mode 100644 lib/mix/tasks/configure_esbuild.ex delete mode 100644 lib/mix/tasks/configure_phoenix.ex delete mode 100644 lib/mix/tasks/install_npm_deps.ex create mode 100644 lib/mix/tasks/live_svelte.install.ex delete mode 100644 lib/mix/tasks/setup.ex diff --git a/assets/copy/build.js b/assets/copy/build.js deleted file mode 100644 index 0447c41..0000000 --- a/assets/copy/build.js +++ /dev/null @@ -1,71 +0,0 @@ -const esbuild = require("esbuild") -const sveltePlugin = require("esbuild-svelte") -const importGlobPlugin = require("esbuild-plugin-import-glob").default -const sveltePreprocess = require("svelte-preprocess") - -const args = process.argv.slice(2) -const watch = args.includes("--watch") -const deploy = args.includes("--deploy") - -let clientConditions = ["svelte", "browser"] -let serverConditions = ["svelte"] - -if (!deploy) { - clientConditions.push("development") - serverConditions.push("development") -} - -let optsClient = { - entryPoints: ["js/app.js"], - bundle: true, - minify: deploy, - conditions: clientConditions, - alias: {svelte: "svelte"}, - outdir: "../priv/static/assets/js", - logLevel: "info", - sourcemap: watch ? "inline" : false, - tsconfig: "./tsconfig.json", - plugins: [ - importGlobPlugin(), - sveltePlugin({ - preprocess: sveltePreprocess(), - compilerOptions: {dev: !deploy, css: "injected", generate: "client"}, - }), - ], -} - -let optsServer = { - entryPoints: ["js/server.js"], - platform: "node", - bundle: true, - minify: false, - target: "node19.6.1", - conditions: serverConditions, - alias: {svelte: "svelte"}, - outdir: "../priv/svelte", - logLevel: "info", - sourcemap: watch ? "inline" : false, - tsconfig: "./tsconfig.json", - plugins: [ - importGlobPlugin(), - sveltePlugin({ - preprocess: sveltePreprocess(), - compilerOptions: {dev: !deploy, css: "injected", generate: "server"}, - }), - ], -} - -if (watch) { - esbuild - .context(optsClient) - .then(ctx => ctx.watch()) - .catch(_error => process.exit(1)) - - esbuild - .context(optsServer) - .then(ctx => ctx.watch()) - .catch(_error => process.exit(1)) -} else { - esbuild.build(optsClient) - esbuild.build(optsServer) -} diff --git a/assets/copy/js/app.js b/assets/copy/js/app.js deleted file mode 100644 index 6524991..0000000 --- a/assets/copy/js/app.js +++ /dev/null @@ -1,42 +0,0 @@ -// If you want to use Phoenix channels, run `mix help phx.gen.channel` -// to get started and then uncomment the line below. -// import "./user_socket.js" - -// You can include dependencies in two ways. -// -// The simplest option is to put them in assets/vendor and -// import them using relative paths: -// -// import "../vendor/some-package.js" -// -// Alternatively, you can `npm install some-package --prefix assets` and import -// them using a path starting with the package name: -// -// import "some-package" -// - -// Include phoenix_html to handle method=PUT/DELETE in forms and buttons. -import "phoenix_html" -// Establish Phoenix Socket and LiveView configuration. -import {Socket} from "phoenix" -import {LiveSocket} from "phoenix_live_view" -import topbar from "../vendor/topbar" -import {getHooks} from "live_svelte" -import * as Components from "../svelte/**/*.svelte" - -let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") -let liveSocket = new LiveSocket("/live", Socket, {hooks: getHooks(Components), params: {_csrf_token: csrfToken}}) - -// Show progress bar on live navigation and form submits -topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"}) -window.addEventListener("phx:page-loading-start", _info => topbar.show(300)) -window.addEventListener("phx:page-loading-stop", _info => topbar.hide()) - -// connect if there are any LiveViews on the page -liveSocket.connect() - -// expose liveSocket on window for web console debug logs and latency simulation: -// >> liveSocket.enableDebug() -// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session -// >> liveSocket.disableLatencySim() -window.liveSocket = liveSocket diff --git a/assets/copy/js/server.js b/assets/copy/js/server.js deleted file mode 100644 index 9157f7c..0000000 --- a/assets/copy/js/server.js +++ /dev/null @@ -1,4 +0,0 @@ -import * as Components from "../svelte/**/*.svelte" -import {getRender} from "live_svelte" - -export const render = getRender(Components) diff --git a/example_project/mix.lock b/example_project/mix.lock index 1c92393..ef5bdb8 100644 --- a/example_project/mix.lock +++ b/example_project/mix.lock @@ -38,6 +38,7 @@ "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_test": {:hex, :phoenix_test, "0.9.1", "ac58a4d341c594ac57ce52a6ce643200084fad419a91b72896a44881fe84809c", [:mix], [{:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:lazy_html, "~> 0.1.7", [hex: :lazy_html, repo: "hexpm", optional: false]}, {:mime, ">= 1.0.0", [hex: :mime, repo: "hexpm", optional: true]}, {:phoenix, ">= 1.7.10", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "ed453394c0f8987aa58a06e2302e7dd4bc53cd2d25eff5a18c4a5775241ebe61"}, + "phoenix_vite": {:hex, :phoenix_vite, "0.4.1", "16fc8b4acc7d4e26fab2bcfb10d125392fcfb5a4ba05dd3aea7f2bec048c5e71", [:mix], [{:bun, ">= 1.5.1 and < 2.0.0-0", [hex: :bun, repo: "hexpm", optional: true]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "f2bdb1802bc82f2fa93b24606cd25ebdd389bcf2afc55af804f0af3377410f0d"}, "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, diff --git a/lib/logger.ex b/lib/logger.ex deleted file mode 100644 index 9702b5e..0000000 --- a/lib/logger.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule LiveSvelte.Logger do - @moduledoc false - - @doc false - def log_info(status), do: Mix.shell().info([status, :reset]) - - @doc false - def log_success(status), do: Mix.shell().info([:green, status, :reset]) - - @doc false - def log_warning(status), do: Mix.shell().info([:yellow, status, :reset]) - - @doc false - def log_error(status), do: Mix.shell().error([status, :reset]) -end diff --git a/lib/mix/tasks/configure_esbuild.ex b/lib/mix/tasks/configure_esbuild.ex deleted file mode 100644 index 75d52c9..0000000 --- a/lib/mix/tasks/configure_esbuild.ex +++ /dev/null @@ -1,23 +0,0 @@ -defmodule Mix.Tasks.LiveSvelte.ConfigureEsbuild do - @moduledoc """ - Creates Javascript files to be used by esbuild. Necessary for LiveSvelte to work. - """ - - import LiveSvelte.Logger - - def run(_) do - log_info("-- Configuring esbuild...") - - Mix.Project.deps_paths(depth: 1) - |> Map.fetch!(:live_svelte) - |> Path.join("assets/copy/**/*.{js,json}") - |> Path.wildcard() - |> Enum.each(fn full_path -> - [_beginning, relative_path] = String.split(full_path, "copy", parts: 2) - - Mix.Generator.copy_file(full_path, "assets" <> relative_path) - end) - - Mix.Generator.create_directory("assets/svelte") - end -end diff --git a/lib/mix/tasks/configure_phoenix.ex b/lib/mix/tasks/configure_phoenix.ex deleted file mode 100644 index edb1c6c..0000000 --- a/lib/mix/tasks/configure_phoenix.ex +++ /dev/null @@ -1,116 +0,0 @@ -defmodule Mix.Tasks.LiveSvelte.ConfigurePhoenix do - @moduledoc """ - Configures any necessary code changes inside Phoenix to make LiveSvelte work. - """ - - import LiveSvelte.Logger - - @watcher_regex ~r/watchers:\s\[(?!\s+node:)/ - @esbuild_regex ~r/(? - err - |> Exception.message() - |> log_error() - end - - Mix.Task.run("format") - end - - defp configure_dev_config() do - text = ~s""" - node: ["build.js", "--watch", cd: Path.expand("../assets", __DIR__)],\ - """ - - {path, file} = path_and_file("config/", "dev.exs") - - File.read!(path) - |> insert(@watcher_regex, text, "'#{text}' in #{file}") - |> comment(@esbuild_regex, "old esbuild watcher in #{file}") - |> save(path) - end - - defp configure_application() do - text = ~s""" - {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]},\ - """ - - {path, file} = path_and_file("lib/**/", "application.ex") - - File.read!(path) - |> insert(@nodejs_regex, text, "'#{text}' in #{file}") - |> save(path) - end - - defp configure_gitignore() do - text = ~s""" - - - # Ignore automatically generated Svelte files by the ~V sigil - /assets/svelte/_build/ - - # Ignore ssr build for svelte. - /priv/svelte/\ - """ - - {path, file} = path_and_file("", ".gitignore") - - File.read!(path) - |> insert(@gitignore_regex, text, "'#{text}' in #{file}") - |> save(path) - end - - defp path_and_file(wildcard, filename) do - {find_file("#{wildcard}#{filename}", filename), filename} - end - - defp find_file(wildcard, file_name) do - with [path] <- Path.wildcard(wildcard) do - path - else - [] -> raise "Could not find #{file_name}" - [_ | _] -> raise "Found multiple #{file_name} files" - end - end - - defp insert(source, regex, to_insert, name) do - case Regex.run(regex, source, return: :index) do - [{pos, len}] -> - log_success("Inserted #{name}") - insert_position(source, pos + len, to_insert) - - nil -> - log_error("Could not insert #{name}, please do so yourself") - source - end - end - - defp comment(source, regex, name) do - case Regex.run(regex, source, return: :index) do - [{pos, _len}] -> - log_success("Commented out #{name}") - insert_position(source, pos, "# ") - - nil -> - log_warning("Could not comment out #{name}") - source - end - end - - defp insert_position(source, position, to_insert) do - {head, tail} = String.split_at(source, position) - head <> to_insert <> tail - end - - defp save(source, target_file), do: File.write!(target_file, source) -end diff --git a/lib/mix/tasks/install_npm_deps.ex b/lib/mix/tasks/install_npm_deps.ex deleted file mode 100644 index b294de1..0000000 --- a/lib/mix/tasks/install_npm_deps.ex +++ /dev/null @@ -1,17 +0,0 @@ -defmodule Mix.Tasks.LiveSvelte.InstallNpmDeps do - @moduledoc """ - Installs npm dependencies for LiveSvelte. - """ - - import LiveSvelte.Logger - - def run(_) do - log_info("-- Installing npm dependencies...") - - "npm install --prefix ./assets --save-dev esbuild esbuild-svelte svelte svelte-preprocess esbuild-plugin-import-glob && - npm install --prefix ./assets --save ./deps/phoenix ./deps/phoenix_html ./deps/phoenix_live_view ./deps/live_svelte" - |> String.to_charlist() - |> :os.cmd() - |> IO.puts() - end -end diff --git a/lib/mix/tasks/live_svelte.install.ex b/lib/mix/tasks/live_svelte.install.ex new file mode 100644 index 0000000..0584384 --- /dev/null +++ b/lib/mix/tasks/live_svelte.install.ex @@ -0,0 +1,497 @@ +defmodule Mix.Tasks.LiveSvelte.Install do + @moduledoc """ + Installer for LiveSvelte with Vite. + + This task first installs Vite using the PhoenixVite installer, + then configures the project for LiveSvelte. + + ## Options + + * `--bun` - Use Bun instead of Node.js/npm + + ## Examples + + mix igniter.install live_svelte + mix igniter.install live_svelte --bun + + """ + + import Mix.Tasks.PhoenixVite.Install.Helper + + with_igniter do + use Igniter.Mix.Task + + alias Igniter.Libs.Phoenix + alias Igniter.Project.Config + + @impl Igniter.Mix.Task + def info(_argv, _parent) do + %Igniter.Mix.Task.Info{ + composes: ["phoenix_vite.install"], + schema: [bun: :boolean], + aliases: [b: :bun] + } + end + + @impl Igniter.Mix.Task + def igniter(igniter) do + app_name = Igniter.Project.Application.app_name(igniter) + + igniter + |> Igniter.compose_task("phoenix_vite.install", igniter.args.argv) + |> configure_environments(app_name) + |> add_live_svelte_to_html_helpers(app_name) + |> update_javascript_configuration() + |> configure_tailwind_for_svelte() + |> update_vite_configuration() + |> update_package_json_for_svelte() + |> create_svelte_files() + |> setup_ssr_for_production(app_name) + |> update_mix_aliases() + |> add_svelte_demo_route() + |> update_home_template() + |> update_gitignore() + |> create_ssr_vite_config() + end + + # Configure environments (config.exs, dev.exs, prod.exs) + defp configure_environments(igniter, _app_name) do + igniter + |> Config.configure("config.exs", :live_svelte, [:ssr], true) + |> Config.configure("dev.exs", :live_svelte, [:ssr_module], {:code, Sourceror.parse_string!("LiveSvelte.SSR.ViteJS")}) + |> Config.configure("dev.exs", :live_svelte, [:vite_host], "http://localhost:5173") + |> Config.configure("prod.exs", :live_svelte, [:ssr_module], {:code, Sourceror.parse_string!("LiveSvelte.SSR.NodeJS")}) + |> Config.configure("prod.exs", :live_svelte, [:ssr], true) + end + + # Add import LiveSvelte to html_helpers in lib/app_web.ex + defp add_live_svelte_to_html_helpers(igniter, _app_name) do + web_module = Phoenix.web_module(igniter) + web_folder = Macro.underscore(web_module) + web_file = Path.join(["lib", web_folder <> ".ex"]) + + Igniter.update_file(igniter, web_file, fn source -> + Rewrite.Source.update(source, :content, fn content -> + if String.contains?(content, "import LiveSvelte") do + content + else + # Add after the use Gettext or import ...Gettext line in html_helpers + web_module_name = web_module |> Module.split() |> Enum.join(".") + + result = + String.replace( + content, + ~r/(use Gettext, backend: #{Regex.escape(web_module_name)}\.Gettext)/, + "\\1\n\n import LiveSvelte" + ) + + # Fallback: try matching import ...Gettext pattern + if result == content do + String.replace( + content, + ~r/(import #{Regex.escape(web_module_name)}\.Gettext)/, + "\\1\n\n import LiveSvelte" + ) + else + result + end + end + end) + end) + end + + # Update app.js to import getHooks and Components from live_svelte + defp update_javascript_configuration(igniter) do + Igniter.update_file(igniter, "assets/js/app.js", fn source -> + Rewrite.Source.update(source, :content, fn content -> + content + |> add_live_svelte_imports() + |> update_live_socket_hooks() + end) + end) + end + + defp add_live_svelte_imports(content) do + if String.contains?(content, "import {getHooks} from \"live_svelte\"") do + content + else + String.replace( + content, + "import topbar from \"topbar\"", + ~s(import topbar from "topbar"\nimport {getHooks} from "live_svelte"\nimport Components from "virtual:live-svelte-components") + ) + end + end + + defp update_live_socket_hooks(content) do + String.replace( + content, + "hooks: {...colocatedHooks},", + "hooks: {...colocatedHooks, ...getHooks(Components)}," + ) + end + + # Configure Tailwind to include Svelte files (add @source "../svelte"; to app.css) + defp configure_tailwind_for_svelte(igniter) do + if Igniter.exists?(igniter, "assets/css/app.css") do + Igniter.update_file(igniter, "assets/css/app.css", fn source -> + Rewrite.Source.update(source, :content, fn content -> + if String.contains?(content, "@source \"../svelte\";") do + content + else + String.replace( + content, + "@source \"../js\";", + ~s(@source "../js";\n@source "../svelte";) + ) + end + end) + end) + else + igniter + end + end + + # Update vite.config.mjs to add Svelte plugin and liveSveltePlugin + defp update_vite_configuration(igniter) do + Igniter.update_file(igniter, "assets/vite.config.mjs", fn source -> + Rewrite.Source.update(source, :content, fn content -> + content + |> add_svelte_vite_imports() + |> update_vite_optimized_deps() + |> update_vite_plugins() + |> add_ssr_config() + end) + end) + end + + defp add_svelte_vite_imports(content) do + if String.contains?(content, "import { svelte }") do + content + else + String.replace( + content, + "import { phoenixVitePlugin } from 'phoenix_vite'", + ~s(import { svelte } from "@sveltejs/vite-plugin-svelte"\nimport liveSveltePlugin from "live_svelte/vitePlugin") + ) + end + end + + defp update_vite_optimized_deps(content) do + if String.contains?(content, "\"live_svelte\"") do + content + else + String.replace( + content, + ~s(include: ["phoenix", "phoenix_html", "phoenix_live_view"],), + ~s(include: ["live_svelte", "phoenix", "phoenix_html", "phoenix_live_view"],) + ) + end + end + + defp update_vite_plugins(content) do + if String.contains?(content, "svelte(") do + content + else + String.replace( + content, + ~r/phoenixVitePlugin\(\{\s*pattern: \/\\.\(ex\|heex\)\$\/\s*\}\)/s, + "svelte({ compilerOptions: { css: \"injected\" } }),\n liveSveltePlugin({ entrypoint: \"./js/server.js\" })" + ) + end + end + + defp add_ssr_config(content) do + if String.contains?(content, "noExternal") do + content + else + String.replace( + content, + ~r/build: \{/s, + "ssr: { noExternal: process.env.NODE_ENV === \"production\" ? true : undefined },\n build: {" + ) + end + end + + # Update package.json with Svelte dependencies + defp update_package_json_for_svelte(igniter) do + igniter + |> Igniter.move_file("assets/package.json", "package.json") + |> Igniter.update_file("package.json", fn source -> + Rewrite.Source.update(source, :content, fn content -> + content + |> add_svelte_dependency() + |> add_svelte_dev_dependencies() + end) + end) + end + + defp add_svelte_dependency(content) do + if String.contains?(content, "\"live_svelte\"") do + content + else + # Add live_svelte to dependencies section + String.replace( + content, + ~s("phoenix": "file:./deps/phoenix"), + ~s("live_svelte": "file:./deps/live_svelte",\n "phoenix": "file:./deps/phoenix") + ) + end + end + + defp add_svelte_dev_dependencies(content) do + if String.contains?(content, "\"@sveltejs/vite-plugin-svelte\"") do + content + else + String.replace( + content, + ~s("typescript":), + ~s("@sveltejs/vite-plugin-svelte": "^5.0.0",\n "svelte": "^5.0.0",\n "typescript":) + ) + end + end + + # Create Svelte project files + defp create_svelte_files(igniter) do + web_module = Phoenix.web_module(igniter) + web_folder = Macro.underscore(web_module) + + igniter + |> Igniter.mkdir("assets/svelte") + |> Igniter.mkdir("lib/#{web_folder}/live") + |> Igniter.create_new_file("assets/js/server.js", server_js_content()) + |> Igniter.create_new_file( + "assets/svelte/.gitignore", + "# Ignore auto-generated Svelte files by ~V sigil\n_build/" + ) + |> Igniter.create_new_file("assets/svelte/SvelteDemo.svelte", demo_svelte_content()) + |> Igniter.create_new_file( + "lib/#{web_folder}/live/svelte_demo_live.ex", + demo_live_view_content(igniter) + ) + end + + # Setup NodeJS SSR supervisor in application.ex + defp setup_ssr_for_production(igniter, _app_name) do + app_module = igniter |> Igniter.Project.Application.app_name() |> to_string() + app_file = "lib/#{Macro.underscore(app_module)}/application.ex" + + Igniter.update_file(igniter, app_file, fn source -> + Rewrite.Source.update(source, :content, fn content -> + if String.contains?(content, "children = [") and not String.contains?(content, "NodeJS.Supervisor") do + String.replace( + content, + ~r/(children = \[\s*\n)/, + "\\1 {NodeJS.Supervisor, [path: LiveSvelte.SSR.NodeJS.server_path(), pool_size: 4]},\n" + ) + else + content + end + end) + end) + end + + # Update mix.exs aliases in the consumer app to use Vite + defp update_mix_aliases(igniter) do + bun? = igniter.args.options[:bun] || false + pm = if bun?, do: "bunx", else: "npx" + + Igniter.update_file(igniter, "mix.exs", fn source -> + Rewrite.Source.update(source, :content, fn content -> + if String.contains?(content, "vite build") do + content + else + content + |> add_assets_js_alias(pm) + |> update_assets_deploy_alias(pm) + end + end) + end) + end + + defp add_assets_js_alias(content, pm) do + if String.contains?(content, "\"assets.js\"") do + content + else + String.replace( + content, + ~r/("assets\.setup":)/, + ~s("assets.js": [\n "cmd --cd assets #{pm} vite build",\n "cmd --cd assets #{pm} vite build --config vite.ssr.config.js",\n "tailwind default"\n ],\n \\1) + ) + end + end + + defp update_assets_deploy_alias(content, pm) do + # Replace esbuild references in assets.deploy with Vite commands + String.replace( + content, + ~r/"esbuild default[^"]*"/, + ~s("cmd --cd assets #{pm} vite build --mode production", "cmd --cd assets #{pm} vite build --config vite.ssr.config.js --mode production") + ) + end + + # Add svelte_demo route to router + defp add_svelte_demo_route(igniter) do + web_module = Phoenix.web_module(igniter) + web_folder = Macro.underscore(web_module) + web_module_name = web_module |> Module.split() |> Enum.join(".") + router_file = Path.join(["lib", web_folder, "router.ex"]) + + Igniter.update_file(igniter, router_file, fn source -> + Rewrite.Source.update(source, :content, fn content -> + if String.contains?(content, "live \"/svelte_demo\"") do + content + else + if String.contains?(content, "live_dashboard") do + String.replace( + content, + ~r/(live_dashboard.*)/, + "\\1\n live \"/svelte_demo\", #{web_module_name}.SvelteDemoLive" + ) + else + String.replace( + content, + ~r/(pipe_through :browser.*)/, + "\\1\n live \"/dev/svelte_demo\", #{web_module_name}.SvelteDemoLive" + ) + end + end + end) + end) + end + + # Update home template with LiveSvelte info + defp update_home_template(igniter) do + web_module = Phoenix.web_module(igniter) + web_folder = Macro.underscore(web_module) + home_template = Path.join(["lib", web_folder, "controllers", "page_html", "home.html.heex"]) + + Igniter.update_file(igniter, home_template, fn source -> + Rewrite.Source.update(source, :content, fn content -> + content + |> String.replace( + "Peace of mind from prototype to production.", + "End-to-end reactivity for your Live Svelte apps." + ) + |> String.replace( + ~s(
), + ~s(Svelte Demo\n
) + ) + end) + end) + end + + # Add gitignore entries for Svelte build artifacts + defp update_gitignore(igniter) do + Igniter.update_file(igniter, ".gitignore", fn source -> + Rewrite.Source.update(source, :content, fn content -> + content + |> add_gitignore_entry("/assets/svelte/_build/") + |> add_gitignore_entry("/priv/svelte/") + end) + end) + end + + defp add_gitignore_entry(content, entry) do + if String.contains?(content, entry) do + content + else + content <> "\n#{entry}" + end + end + + # Create the SSR Vite config file + defp create_ssr_vite_config(igniter) do + Igniter.create_new_file(igniter, "assets/vite.ssr.config.js", ssr_vite_config_content()) + end + + # Content helpers + + defp server_js_content do + """ + import { getRender } from "live_svelte" + import Components from "virtual:live-svelte-components" + export const render = getRender(Components) + """ + end + + defp ssr_vite_config_content do + """ + import { defineConfig } from "vite" + import { svelte } from "@sveltejs/vite-plugin-svelte" + + export default defineConfig({ + plugins: [svelte()], + ssr: { noExternal: true }, + build: { + ssr: "./js/server.js", + outDir: "../priv/svelte", + rollupOptions: { + output: { entryFileNames: "server.js", format: "es" } + } + } + }) + """ + end + + defp demo_svelte_content do + """ + + +
+

LiveSvelte Demo

+

Count: {count}

+ + +
+ """ + end + + defp demo_live_view_content(igniter) do + web_module_name = Phoenix.web_module(igniter) |> Module.split() |> Enum.join(".") + + """ + defmodule #{web_module_name}.SvelteDemoLive do + use #{web_module_name}, :live_view + + @impl true + def render(assigns) do + ~H\"\"\" + <.svelte name="SvelteDemo" props={%{count: @count}} socket={@socket} /> + \"\"\" + end + + @impl true + def mount(_params, _session, socket) do + {:ok, assign(socket, count: 0)} + end + + @impl true + def handle_event("increment", _params, socket) do + {:noreply, update(socket, :count, &(&1 + 1))} + end + + @impl true + def handle_event("decrement", _params, socket) do + {:noreply, update(socket, :count, &(&1 - 1))} + end + end + """ + end + else + use Mix.Task + + @impl Mix.Task + def run(_argv) do + Mix.shell().error(""" + The task 'live_svelte.install' requires igniter. Please install igniter and try again. + + For more information, see: https://hexdocs.pm/igniter/readme.html#installation + """) + + exit({:shutdown, 1}) + end + end +end diff --git a/lib/mix/tasks/setup.ex b/lib/mix/tasks/setup.ex deleted file mode 100644 index fe7c3ce..0000000 --- a/lib/mix/tasks/setup.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Mix.Tasks.LiveSvelte.Setup do - @moduledoc """ - Runs all setup tasks for LiveSvelte. - """ - - import LiveSvelte.Logger - - def run(_) do - [ - "install_npm_deps", - "configure_phoenix", - "configure_esbuild" - ] - |> Enum.each(&Mix.Task.run("live_svelte." <> &1)) - - log_success("live_svelte setup finished.") - end -end diff --git a/mix.exs b/mix.exs index 409a89e..2745c2e 100644 --- a/mix.exs +++ b/mix.exs @@ -45,7 +45,7 @@ defmodule LiveSvelte.MixProject do GitHub: @repo_url }, files: - ~w(assets/copy assets/js lib mix.exs package.json .formatter.exs LICENSE.md README.md CHANGELOG.md) + ~w(assets/copy/tsconfig.json assets/js lib mix.exs package.json .formatter.exs LICENSE.md README.md CHANGELOG.md) ] end @@ -61,6 +61,8 @@ defmodule LiveSvelte.MixProject do defp deps do [ {:ex_doc, "~> 0.37.3", only: :dev, runtime: false}, + {:igniter, "~> 0.6", optional: true}, + {:phoenix_vite, "~> 0.4"}, {:jsonpatch, "~> 2.3"}, {:ecto, ">= 3.0.0", optional: true}, {:phoenix_ecto, ">= 4.0.0", optional: true}, @@ -70,7 +72,7 @@ defmodule LiveSvelte.MixProject do {:telemetry, "~> 0.4 or ~> 1.0"}, {:phoenix, ">= 1.7.0"}, {:phoenix_html, ">= 3.3.1"}, - {:phoenix_live_view, ">= 0.18.0"} + {:phoenix_live_view, "~> 1.0"} ] end diff --git a/mix.lock b/mix.lock index 58c20ed..72eba90 100644 --- a/mix.lock +++ b/mix.lock @@ -7,28 +7,42 @@ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"}, "esbuild": {:hex, :esbuild, "0.6.1", "a774bfa7b4512a1211bf15880b462be12a4c48ed753a170c68c63b2c95888150", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "569f7409fb5a932211573fc20e2a930a0d5cf3377c5b4f6506c651b1783a1678"}, "ex_doc": {:hex, :ex_doc, "0.37.3", "f7816881a443cd77872b7d6118e8a55f547f49903aef8747dbcb345a75b462f9", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "e6aebca7156e7c29b5da4daa17f6361205b2ae5f26e5c7d8ca0d3f7e18972233"}, + "finch": {:hex, :finch, "0.21.0", "b1c3b2d48af02d0c66d2a9ebfb5622be5c5ecd62937cf79a88a7f98d48a8290c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "87dc6e169794cb2570f75841a19da99cfde834249568f2a5b121b809588a4377"}, "fine": {:hex, :fine, "0.1.4", "b19a89c1476c7c57afb5f9314aed5960b5bc95d5277de4cb5ee8e1d1616ce379", [:mix], [], "hexpm", "be3324cc454a42d80951cf6023b9954e9ff27c6daa255483b3e8d608670303f5"}, + "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "igniter": {:hex, :igniter, "0.7.2", "81c132c0df95963c7a228f74a32d3348773743ed9651f24183bfce0fe6ff16d1", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "f4cab73ec31f4fb452de1a17037f8a08826105265aa2d76486fcb848189bef9b"}, "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jsonpatch": {:hex, :jsonpatch, "2.3.1", "49c380f458debbd2bc6e256daeab1081dc89624288f3d77ea83952229388d316", [:make, :mix], [], "hexpm", "06c3e4fff3574cc54d335041f6322fe1b72756e396dd472615ce350d3dd5e758"}, "lazy_html": {:hex, :lazy_html, "0.1.10", "ffe42a0b4e70859cf21a33e12a251e0c76c1dff76391609bd56702a0ef5bc429", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.9.0", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:fine, "~> 0.1.0", [hex: :fine, repo: "hexpm", optional: false]}], "hexpm", "50f67e5faa09d45a99c1ddf3fac004f051997877dc8974c5797bb5ccd8e27058"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, - "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.3", "4252d5d4098da7415c390e847c814bad3764c94a814a0b4245176215615e1035", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "953297c02582a33411ac6208f2c6e55f0e870df7f80da724ed613f10e6706afd"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, - "nodejs": {:hex, :nodejs, "3.1.0", "904c07b81a7b6077af35784df32ab36c62bd2b96edb91bfd04c157c21956cfa5", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1", [hex: :poolboy, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.7", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "a6b4480f3f266abb5927be8afacfc7809feefd7d1337fa3ce957d0b98eeeae52"}, - "phoenix": {:hex, :phoenix, "1.7.0", "cbed113bdc203e2ced75859011fe7e71eeebb6259cefa54de810d9c7048b5e22", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8526139d4bd79ec97c5c3c8e69f6cd663597f782756cec874ba7da5429c93e34"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "nodejs": {:hex, :nodejs, "3.1.3", "8693fae9fbefa14fb99329292c226df4d4711acfa5a3fa4182dd8d3f779b30bf", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5.1", [hex: :poolboy, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.7", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e7751aad77ac55f8e6c5c07617378afd88d2e0c349d9db2ebb5273aae46ef6a9"}, + "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, + "phoenix": {:hex, :phoenix, "1.8.4", "0387f84f00071cba8d71d930b9121b2fb3645197a9206c31b908d2e7902a4851", [:mix], [{:bandit, "~> 1.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c988b1cd3b084eebb13e6676d572597d387fa607dab258526637b4e6c4c08543"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.7.0", "75c4b9dfb3efdc42aec2bd5f8bccd978aca0651dbcbc7a3f362ea5d9d43153c6", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "1d75011e4254cb4ddf823e81823a9629559a1be93b4321a6a5f11a5306fbf4cc"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.15", "58137e648fca9da56d6e931c9c3001f895ff090291052035f395bc958b82f1a5", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "888dd8ea986bebbda741acc65aef788c384d13db91fea416461b2e96aa06a193"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, - "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, + "phoenix_html": {:hex, :phoenix_html, "4.3.0", "d3577a5df4b6954cd7890c84d955c470b5310bb49647f0a114a6eeecc850f7ad", [:mix], [], "hexpm", "3eaa290a78bab0f075f791a46a981bbe769d94bc776869f4f3063a14f30497ad"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "1.1.25", "abc1bdf7f148d7f9a003f149834cc858b24290c433b10ef6d1cbb1d6e9a211ca", [:mix], [{:igniter, ">= 0.6.16 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:lazy_html, "~> 0.1.0", [hex: :lazy_html, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0 or ~> 1.8.0-rc", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b8946e474799da1f874eab7e9ce107502c96ca318ed46d19f811f847df270865"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.2.0", "ff3a5616e1bed6804de7773b92cbccfc0b0f473faf1f63d7daf1206c7aeaaa6f", [:mix], [], "hexpm", "adc313a5bf7136039f63cfd9668fde73bba0765e0614cba80c06ac9460ff3e96"}, + "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "phoenix_vite": {:hex, :phoenix_vite, "0.4.1", "16fc8b4acc7d4e26fab2bcfb10d125392fcfb5a4ba05dd3aea7f2bec048c5e71", [:mix], [{:bun, ">= 1.5.1 and < 2.0.0-0", [hex: :bun, repo: "hexpm", optional: true]}, {:igniter, "~> 0.6", [hex: :igniter, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "f2bdb1802bc82f2fa93b24606cd25ebdd389bcf2afc55af804f0af3377410f0d"}, + "plug": {:hex, :plug, "1.19.1", "09bac17ae7a001a68ae393658aa23c7e38782be5c5c00c80be82901262c394c0", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "560a0017a8f6d5d30146916862aaf9300b7280063651dd7e532b8be168511e62"}, + "plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, + "req": {:hex, :req, "0.5.17", "0096ddd5b0ed6f576a03dde4b158a0c727215b15d2795e59e0916c6971066ede", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0b8bc6ffdfebbc07968e59d3ff96d52f2202d0536f10fef4dc11dc02a2a43e39"}, + "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, + "sourceror": {:hex, :sourceror, "1.11.0", "df2cdaffdc323e804009ff50b50bb31e6f2d6e116d936ccf22981f592594d624", [:mix], [], "hexpm", "6e26f572bdfc21d7ad397f596b4cfbbf31d7112126fe3e902c120947073231a8"}, + "spitfire": {:hex, :spitfire, "0.3.8", "4151d539b11eab51903fafcf0eed50d64247ce732fcba24680f5428db406c5f6", [:mix], [], "hexpm", "4328ee4af65d41ea1a7874808e36c1c7fc4a7dafb932f006c07aa1847e0c9dd5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, - "websock": {:hex, :websock, "0.4.3", "184ac396bdcd3dfceb5b74c17d221af659dd559a95b1b92041ecb51c9b728093", [:mix], [], "hexpm", "5e4dd85f305f43fd3d3e25d70bec4a45228dfed60f0f3b072d8eddff335539cf"}, - "websock_adapter": {:hex, :websock_adapter, "0.4.5", "30038a3715067f51a9580562c05a3a8d501126030336ffc6edb53bf57d6d2d26", [:mix], [{:bandit, "~> 0.6", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.4", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "1d9812dc7e703c205049426fd4fe0852a247a825f91b099e53dc96f68bafe4c8"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.9", "43dc3ba6d89ef5dec5b1d0a39698436a1e856d000d84bf31a3149862b01a287f", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "5534d5c9adad3c18a0f58a9371220d75a803bf0b9a3d87e6fe072faaeed76a08"}, }