Composer.Recipe (composer v0.2.0)

Copy Markdown View Source

A recipe: a named, self-describing list of steps. Recipes live in group files (see Composer.Recipe.Group), recipes/<group>.exs, and are addressed as <cookbook>:<group>/<name>.

# recipes/ash.exs
[
  base: [
    shortdoc: "Ash core: sourceror, ash and ash_phoenix",
    steps: ["composer_lab:sourceror", "composer_lab:ash", "composer_lab:ash/phoenix"]
  ],
  auth: [
    shortdoc: "Ash with authentication and accounts",
    doc: """
    Builds on ash/base. Pass --oban-pro to add Oban Pro.
    """,
    flags: [oban_pro: "Add Oban Pro"],
    steps: [
      {:use, "base"},
      "composer_lab:ash/authentication",
      {"core:oban_pro", if: :oban_pro},
      {:task, "ash.codegen", ["initial"]},
      {:queue, "ecto.setup", []}
    ]
  ]
]

Keys

  • shortdoc (required) - one line, shown by mix composer.recipe.list
  • doc - longer help, shown by mix composer.recipe.show
  • flags - [flag: "description"]. Every if: :flag in the recipe's own steps must be declared here, and mix composer.recipe.run rejects a --flag that no recipe it reaches declares.
  • aliases - [alias: "cookbook"], short names for cookbooks in this recipe's own references (see "Cookbook aliases" below)
  • steps (required) - the entries below

Entries

  • "ref" - a step: "cookbook:path", or an unqualified "path" (see Composer.Resolver for how it is found). An unqualified name that matches no step is a Hex package: "oban" is "hex:oban".

  • {"ref", if: :flag} - a step that runs only with --flag

  • {"ref", argv: ["--opt"]} - a step run with options, as after -- on mix composer.step.run; combines with if:

  • "hex:pkg" / {"hex:pkg", opts} - a package added to mix.exs, whose own installer runs ("hex:pkg@<spec>" is the @ shorthand, see Composer.Ref). Besides if:, opts may hold:

    • argv: ["--opt"] - arguments for the package's installer
    • only: :dev (or a list of atoms), runtime: false - dep options
    • at most one source: version: "~> 3.5", github: "owner/repo", git: url or path: "../my_lib"; with github: or git:, at most one of ref:, branch:, tag:, and sparse: (a string), submodules: (a boolean)

    An unqualified name accepts the same options, and is an error if it names a step rather than a package.

  • {:use, "ref"} / {:use, "ref", if: :flag} - another recipe's steps, in place. An unqualified name without a / ({:use, "base"}) is the recipe of that name in the same group.

  • {:task, "x.y", argv} - compose any Igniter-aware Mix task

  • {:queue, "x.y", argv} - run a Mix task after the changes apply

{:task, ...} and {:queue, ...} take an optional if: keyword as a fourth element.

Cookbook aliases

aliases: [lab: "composer_lab"] lets the recipe write "lab:auth/passkeys" (or {:use, "lab:auth/base"}) for "composer_lab:auth/passkeys". An alias is only a lookup shortcut:

  • it applies to the declaring recipe's own references, never inside a recipe it includes, and never on the command line
  • the target is a package cookbook's name (a string), never core, hex, local or another reserved name
  • the alias is a lowercase [a-z0-9_] name other than core, hex and local, and must not be the name of an installed cookbook
  • resolved entries, listings and --json show the real cookbook name

To share aliases across the recipes of a group, bind them before the final list (see Composer.Recipe.Group).

Warning

A group file is Elixir code, evaluated with Code.eval_file/1. Only use recipes you wrote or trust.

Summary

Functions

Returns the cookbook that name, the cookbook part of a reference, is an alias for in recipe, or nil.

Returns the cookbook parts of the qualified references in steps (step and {:use, ...} references), without duplicates.

Returns nil for a well-formed entry, or why it is not, for Composer.Error.message/1. ref is the recipe the entry is in.

The keys that refine a github: or git: source.

Returns every if: flag in steps, in order, without duplicates.

Loads recipe name from the group file at path, in cookbook.

