Packages

phoenix_kit

1.2.2
1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib mix tasks phoenix_kit.update.ex
Raw

lib/mix/tasks/phoenix_kit.update.ex

defmodule Mix.Tasks.PhoenixKit.Update do
use Mix.Task
@moduledoc """
Updates PhoenixKit to the latest version.
This task handles updating an existing PhoenixKit installation to the latest version
by creating upgrade migrations that preserve existing data while adding new features.
## Usage
$ mix phoenix_kit.update
$ mix phoenix_kit.update --prefix=myapp
$ mix phoenix_kit.update --status
$ mix phoenix_kit.update --skip-assets
## Options
* `--prefix` - Database schema prefix (default: "public")
* `--status` - Show current installation status and available updates
* `--force` - Force update even if already up to date
* `--skip-assets` - Skip automatic asset rebuild check
## Examples
# Update PhoenixKit to latest version
mix phoenix_kit.update
# Check what version is installed and what updates are available
mix phoenix_kit.update --status
# Update with custom schema prefix
mix phoenix_kit.update --prefix=auth
## Version Management
PhoenixKit uses a versioned migration system similar to Oban. Each version
contains specific database schema changes that can be applied incrementally.
Current version: V02 (simplified role system without is_active column)
- V01: Basic authentication with role system
- V02: Remove is_active column from role assignments (direct deletion)
## Safe Updates
All PhoenixKit updates are designed to be:
- Non-destructive (existing data is preserved)
- Backward compatible (existing code continues to work)
- Idempotent (safe to run multiple times)
- Rollback-capable (can be reverted if needed)
"""
alias PhoenixKit.Migrations.Postgres
alias PhoenixKit.Install.{AssetRebuild, Common}
@shortdoc "Updates PhoenixKit to the latest version"
@switches [
prefix: :string,
status: :boolean,
force: :boolean,
skip_assets: :boolean
]
@aliases [
p: :prefix,
s: :status,
f: :force
]
@impl Mix.Task
def run(argv) do
{opts, _argv, _errors} = OptionParser.parse(argv, switches: @switches, aliases: @aliases)
if opts[:status] do
show_status(opts)
else
perform_update(opts)
end
end
# Show current installation status and available updates
defp show_status(opts) do
prefix = opts[:prefix] || "public"
case check_installation_status(prefix) do
{:not_installed} ->
Mix.shell().info("""
❌ PhoenixKit is not installed.
Please run: mix phoenix_kit.install
""")
{:current_version, version} ->
target_version = Postgres.current_version()
if version >= target_version do
Mix.shell().info("""
✅ PhoenixKit is up to date!
Current version: V#{pad_version(version)}
Latest version: V#{pad_version(target_version)}
""")
else
changes = describe_version_changes(version, target_version)
Mix.shell().info("""
📦 PhoenixKit Update Available!
Current version: V#{pad_version(version)}
Latest version: V#{pad_version(target_version)}
What's new:
#{changes}
To update, run: mix phoenix_kit.update
""")
end
end
end
# Handle not installed scenario
defp handle_not_installed do
Mix.shell().error("""
❌ PhoenixKit is not installed.
Please run: mix phoenix_kit.install
""")
end
# Handle update check logic
defp handle_update_check(prefix, current_version, force, skip_assets) do
target_version = Common.current_version()
cond do
current_version >= target_version && !force ->
handle_already_up_to_date(current_version)
current_version < target_version || force ->
handle_update_needed(prefix, current_version, target_version, force, skip_assets)
true ->
Mix.shell().info("No update needed.")
end
end
# Handle already up to date scenario
defp handle_already_up_to_date(current_version) do
Mix.shell().info("""
✅ PhoenixKit is already up to date (V#{pad_version(current_version)}).
Use --force to regenerate the migration anyway.
""")
end
# Handle update needed scenario
defp handle_update_needed(prefix, current_version, target_version, force, skip_assets) do
create_update_migration(prefix, current_version, target_version, force)
suggest_layout_integration_if_needed()
# Check and rebuild assets after migration creation (unless skipped)
unless skip_assets do
suggest_asset_rebuild_if_needed(current_version, target_version)
end
end
# Perform the actual update
defp perform_update(opts) do
prefix = opts[:prefix] || "public"
force = opts[:force] || false
skip_assets = opts[:skip_assets] || false
case check_installation_status(prefix) do
{:not_installed} ->
handle_not_installed()
{:current_version, current_version} ->
handle_update_check(prefix, current_version, force, skip_assets)
end
end
# Create update migration from current to target version
defp create_update_migration(prefix, current_version, target_version, force) do
create_schema = prefix != "public"
# Ensure migrations directory exists
migrations_dir = "priv/repo/migrations"
File.mkdir_p!(migrations_dir)
# Generate timestamp and migration file name using Ecto format
timestamp = generate_timestamp()
action = if force, do: "force_update", else: "update"
migration_name =
"#{timestamp}_phoenix_kit_#{action}_v#{pad_version(current_version)}_to_v#{pad_version(target_version)}.exs"
migration_file = Path.join(migrations_dir, migration_name)
# Generate module name
module_name =
"PhoenixKit#{String.capitalize(action)}V#{pad_version(current_version)}ToV#{pad_version(target_version)}"
# Create migration content
migration_content = """
defmodule Ecto.Migrations.#{module_name} do
@moduledoc false
use Ecto.Migration
def up do
# PhoenixKit Update Migration: V#{pad_version(current_version)} -> V#{pad_version(target_version)}
PhoenixKit.Migrations.up([
prefix: "#{prefix}",
version: #{target_version},
create_schema: #{create_schema}
])
end
def down do
# Rollback PhoenixKit to V#{pad_version(current_version)}
PhoenixKit.Migrations.down([
prefix: "#{prefix}",
version: #{current_version}
])
end
end
"""
# Write migration file
File.write!(migration_file, migration_content)
# Show success notice with version information
changes = describe_version_changes(current_version, target_version)
phoenix_kit_version =
case :application.get_key(:phoenix_kit, :vsn) do
{:ok, vsn} when is_list(vsn) -> List.to_string(vsn)
{:ok, vsn} -> to_string(vsn)
:undefined -> "unknown"
end
notice = """
📦 PhoenixKit Update Migration Created:
- PhoenixKit Module Version: #{phoenix_kit_version}
- Migration: #{migration_name}
- Updating from Migration V#{pad_version(current_version)} to V#{pad_version(target_version)}
What's new:
#{changes}
Next steps:
1. Run: mix ecto.migrate
2. Your PhoenixKit installation will be updated!
#{if current_version > 0 do
"Note: This update preserves all existing data and is fully backward compatible."
else
""
end}
"""
Mix.shell().info(notice)
end
# Suggest asset rebuild if needed after update
defp suggest_asset_rebuild_if_needed(_current_version, target_version) do
if AssetRebuild.asset_rebuild_needed?(false) do
Mix.shell().info("""
🎨 Asset Rebuild Recommended:
PhoenixKit V#{pad_version(target_version)} includes CSS/theme changes that may
require rebuilding your application's assets.
To rebuild assets automatically:
mix phoenix_kit.assets.rebuild
Or check if rebuild is needed:
mix phoenix_kit.assets.rebuild --check
This ensures your application uses the latest PhoenixKit styles.
""")
end
end
# Check what version of PhoenixKit is currently installed
defp check_installation_status(prefix) do
# Use the same version detection logic as the migration system
opts = %{prefix: prefix, escaped_prefix: String.replace(prefix, "'", "\\'")}
try do
# Use PhoenixKit's centralized runtime version detection function
current_version = Postgres.migrated_version_runtime(opts)
if current_version == 0 do
# Check if migration files exist but haven't been run
case find_existing_phoenix_kit_migrations() do
[] -> {:not_installed}
# Migration files exist but not run - treat as V01 (first version)
_migrations -> {:current_version, 1}
end
else
{:current_version, current_version}
end
rescue
_ ->
# Database error, check migration files as fallback
case find_existing_phoenix_kit_migrations() do
[] -> {:not_installed}
# Migration files exist but DB not accessible - assume V01
_migrations -> {:current_version, 1}
end
end
end
# Find existing PhoenixKit migrations in the project
defp find_existing_phoenix_kit_migrations do
if File.exists?("priv/repo/migrations") do
"priv/repo/migrations"
|> File.ls!()
|> Enum.filter(&String.contains?(&1, "phoenix_kit"))
|> Enum.map(&Path.join("priv/repo/migrations", &1))
else
[]
end
end
# Describe what changed between versions
@spec describe_version_changes(integer(), integer()) :: String.t()
defp describe_version_changes(from_version, to_version) do
case {from_version, to_version} do
{1, 3} ->
"- Remove is_active column from role assignments (simplified role system)\n" <>
"- Add settings table with user preferences support"
{2, 3} ->
"- Add settings table with user preferences support"
{_, _} ->
"- Various improvements and new features"
end
end
# Generate timestamp in Ecto migration format (same as phoenix_kit.install.ex)
defp generate_timestamp do
{{y, m, d}, {hh, mm, ss}} = :calendar.universal_time()
"#{y}#{pad(m)}#{pad(d)}#{pad(hh)}#{pad(mm)}#{pad(ss)}"
end
defp pad(i) when i < 10, do: <<?0, ?0 + i>>
defp pad(i), do: to_string(i)
# Pad version number for consistent naming
defp pad_version(version) when version < 10, do: "0#{version}"
defp pad_version(version), do: to_string(version)
# Suggest layout integration if parent app has layouts but PhoenixKit not configured
defp suggest_layout_integration_if_needed do
case check_layout_integration_status() do
:already_configured ->
# Layout integration already configured, do nothing
:ok
{:suggest_integration, layouts_module} ->
Mix.shell().info("""
🎨 Layout Integration Available:
Your app has layouts (#{inspect(layouts_module)}), but PhoenixKit
is using its default layouts. To match your app's design:
Add to config/config.exs:
config :phoenix_kit,
layout: {#{inspect(layouts_module)}, :app},
root_layout: {#{inspect(layouts_module)}, :root}, # Optional
page_title_prefix: "Auth" # Optional
This will make PhoenixKit use your app's design.
""")
:no_layouts_found ->
# No parent layouts detected, keep using PhoenixKit defaults
:ok
end
end
# Check if layout integration should be suggested
defp check_layout_integration_status do
# Check if PhoenixKit layout is already configured
current_layout_config = Application.get_env(:phoenix_kit, :layout)
case current_layout_config do
{_module, _template} ->
# Layout integration already configured
:already_configured
_ ->
# No layout configured, check if parent app has layouts
case detect_parent_app_layouts() do
nil -> :no_layouts_found
layouts_module -> {:suggest_integration, layouts_module}
end
end
end
# Detect parent app layouts following Phoenix conventions
defp detect_parent_app_layouts do
case Mix.Project.get() do
nil -> nil
project -> detect_layouts_for_project(project)
end
end
# Detect layouts for a given project
defp detect_layouts_for_project(project) do
app_name = project.project()[:app]
if app_name && app_name != :phoenix_kit do
try_layout_module_patterns(app_name)
else
nil
end
end
# Try different layout module patterns for the app
defp try_layout_module_patterns(app_name) do
# Try common Phoenix layout module patterns
app_web_module = Module.concat([Macro.camelize(to_string(app_name)) <> "Web"])
layouts_module = Module.concat([app_web_module, "Layouts"])
if Code.ensure_loaded?(layouts_module) do
layouts_module
else
try_alternative_layout_pattern(app_name)
end
end
# Try alternative layout pattern
defp try_alternative_layout_pattern(app_name) do
alt_layouts = Module.concat([Macro.camelize(to_string(app_name)), "Layouts"])
if Code.ensure_loaded?(alt_layouts) do
alt_layouts
else
nil
end
end
end