Clean install from docs The package `ash_phoenix` had no associated installer task

hi
first time Ash user here

using the docs and example I do an install and get at the end The package `ash_phoenix` had no associated installer task.

is that an error, a warning or just info?

  (base) niccolox@niccolox-devekko:~/Projects/devekko_ash$ mix igniter.new liveshop \
>   --install ash,ash_postgres,ash_phoenix \
>   --with phx.new \
>   --extend postgres \
>   --example
* creating liveshop/lib/liveshop/application.ex
* creating liveshop/lib/liveshop.ex
* creating liveshop/lib/liveshop_web/controllers/error_json.ex
* creating liveshop/lib/liveshop_web/endpoint.ex
* creating liveshop/lib/liveshop_web/router.ex
* creating liveshop/lib/liveshop_web/telemetry.ex
* creating liveshop/lib/liveshop_web.ex
* creating liveshop/mix.exs
* creating liveshop/README.md
* creating liveshop/.formatter.exs
* creating liveshop/.gitignore
* creating liveshop/test/support/conn_case.ex
* creating liveshop/test/test_helper.exs
* creating liveshop/test/liveshop_web/controllers/error_json_test.exs
* creating liveshop/lib/liveshop/repo.ex
* creating liveshop/priv/repo/migrations/.formatter.exs
* creating liveshop/priv/repo/seeds.exs
* creating liveshop/test/support/data_case.ex
* creating liveshop/lib/liveshop_web/controllers/error_html.ex
* creating liveshop/test/liveshop_web/controllers/error_html_test.exs
* creating liveshop/lib/liveshop_web/components/core_components.ex
* creating liveshop/lib/liveshop_web/controllers/page_controller.ex
* creating liveshop/lib/liveshop_web/controllers/page_html.ex
* creating liveshop/lib/liveshop_web/controllers/page_html/home.html.heex
* creating liveshop/test/liveshop_web/controllers/page_controller_test.exs
* creating liveshop/lib/liveshop_web/components/layouts/root.html.heex
* creating liveshop/lib/liveshop_web/components/layouts/app.html.heex
* creating liveshop/lib/liveshop_web/components/layouts.ex
* creating liveshop/priv/static/images/logo.svg
* creating liveshop/lib/liveshop/mailer.ex
* creating liveshop/lib/liveshop_web/gettext.ex
* creating liveshop/priv/gettext/en/LC_MESSAGES/errors.po
* creating liveshop/priv/gettext/errors.pot
* creating liveshop/priv/static/robots.txt
* creating liveshop/priv/static/favicon.ico
* creating liveshop/assets/js/app.js
* creating liveshop/assets/vendor/topbar.js
* creating liveshop/assets/css/app.css
* creating liveshop/assets/tailwind.config.js

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix assets.setup
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd liveshop

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

==> phoenix_live_view
Compiling 39 files (.ex)
Generated phoenix_live_view app
===> Analyzing applications...
===> Compiling telemetry
===> Analyzing applications...
===> Compiling telemetry_poller
==> liveshop

Update: mix.exs

     ...|
34 34   |  defp deps do
35 35   |    [
   36 + |      {:ash_phoenix, "~> 2.0"},
   37 + |      {:ash_postgres, "~> 2.0"},
   38 + |      {:ash, "~> 3.0"},
36 39   |      {:igniter, "~> 0.4"},
37 40   |      {:phoenix, "~> 1.7.15"},
     ...|


These dependencies should be installed before continuing. Modify mix.exs and install? [y/n] y
running mix deps.get
==> stream_data
Compiling 3 files (.ex)
Generated stream_data app
==> libgraph
Compiling 15 files (.ex)
Generated libgraph app
===> Analyzing applications...
===> Compiling telemetry
===> Analyzing applications...
===> Compiling telemetry_poller
==> splode
Compiling 5 files (.ex)
Generated splode app
==> ets
Compiling 7 files (.ex)
Generated ets app
==> iterex
Compiling 48 files (.ex)
Generated iterex app
==> inflex
Compiling 6 files (.ex)
Generated inflex app
==> spark
Compiling 36 files (.ex)
Generated spark app
==> reactor
Compiling 86 files (.ex)
Generated reactor app
==> owl
Compiling 19 files (.ex)
Generated owl app
==> ash
Compiling 501 files (.ex)
Compiling lib/ash/reactor/reactor.ex (it's taking more than 10s)
Generated ash app
==> ash_sql
Compiling 13 files (.ex)
Generated ash_sql app
==> ash_postgres
Compiling 54 files (.ex)
Generated ash_postgres app
==> phoenix_live_view
Compiling 39 files (.ex)
Generated phoenix_live_view app
==> ash_phoenix
Compiling 24 files (.ex)
Generated ash_phoenix app
==> liveshop
Compiling 15 files (.ex)
warning: defining a Gettext backend by calling

    use Gettext, otp_app: ...

is deprecated. To define a backend, call:

    use Gettext.Backend, otp_app: :my_app

Then, instead of importing your backend, call this in your module:

    use Gettext, backend: MyApp.Gettext

  lib/liveshop_web/gettext.ex:23: LiveshopWeb.Gettext (module)

Generated liveshop app
Generating Liveshop.Repo

What is the minimum PostgreSQL version you will be using?

AshPostgres uses this information when generating queries and migrations,
to choose the best available features for your version of PostgreSQL.


Please enter the version in the format major.minor.patch (e.g. 13.4.0)

Default: 16.0.0

❯ 17.2.0

The following installers were found and executed: `ash`, `ash_postgres`:

Update: .formatter.exs

1 1   |[
2   - |  import_deps: [:ecto, :ecto_sql, :phoenix],
  2 + |  import_deps: [:ash_postgres, :ash, :ecto, :ecto_sql, :phoenix],
3 3   |  subdirectories: ["priv/*/migrations"],
4   - |  plugins: [Phoenix.LiveView.HTMLFormatter],
  4 + |  plugins: [Spark.Formatter, Phoenix.LiveView.HTMLFormatter],
5 5   |  inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
6 6   |]
   ...|


Update: /home/niccolox/Projects/devekko_ash/liveshop/lib/liveshop_web/components/core_components.ex

       ...|
 80  80   |              </div>
 81  81   |              <div id={"#{@id}-content"}>
 82     - |                <%= render_slot(@inner_block) %>
     82 + |                {render_slot(@inner_block)}
 83  83   |              </div>
 84  84   |            </.focus_wrap>
       ...|
125 125   |        <.icon :if={@kind == :info} name="hero-information-circle-mini" class="h-4 w-4" />
126 126   |        <.icon :if={@kind == :error} name="hero-exclamation-circle-mini" class="h-4 w-4" />
127     - |        <%= @title %>
    127 + |        {@title}
128 128   |      </p>
129     - |      <p class="mt-2 text-sm leading-5"><%= msg %></p>
    129 + |      <p class="mt-2 text-sm leading-5">{msg}</p>
130 130   |      <button type="button" class="group absolute top-1 right-1 p-2" aria-label={gettext("close")}>
131 131   |        <.icon name="hero-x-mark-solid" class="h-5 w-5 opacity-40 group-hover:opacity-70" />
       ...|
158 158   |        hidden
159 159   |      >
160     - |        <%= gettext("Attempting to reconnect") %>
    160 + |        {gettext("Attempting to reconnect")}
161 161   |        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
162 162   |      </.flash>
       ...|
170 170   |        hidden
171 171   |      >
172     - |        <%= gettext("Hang in there while we get back on track") %>
    172 + |        {gettext("Hang in there while we get back on track")}
173 173   |        <.icon name="hero-arrow-path" class="ml-1 h-3 w-3 animate-spin" />
174 174   |      </.flash>
       ...|
204 204   |    <.form :let={f} for={@for} as={@as} {@rest}>
205 205   |      <div class="mt-10 space-y-8 bg-white">
206     - |        <%= render_slot(@inner_block, f) %>
    206 + |        {render_slot(@inner_block, f)}
207 207   |        <div :for={action <- @actions} class="mt-2 flex items-center justify-between gap-6">
208     - |          <%= render_slot(action, f) %>
    208 + |          {render_slot(action, f)}
209 209   |        </div>
210 210   |      </div>
       ...|
238 238   |      {@rest}
239 239   |    >
240     - |      <%= render_slot(@inner_block) %>
    240 + |      {render_slot(@inner_block)}
241 241   |    </button>
242 242   |    """
       ...|
322 322   |          {@rest}
323 323   |        />
324     - |        <%= @label %>
    324 + |        {@label}
325 325   |      </label>
326     - |      <.error :for={msg <- @errors}><%= msg %></.error>
    326 + |      <.error :for={msg <- @errors}>{msg}</.error>
327 327   |    </div>
328 328   |    """
       ...|
332 332   |    ~H"""
333 333   |    <div>
334     - |      <.label for={@id}><%= @label %></.label>
    334 + |      <.label for={@id}>{@label}</.label>
335 335   |      <select
336 336   |        id={@id}
       ...|
340 340   |        {@rest}
341 341   |      >
342     - |        <option :if={@prompt} value=""><%= @prompt %></option>
343     - |        <%= Phoenix.HTML.Form.options_for_select(@options, @value) %>
    342 + |        <option :if={@prompt} value="">{@prompt}</option>
    343 + |        {Phoenix.HTML.Form.options_for_select(@options, @value)}
344 344   |      </select>
345     - |      <.error :for={msg <- @errors}><%= msg %></.error>
    345 + |      <.error :for={msg <- @errors}>{msg}</.error>
346 346   |    </div>
347 347   |    """
       ...|
351 351   |    ~H"""
352 352   |    <div>
353     - |      <.label for={@id}><%= @label %></.label>
    353 + |      <.label for={@id}>{@label}</.label>
354 354   |      <textarea
355 355   |        id={@id}
       ...|
362 362   |        {@rest}
363 363   |      ><%= Phoenix.HTML.Form.normalize_value("textarea", @value) %></textarea>
364     - |      <.error :for={msg <- @errors}><%= msg %></.error>
    364 + |      <.error :for={msg <- @errors}>{msg}</.error>
365 365   |    </div>
366 366   |    """
       ...|
371 371   |    ~H"""
372 372   |    <div>
373     - |      <.label for={@id}><%= @label %></.label>
    373 + |      <.label for={@id}>{@label}</.label>
374 374   |      <input
375 375   |        type={@type}
       ...|
384 384   |        {@rest}
385 385   |      />
386     - |      <.error :for={msg <- @errors}><%= msg %></.error>
    386 + |      <.error :for={msg <- @errors}>{msg}</.error>
387 387   |    </div>
388 388   |    """
       ...|
398 398   |    ~H"""
399 399   |    <label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
400     - |      <%= render_slot(@inner_block) %>
    400 + |      {render_slot(@inner_block)}
401 401   |    </label>
402 402   |    """
       ...|
412 412   |    <p class="mt-3 flex gap-3 text-sm leading-6 text-rose-600">
413 413   |      <.icon name="hero-exclamation-circle-mini" class="mt-0.5 h-5 w-5 flex-none" />
414     - |      <%= render_slot(@inner_block) %>
    414 + |      {render_slot(@inner_block)}
415 415   |    </p>
416 416   |    """
       ...|
431 431   |      <div>
432 432   |        <h1 class="text-lg font-semibold leading-8 text-zinc-800">
433     - |          <%= render_slot(@inner_block) %>
    433 + |          {render_slot(@inner_block)}
434 434   |        </h1>
435 435   |        <p :if={@subtitle != []} class="mt-2 text-sm leading-6 text-zinc-600">
436     - |          <%= render_slot(@subtitle) %>
    436 + |          {render_slot(@subtitle)}
437 437   |        </p>
438 438   |      </div>
439     - |      <div class="flex-none"><%= render_slot(@actions) %></div>
    439 + |      <div class="flex-none">{render_slot(@actions)}</div>
440 440   |    </header>
441 441   |    """
       ...|
478 478   |        <thead class="text-sm text-left leading-6 text-zinc-500">
479 479   |          <tr>
480     - |            <th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal"><%= col[:label] %></th>
    480 + |            <th :for={col <- @col} class="p-0 pb-4 pr-6 font-normal">{col[:label]}</th>
481 481   |            <th :if={@action != []} class="relative p-0 pb-4">
482     - |              <span class="sr-only"><%= gettext("Actions") %></span>
    482 + |              <span class="sr-only">{gettext("Actions")}</span>
483 483   |            </th>
484 484   |          </tr>
       ...|
498 498   |                <span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
499 499   |                <span class={["relative", i == 0 && "font-semibold text-zinc-900"]}>
500     - |                  <%= render_slot(col, @row_item.(row)) %>
    500 + |                  {render_slot(col, @row_item.(row))}
501 501   |                </span>
502 502   |              </div>
       ...|
509 509   |                  class="relative ml-4 font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
510 510   |                >
511     - |                  <%= render_slot(action, @row_item.(row)) %>
    511 + |                  {render_slot(action, @row_item.(row))}
512 512   |                </span>
513 513   |              </div>
       ...|
539 539   |      <dl class="-my-4 divide-y divide-zinc-100">
540 540   |        <div :for={item <- @item} class="flex gap-4 py-4 text-sm leading-6 sm:gap-8">
541     - |          <dt class="w-1/4 flex-none text-zinc-500"><%= item.title %></dt>
542     - |          <dd class="text-zinc-700"><%= render_slot(item) %></dd>
    541 + |          <dt class="w-1/4 flex-none text-zinc-500">{item.title}</dt>
    542 + |          <dd class="text-zinc-700">{render_slot(item)}</dd>
543 543   |        </div>
544 544   |      </dl>
       ...|
565 565   |      >
566 566   |        <.icon name="hero-arrow-left-solid" class="h-3 w-3" />
567     - |        <%= render_slot(@inner_block) %>
    567 + |        {render_slot(@inner_block)}
568 568   |      </.link>
569 569   |    </div>
       ...|


Update: config/config.exs

     ...|
 8  8   |import Config
 9  9   |
   10 + |config :ash,
   11 + |  include_embedded_source_by_default?: false,
   12 + |  default_page_type: :keyset,
   13 + |  policies: [no_filter_static_forbidden_reads?: false],
   14 + |  custom_types: [ticket_status: Liveshop.Support.Ticket.Types.Status]
   15 + |
   16 + |config :spark,
   17 + |  formatter: [
   18 + |    remove_parens?: true,
   19 + |    "Ash.Resource": [
   20 + |      section_order: [
   21 + |        :postgres,
   22 + |        :resource,
   23 + |        :code_interface,
   24 + |        :actions,
   25 + |        :policies,
   26 + |        :pub_sub,
   27 + |        :preparations,
   28 + |        :changes,
   29 + |        :validations,
   30 + |        :multitenancy,
   31 + |        :attributes,
   32 + |        :relationships,
   33 + |        :calculations,
   34 + |        :aggregates,
   35 + |        :identities
   36 + |      ]
   37 + |    ],
   38 + |    "Ash.Domain": [section_order: [:resources, :policies, :authorization, :domain, :execution]]
   39 + |  ]
   40 + |
10 41   |config :liveshop,
11 42   |  ecto_repos: [Liveshop.Repo],
12    - |  generators: [timestamp_type: :utc_datetime]
   43 + |  generators: [timestamp_type: :utc_datetime],
   44 + |  ash_domains: [Liveshop.Support]
13 45   |
14 46   |# Configures the endpoint
     ...|


Update: config/test.exs

 1  1   |import Config
    2 + |config :ash, disable_async?: true
 2  3   |
 3  4   |# Configure your database
     ...|


Update: lib/liveshop/repo.ex

 1  1   |defmodule Liveshop.Repo do
 2    - |  use Ecto.Repo,
 3    - |    otp_app: :liveshop,
 4    - |    adapter: Ecto.Adapters.Postgres
    2 + |  use AshPostgres.Repo,
    3 + |    otp_app: :liveshop
    4 + |
    5 + |  def installed_extensions do
    6 + |    # Add extensions here, and the migration generator will install them.
    7 + |    ["ash-functions"]
    8 + |  end
    9 + |
   10 + |  # Don't open unnecessary transactions
   11 + |  # will default to `false` in 4.0
   12 + |  def prefer_transaction? do
   13 + |    false
   14 + |  end
   15 + |
   16 + |  def min_pg_version do
   17 + |    %Version{major: 17, minor: 2, patch: 0}
   18 + |  end
 5 19   |end
 6 20   |


Create: lib/liveshop/support.ex

1 |defmodule Liveshop.Support do
2 |  use Ash.Domain
3 |
4 |  resources do
5 |    resource Liveshop.Support.Ticket
6 |    resource Liveshop.Support.Representative
7 |  end
8 |end
9 |


Create: lib/liveshop/support/representative.ex

1  |defmodule Liveshop.Support.Representative do
2  |  use Ash.Resource,
3  |    otp_app: :liveshop,
4  |    domain: Liveshop.Support,
5  |    data_layer: AshPostgres.DataLayer
6  |
7  |  postgres do
8  |    table "representatives"
9  |    repo Liveshop.Repo
10 |  end
11 |
12 |  actions do
13 |    defaults [:read, create: [:name]]
14 |  end
15 |
16 |  attributes do
17 |    uuid_primary_key :id
18 |
19 |    attribute :name, :string do
20 |      allow_nil? false
21 |      public? true
22 |    end
23 |  end
24 |
25 |  relationships do
26 |    has_many :tickets, Liveshop.Support.Ticket do
27 |      public? true
28 |    end
29 |  end
30 |end
31 |


Create: lib/liveshop/support/ticket.ex

1  |defmodule Liveshop.Support.Ticket do
2  |  use Ash.Resource,
3  |    otp_app: :liveshop,
4  |    domain: Liveshop.Support,
5  |    data_layer: AshPostgres.DataLayer
6  |
7  |  postgres do
8  |    table "tickets"
9  |    repo Liveshop.Repo
10 |  end
11 |
12 |  actions do
13 |    defaults [:read]
14 |
15 |    create :open do
16 |      accept [:subject]
17 |    end
18 |
19 |    update :close do
20 |      accept []
21 |
22 |      validate attribute_does_not_equal(:status, :closed) do
23 |        message "Ticket is already closed"
24 |      end
25 |
26 |      change set_attribute(:status, :closed)
27 |    end
28 |
29 |    update :assign do
30 |      accept [:representative_id]
31 |    end
32 |  end
33 |
34 |  attributes do
35 |    uuid_primary_key :id
36 |
37 |    attribute :subject, :string do
38 |      allow_nil? false
39 |      public? true
40 |    end
41 |
42 |    attribute :status, :ticket_status do
43 |      default :open
44 |      allow_nil? false
45 |    end
46 |  end
47 |
48 |  relationships do
49 |    belongs_to :representative, Liveshop.Support.Representative do
50 |      public? true
51 |    end
52 |  end
53 |end
54 |


Create: lib/liveshop/support/ticket/types/status.ex

1 |defmodule Liveshop.Support.Ticket.Types.Status do
2 |  use Ash.Type.Enum, values: [:open, :closed]
3 |end
4 |


Update: mix.exs

     ...|
76 76   |  defp aliases do
77 77   |    [
78    - |      setup: ["deps.get", "ecto.setup", "assets.setup", "assets.build"],
   78 + |      setup: ["deps.get", "ash.setup", "assets.setup", "assets.build", "run priv/repo/seeds.exs"],
79 79   |      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
80 80   |      "ecto.reset": ["ecto.drop", "ecto.setup"],
81    - |      test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"],
   81 + |      test: ["ash.setup --quiet", "test"],
82 82   |      "assets.setup": ["tailwind.install --if-missing", "esbuild.install --if-missing"],
83 83   |      "assets.build": ["tailwind liveshop", "esbuild liveshop"],
     ...|



These tasks will be run after the above changes:

* ash.codegen initialize

Proceed with changes? [y/n] y
Resolving Hex dependencies...
Resolution completed in 0.415s
Unchanged:
  ash 3.4.43
  ash_phoenix 2.1.8
  ash_postgres 2.4.14
  ash_sql 0.2.39
  bandit 1.6.0
  castore 1.0.10
  db_connection 2.7.0
  decimal 2.2.0
  dns_cluster 0.1.3
  ecto 3.12.5
  ecto_sql 3.12.1
  esbuild 0.8.2
  ets 0.9.0
  expo 1.1.0
  file_system 1.0.1
  finch 0.19.0
  floki 0.36.3
  gettext 0.26.2
  glob_ex 0.1.11
  hpax 1.0.0
  igniter 0.4.8
  inflex 2.1.0
  iterex 0.1.2
  jason 1.4.4
  libgraph 0.16.0
  mime 2.0.6
  mint 1.6.2
  nimble_options 1.1.1
  nimble_pool 1.1.0
  owl 0.12.0
  phoenix 1.7.15
  phoenix_ecto 4.6.3
  phoenix_html 4.1.1
  phoenix_live_dashboard 0.8.5
  phoenix_live_reload 1.5.3
  phoenix_live_view 1.0.0-rc.8
  phoenix_pubsub 2.1.3
  phoenix_template 1.0.4
  plug 1.16.1
  plug_crypto 2.1.0
  postgrex 0.19.3
  reactor 0.10.2
  rewrite 1.1.1
  sourceror 1.7.1
  spark 2.2.35
  spitfire 0.1.3
  splode 0.2.7
  stream_data 1.1.2
  swoosh 1.17.3
  tailwind 0.2.4
  telemetry 1.3.0
  telemetry_metrics 1.0.0
  telemetry_poller 1.1.0
  text_diff 0.1.0
  thousand_island 1.3.7
  websock 0.5.3
  websock_adapter 0.5.8
All dependencies are up to date
==> ash
Compiling 501 files (.ex)
Compiling lib/ash/reactor/reactor.ex (it's taking more than 10s)
Generated ash app
==> ash_phoenix
Compiling 24 files (.ex)
Generated ash_phoenix app
==> liveshop
Compiling 19 files (.ex)
warning: defining a Gettext backend by calling

    use Gettext, otp_app: ...

is deprecated. To define a backend, call:

    use Gettext.Backend, otp_app: :my_app

Then, instead of importing your backend, call this in your module:

    use Gettext, backend: MyApp.Gettext

  lib/liveshop_web/gettext.ex:23: LiveshopWeb.Gettext (module)

Generated liveshop app
Getting extensions in current project...
Running codegen for AshPostgres.DataLayer...

Extension Migrations: 
* creating _build/dev/lib/liveshop/priv/resource_snapshots/repo/extensions.json
* creating _build/dev/lib/liveshop/priv/repo/migrations/20241203052608_initialize_extensions_1.exs

Generating Tenant Migrations: 

Generating Migrations:
* creating _build/dev/lib/liveshop/priv/repo/migrations/20241203052609_initialize.exs
* creating _build/dev/lib/liveshop/priv/resource_snapshots/repo/representatives/20241203052609.json
* creating _build/dev/lib/liveshop/priv/resource_snapshots/repo/tickets/20241203052609.json
The package `ash_phoenix` had no associated installer task.

1 Like

It’s just a notice, and can be safely ignored.

No AshPhoenix-specific code needs to be generated into your app on installation, and so there is no installer task.

1 Like