Returns an entry's own if: flag, or nil.

The options of a {"hex:pkg", opts} entry that describe the package: dependency options and source keys, without if: and argv:.

The keys that name where a package comes from.

Returns the first line of a recipe's shortdoc.

True for a well-formed recipe entry.

Validates an evaluated recipe term: a list of valid entries.

Validates a recipe's aliases keyword list, returning every problem as an error reason for Composer.Error.message/1.

Types

entry()

@type entry() ::
  String.t()
  | {String.t(), keyword()}
  | {:use, String.t()}
  | {:use, String.t(), keyword()}
  | {:task | :queue, String.t()}
  | {:task | :queue, String.t(), [String.t()]}
  | {:task | :queue, String.t(), [String.t()], keyword()}

t()

@type t() :: %Composer.Recipe{
  aliases: keyword(String.t()),
  cookbook: String.t(),
  doc: String.t() | nil,
  flags: keyword(String.t()),
  group: String.t(),
  id: String.t(),
  name: String.t(),
  path: Path.t(),
  ref: String.t(),
  shortdoc: String.t(),
  steps: [entry()]
}

Functions

alias_target(recipe, name)

@spec alias_target(t(), String.t() | nil) :: String.t() | nil

Returns the cookbook that name, the cookbook part of a reference, is an alias for in recipe, or nil.

cookbooks_referenced(steps)

@spec cookbooks_referenced([entry()]) :: [String.t()]

Returns the cookbook parts of the qualified references in steps (step and {:use, ...} references), without duplicates.

entry_error(entry, ref)

@spec entry_error(term(), String.t() | nil) :: term() | nil

Returns nil for a well-formed entry, or why it is not, for Composer.Error.message/1. ref is the recipe the entry is in.

Old step forms are {:legacy_form, ref, entry, suggestion}, where suggestion is the new syntax when one can be derived. Options of a {"ref", opts} entry are checked one by one:

  • {:unknown_entry_option, ref, entry, key}
  • {:bad_entry_option, ref, entry, key, value} - a value of the wrong type
  • {:hex_only_option, ref, entry, key} - a package option on a qualified step reference
  • {:multiple_sources, ref, entry, keys}
  • {:git_option_without_git, ref, entry, key}
  • {:multiple_git_refs, ref, entry, keys} - more than one of ref, branch and tag
  • {:source_conflict, ref, entry} - an @ source and a keyword source
  • {:repo_source, ref, entry, key} - a non-version source for a private-repo package (hex:org.pkg)

An unqualified reference accepts the package options, since it may be a Hex package; Composer.Resolver rejects them if it names a step. An invalid reference string is reported as {:invalid_ref, ...}.

git_keys()

@spec git_keys() :: [atom()]

The keys that refine a github: or git: source.

if_flags(steps)

@spec if_flags([entry()]) :: [atom()]

Returns every if: flag in steps, in order, without duplicates.

load(path, cookbook, name)

@spec load(Path.t(), String.t(), String.t()) :: {:ok, t()} | {:error, term()}

Loads recipe name from the group file at path, in cookbook.

own_flag(arg1)

@spec own_flag(entry()) :: atom() | nil

Returns an entry's own if: flag, or nil.

package_options()

@spec package_options() :: [atom()]

The options of a {"hex:pkg", opts} entry that describe the package: dependency options and source keys, without if: and argv:.

source_keys()

@spec source_keys() :: [atom()]

The keys that name where a package comes from.

summary(text)

@spec summary(t() | String.t() | nil) :: String.t()

Returns the first line of a recipe's shortdoc.

valid?(entry)

@spec valid?(term()) :: boolean()

True for a well-formed recipe entry.

validate(steps, ref)

@spec validate(term(), String.t()) :: {:ok, [entry()]} | {:error, term()}

Validates an evaluated recipe term: a list of valid entries.

Returns the first entry's error, as entry_error/2 reports it.

validate_aliases(aliases, ref)

@spec validate_aliases(
  keyword(),
  String.t()
) :: [term()]

Validates a recipe's aliases keyword list, returning every problem as an error reason for Composer.Error.message/1.