Packages

mix composer.new: create an Elixir app and apply a composer recipe

Current section

Files

Jump to
composer_new README.md
Raw

README.md

# composer_new

`mix composer.new` creates an Elixir app and applies a
[composer](https://github.com/elixir-composer/composer) recipe to it, in
one command:

```bash
mix composer.new my_app cb_lab:base/completions --with phx.new --with-args=--no-ecto
```

It drives `igniter.new` and `igniter.install`; it doesn't replace them.

## Install

```bash
mix archive.install hex igniter_new     # 0.5.32 or later
mix archive.install hex composer_new
mix archive.install hex phx_new         # only for --with phx.new
```

`composer_new` and `composer` have the same version, and an app created
by `composer_new` X.Y.Z gets `composer ~> X.Y.Z` unless `--composer` says
otherwise.

`igniter_new` must be 0.5.32 or later: earlier releases either ignore
`--no-git` or pass it on to the installers of `--install` packages. Upgrade
with `mix archive.install hex igniter_new --force`.

## Usage

```
mix composer.new APP [REF] [RECIPE-FLAGS] [OPTIONS]
```

APP comes first, as with `igniter.new` and `phx.new`. It may have a
directory part (`/tmp/my_app`); its last segment must be a valid app name.

### REF

```
REF    = COOKBOOK:GROUP/RECIPE[@SOURCE]
SOURCE = <version>                     hex, e.g. @0.3 or @~> 0.3.1 (no @: the latest)
       | github:OWNER/REPO[@GITREF]
       | git:URL
       | path:PATH
```

REF is split on its first `@`: `COOKBOOK:GROUP/RECIPE` goes to
`mix composer.recipe.run`, and `COOKBOOK@SOURCE` to `mix igniter.install`.

```bash
# hex
mix composer.new my_app cb_lab:base/completions
# github, default branch or pinned
mix composer.new my_app cb_lab:base/completions@github:elixir-composer/cb_lab
mix composer.new my_app cb_lab:base/completions@github:elixir-composer/cb_lab@v0.3.0
# a local checkout
mix composer.new my_app cb_home:lv/base@path:~/src/Gen/cb_home --with phx.new
# a base that is a package, not a task
mix composer.new my_blog cb_home:astral/min@path:~/src/Gen/cb_home \
  --install astral@path:~/src/Fork/astral
# recipe flags pass through to composer.recipe.run
mix composer.new my_app cb_mine:phx/saas --oban-pro --gigalixir
```

Rules:

- REF must name its cookbook: `lv/base` alone is an error (there are no
  shortnames).
- `core:GROUP/RECIPE` installs no cookbook (`core` ships with composer), so
  it takes no `@SOURCE`.
- `hex:` refs name packages, not cookbooks, and are rejected.
- Without REF, the app gets composer and nothing else. Run
  `mix composer.init` in it to start the app's own cookbook.

### Options

| Option                 | Meaning                                                                       |
|------------------------|-------------------------------------------------------------------------------|
| `--with TASK`          | the base generator, as in `igniter.new` (`phx.new`, ...); default `new`       |
| `--with-args ARGS`     | arguments for `--with`; prefer `--with-args="--no-ecto --binary-id"`          |
| `--install PKG[,PKG]`  | base packages installed with the app as prod deps (repeatable)                |
| `--composer SOURCE`    | composer's source: `path:...`, `github:...` or a version; default `~> <archive version>` |
| `--extra-dep PKG`      | another dev/test dep, such as a second cookbook (repeatable)                  |
| `--igniter-local PATH` | a local igniter checkout, passed as `igniter.new --local` (made relative to APP's parent) |
| `--reset`              | delete APP first                                                              |
| `--dry-run`            | print the plan and run nothing                                                |
| `--no-recipe`          | create, install and commit, but don't run the recipe                          |

Every other switch is a recipe flag and goes to `mix composer.recipe.run`,
which rejects any flag the recipe doesn't declare before it changes
anything. Everything after `--` is a recipe flag too. There is no `--yes`:
the command runs unattended.

A `path:` source, relative or starting with `~`, is made absolute, since
the install runs inside the new app.

Composer reserves these option names: no recipe may declare a flag called
`with`, `install`, `reset`, `dry_run`, and so on, and
`mix composer.cookbook.lint` says so.

## What it runs

The plan (app, cookbook, source, base generator and every command) is
printed first, every time. `--dry-run` stops there.

0. **Preflight**: the ref and options are valid; APP doesn't exist (unless
   `--reset`); `igniter_new` >= 0.5.32 is installed; the `--with` task's
   archive is installed; `git` is on the PATH.
1. **Create**: `mix igniter.new APP --no-git --yes`, with `--with`,
   `--with-args`, `--install` and `--local`.
2. **Install**: in APP,
   `mix igniter.install composer@SRC,COOKBOOK@SRC,... --only dev,test --yes`.
   The cookbook's own deps come with it.
3. **Record**: in APP, `git init`, `git add -A`, and a commit whose message
   is the `mix composer.new` command line. `git log --reverse` shows how to
   rebuild the app, and `recipe.run` gets the clean tree it needs.
4. **Run**: in APP, `mix composer.recipe.run COOKBOOK:GROUP/RECIPE
   [RECIPE-FLAGS]`, which commits the recipe's changes. Skipped without a
   REF, or with `--no-recipe`.

Each command is its own `mix` (or `git`) process, with its output streamed
to the terminal.

## When something fails

The run stops at the first command that fails, and exits non-zero.

- **Create or install failed**: APP is partial. Fix the cause and rerun the
  same command with `--reset`.
- **The recipe failed** (a mistyped flag, an unmet `requires`, a step
  error): APP is left at its generation commit, and the exact command to
  retry is printed:

  ```bash
  cd my_app && mix composer.recipe.run cb_lab:base/completions --gigalixr
  ```

- **The install failed with the default `--composer`**: that composer
  version may not be on Hex yet; point at a checkout with
  `--composer path:/path/to/composer`.

A cookbook installed from `@github:` or `@git:` must declare hex or git
deps, not path deps: a path is resolved next to the checkout in `deps/`,
where it doesn't exist. `mix composer.cookbook.lint` warns about path deps
in a project with a `package:` section.

## A recipe is code

A cookbook from `@github:` or `@git:` runs someone else's code on your
machine, just as `igniter.new --install` does. To see what a recipe would do
first:

```bash
mix composer.new my_app cb_lab:base/completions@github:elixir-composer/cb_lab --no-recipe
cd my_app
mix composer.recipe.show cb_lab:base/completions
mix composer.recipe.run cb_lab:base/completions --dry-run
```

## License

MIT, see [LICENSE.md](LICENSE.md).