Skip to content

Collapse :add and :install into one verb (0.2.0) - #1

Merged
jamilabreu merged 1 commit into
mainfrom
collapse-add-and-install
Aug 6, 2026
Merged

jamilabreu merged 1 commit into
mainfrom
collapse-add-and-install

Conversation

@jamilabreu

@jamilabreu jamilabreu commented Aug 6, 2026 •

Copy link
Copy Markdown
Owner

Whether a package ships an Igniter installer is upstream's business and
changes over time, but the two-verb API made every workflow file encode
it: a package gaining an installer meant hand-editing {:add, :x} to
{:install, :x} in every workflow naming it.

That distinction only existed because installs had to be queued into a
subprocess, which in turn rested on the belief that installers cannot be
composed mid-run. They can — Igniter's own run_installers/5 composes them
into the same igniter; igniter.install just ends with do_or_dry_run
because it is a top-level task.

So installs now happen in-run, using public Igniter API: collect the
install packages up front, add and fetch them once via
apply_and_fetch_dependencies/2 (which writes only the deps change,
leaving the rest of the diff pending), then compose each .install at
its position in the step list.

With installs composable, the verbs collapse: {:add, name} runs Starter's
step when it has one and otherwise installs the package and runs its own
installer. {:install, name} survives as the explicit override.

Consequences:

  • One diff, one confirmation. No queued subprocess for installs, so the
    --yes-into-subprocess workaround (0.1.1) and the argv-leak bug (0.1.2)
    are structurally impossible for installs.
  • Ordering is list position. A step after one that installs a package
    sees its effects, so sort_deps is an in-run step again and oban_pro
    just follows oban. This supersedes 0.1.2's queued sort_deps.
  • Runs print a plan naming what each step resolved to, which is where
    the built-in-vs-installer distinction lives now that the step list no
    longer draws it.

The fall-through means a typo'd step name would become a doomed Hex
lookup, so names closely resembling a built-in step raise a suggestion
instead.

Dependencies remain the one thing that reaches disk before the main
diff — an installer has to be on disk to compose at all. That change is
shown and confirmed on its own.

Verified against a real project: oban.install runs in-run, and sort_deps
placed last sorts the deps the installers added.


Note

High Risk
This rewrites core workflow execution (dep prefetch timing, single diff vs subprocess installs) and changes the public step semantics users encode in workflow files, though {:install} is preserved.

Overview
0.2.0 collapses package setup into {:add, name}: built-in Starter steps when they exist, otherwise add the dep and compose the package’s <name>.install inside the same Igniter run. {:install, name} remains as an explicit “always use upstream’s installer” override.

Starter.Runner no longer queues mix igniter.install after apply. Install packages are collected up front, written to mix.exs, and fetched via Igniter.apply_and_fetch_dependencies/2 (separate confirm), then each installer runs at its list position in one combined diff. Runs print a Starter plan of how each step resolved, and Starter.Runner.installs/2 exposes the prefetch package set.

mix starter.new’s catalog now uses {:add, :oban} (etc.) with oban_pro after oban, and sort_deps as an in-run {:gen} step instead of a post-install queue. {:add} typos near built-in names raise “Did you mean?” instead of Hex lookups.

Reviewed by Cursor Bugbot for commit e210091. Bugbot is set up for automated code reviews on this repo. Configure here.

Whether a package ships an Igniter installer is upstream's business and
changes over time, but the two-verb API made every workflow file encode
it: a package gaining an installer meant hand-editing {:add, :x} to
{:install, :x} in every workflow naming it.

That distinction only existed because installs had to be queued into a
subprocess, which in turn rested on the belief that installers cannot be
composed mid-run. They can — Igniter's own run_installers/5 composes them
into the same igniter; igniter.install just ends with do_or_dry_run
because it is a top-level task.

So installs now happen in-run, using public Igniter API: collect the
install packages up front, add and fetch them once via
apply_and_fetch_dependencies/2 (which writes only the deps change,
leaving the rest of the diff pending), then compose each <pkg>.install at
its position in the step list.

With installs composable, the verbs collapse: {:add, name} runs Starter's
step when it has one and otherwise installs the package and runs its own
installer. {:install, name} survives as the explicit override.

Consequences:

  - One diff, one confirmation. No queued subprocess for installs, so the
    --yes-into-subprocess workaround (0.1.1) and the argv-leak bug (0.1.2)
    are structurally impossible for installs.
  - Ordering is list position. A step after one that installs a package
    sees its effects, so sort_deps is an in-run step again and oban_pro
    just follows oban. This supersedes 0.1.2's queued sort_deps.
  - Runs print a plan naming what each step resolved to, which is where
    the built-in-vs-installer distinction lives now that the step list no
    longer draws it.

The fall-through means a typo'd step name would become a doomed Hex
lookup, so names closely resembling a built-in step raise a suggestion
instead.

Dependencies remain the one thing that reaches disk before the main
diff — an installer has to be on disk to compose at all. That change is
shown and confirmed on its own.

Verified against a real project: oban.install runs in-run, and sort_deps
placed last sorts the deps the installers added.
@jamilabreu
jamilabreu merged commit e210091 into main Aug 6, 2026
3 checks passed

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e210091. Configure here.

Comment thread lib/starter/runner.ex
Comment thread lib/starter/runner.ex
jamilabreu added a commit that referenced this pull request Aug 6, 2026
0.2.0 added every package a workflow installs to mix.exs before any step
runs, which quietly broke the oban_pro step's guard. has_dep?(:oban) used
to mean "Oban is already set up" — reliable when installs were queued to
run after the workflow — but now answers "will oban be a dep?", which is
true from the start whenever oban appears anywhere in the list.

So {:add, :oban_pro} placed before {:add, :oban} passed the guard and
wrote Oban config that oban.install then clobbers, where the old queued
behavior warned and skipped.

Guard on Oban's config instead. Since oban.install now composes into the
same run, a later step can see what it wrote, so this is both correct and
order-sensitive in the way the dep check no longer was.

The warning also still pointed at {:install, :oban} and
{:queue, "starter.add", ["oban_pro"]} — the API 0.2.0 removed.

CI missed this because the golden test runs `mix starter.run --yes` with
no flags, so oban_pro (behind --oban-pro) never executed. It now passes
the flags CI can run. --oban-pro is not among them: oban_pro is licensed
and lives in a private Hex repo, so it stays unit-tested only.

Reported by Cursor Bugbot on #1. Its second finding — that private-repo
deps break — is not a regression: igniter.install resolves string deps
through the same determine_dep_type_and_version!/2, so bare names have
always resolved against public Hex. Documented the qualified form
instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant