Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion src/content/docs/guides/environment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,47 @@ commands:
cmd: ./deploy.sh staging
```

Missing files are silently skipped - no error.
A file like `.env.local` is often absent, for example when you start a project
from a checked-out repo with an `.ahoy.yml` file in it. From v3.0.1 Ahoy
reports this, rather than passing over it in silence, so please start marking such entries
as optional - see below.

## Files that may be absent <Badge text="v3.0.1+" variant="success" />

A missing env file never stops a command - it is skipped, the variables it
would have set stay empty, and the command runs. What changed in v3.0.1 is
that Ahoy now shows a warning:

```
[warn] environment file '.env.local' not found, continuing without it. Add 'optional: true' to that entry if it is meant to be absent.
```

Every entry is required by default, so a typo in a filename surfaces instead of
leaving you to work out why a variable is empty. For the files that are meant
to be missing - a personal `.env.local` that only some developers have - write
the entry as a mapping and mark it `optional: true`:

```yaml
env:
- .env.base # Required: warns when absent
- path: .env.local
optional: true # Silent when absent
```

This is the same `optional` you may already use on
[imports](/guides/importing/): simply meaning that the file is not expected to
always present.

The warning goes to stderr, so it never contaminates a command's stdout when
you pipe it. `ahoy config validate` distinguishes the two as well, listing a
missing required file with `❌` and a missing optional one with `➖ ... -
missing, marked optional`.

<Aside type="caution" title="Requires v3.0.1">
The mapping form is not understood by earlier versions - Ahoy v2.5.0 fails to
parse it with `cannot unmarshal !!seq into string`. Keep the plain string form
if your `.ahoy.yml` is shared with anyone still on v2 or v3.0.0.
</Aside>

## Precedence

Expand Down
57 changes: 55 additions & 2 deletions src/content/docs/reference/environment.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { Aside, Badge } from "@astrojs/starlight/components";

## Overview <Badge text="v2.3.0+" variant="tip" />

Ahoy can load environment variables from files before executing any command. Files are declared using the `env` field at the config level (global) or within individual commands (per-command). Both levels accept a single file path or an array of paths <Badge text="v2.5.0+" variant="note" size="small" />.
Ahoy can load environment variables from files before executing any command. Files are declared using the `env` field at the config level (global) or within individual commands (per-command). Both levels accept the same three forms:

- A single file path: `env: .env`
- An array of file paths <Badge text="v2.5.0+" variant="note" size="small" />
- An array mixing file paths with `{ path, optional }` mapping entries <Badge text="v3.0.1+" variant="success" size="small" />, which mark the files that are allowed to be absent - see [Optional env files](#optional-env-files-)

## File path resolution

Expand All @@ -24,7 +28,56 @@ Variables are applied in this order, from lowest to highest priority:
Later sources override earlier ones. A command-specific variable always wins over a global one with the same name.

<Aside type="note">
Missing env files are silently skipped - specifying a file that doesn't exist is not an error.
A missing env file is skipped and the command still runs - specifying a file that doesn't exist is
not an error. From <Badge text="v3.0.1+" variant="success" size="small" /> Ahoy reports the missing
file on stderr unless its entry is marked `optional: true`; see [Optional env
files](#optional-env-files-) below.
</Aside>

## Optional env files <Badge text="v3.0.1+" variant="success" />

Each entry under an `env` key is **required by default**. When a required file is absent, Ahoy
writes a warning to stderr and carries on:

```
[warn] environment file '.env.local' not found, continuing without it. Add 'optional: true' to that entry if it is meant to be absent.
```

The command still runs, and the exit code is unchanged; only the reporting is new. Because the
warning goes to stderr, a command's stdout stays clean for piping.

To declare a file that is expected to be missing, write the entry as a mapping with `path` and
`optional`:

```yaml
env:
- .env.base # Required
- path: .env.local
optional: true # Absent without comment
- path: .env.deploy
optional: false # Same as the plain string form
```

`optional` means the same thing here as it does for
[`imports`](/reference/yaml-schema/): the entry may legitimately not resolve to a file. It is set on
the env entry itself, though - the command-level `optional` flag covers imports only, and does not
silence a missing env file.

The two forms mix freely within one list, and both work at the global and per-command level. Every
pre-v3.0.1 form parses unchanged: the `env: .env` shorthand, plain lists, empty lists and nulls.

`ahoy config validate` reflects the distinction:

```
🌍 Environment Files:
✅ .env.base (global)
➖ .env.local (global) - missing, marked optional
❌ .env.missing (global) - missing
```

<Aside type="caution" title="Not backwards compatible">
Only Ahoy v3.0.1 and later can parse the mapping form. v2.5.0 fails with `cannot unmarshal !!seq
into string`, so keep to plain strings in a config shared with older installs.
</Aside>

## Runtime variables <Badge text="v3" variant="success" />
Expand Down
60 changes: 54 additions & 6 deletions src/content/docs/reference/yaml-schema.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: YAML Schema
description: Complete technical reference for the Ahoy YAML configuration schema
---

import { Badge } from "@astrojs/starlight/components";
import { Aside, Badge } from "@astrojs/starlight/components";

# YAML Schema Reference

Expand Down Expand Up @@ -78,7 +78,7 @@ usage: "Collection of commands for the project"
### `env` <Badge text="v2.3.0+" variant="tip" />

**Required**: No
**Type**: String or Array of Strings
**Type**: String, Array of Strings, or Array of Strings and Mappings
**Default**: None
**Implementation**: Processed in `getCommands()` function
**Description**: Path(s) to global environment file(s), relative to the YAML file location. Environment variables defined here are available to all commands in this configuration file. Supports both single file and multiple files syntax.
Expand All @@ -100,13 +100,28 @@ env:
- .env.override
```

**Optional Environment Files** <Badge text="v3.0.1+" variant="success" size="small" />:

An entry written as a mapping takes a `path` and an `optional` flag. Entries are required by
default; a required file that is absent is reported on stderr, while an optional one is passed over
without comment. Both forms may be mixed in the same list.

```yaml
env:
- .env.base # Required: warns when absent
- path: .env.local
optional: true # Silent when absent
```

**Technical Details**:

- Environment variables are loaded at runtime and passed to command processes
- When using multiple files, they are loaded in order with later files overriding earlier ones
- Blank lines and lines starting with '#' are ignored
- Variables from global files can be overridden by command-specific environment files
- Non-existent files are gracefully ignored
- A non-existent file is skipped and the command still runs; from v3.0.1 it is reported on stderr
unless that entry is marked `optional: true`. This is set per env entry, and is a different thing
from the command-level [`optional`](#optional-) flag, which covers imports only
- Maintains full backwards compatibility with single file syntax

### `entrypoint`
Expand Down Expand Up @@ -267,7 +282,7 @@ commands:
### `env` <Badge text="v2.3.0+" variant="tip" />

**Required**: No
**Type**: String or Array of Strings
**Type**: String, Array of Strings, or Array of Strings and Mappings
**Default**: None
**Implementation**: Processed in command action
**Description**: Path(s) to command-specific environment file(s), relative to the YAML file location. Variables defined here override any with the same name in the global env files. Supports both single file and multiple files syntax.
Expand Down Expand Up @@ -295,11 +310,26 @@ commands:
cmd: ./deploy.sh
```

**Optional Environment Files** <Badge text="v3.0.1+" variant="success" size="small" />:

```yaml
commands:
deploy:
usage: Deploy application
env:
- .env.deploy # Required: warns when absent
- path: .env.secrets
optional: true # Silent when absent
cmd: ./deploy.sh
```

**Technical Details**:

- When using multiple files, they are loaded in order with later files overriding earlier ones
- Command-specific environment files always override global environment files
- Non-existent files are gracefully ignored
- A non-existent file is skipped and the command still runs; from v3.0.1 it is reported on stderr
unless that entry is marked `optional: true`. This is set per env entry, and is a different thing
from the command-level [`optional`](#optional-) flag, which covers imports only
- Maintains full backwards compatibility with single file syntax

### `hide`
Expand All @@ -324,7 +354,7 @@ commands:
**Type**: Boolean
**Default**: `false`
**Implementation**: Checked in `getCommands()` function
**Description**: When set to `true`, prevents errors when imported files are not found. Used primarily for optional command groups.
**Description**: When set to `true`, prevents errors when a command's imported files are not found. Used primarily for optional command groups. It applies to `imports` and nothing else.

```yaml
commands:
Expand All @@ -335,6 +365,24 @@ commands:
- ./commands/optional.ahoy.yml
```

<Aside type="caution" title="Not the same as an optional env file">
`commands.<name>.optional` does **not** silence a missing env file. A command carrying
`optional: true` still warns about every env file it cannot find. Env-file optionality is
configured per entry, inside the `env` mapping itself:

```yaml
commands:
deploy:
optional: true # Only tolerates missing imports
env:
- path: .env.deploy
optional: true # This is what silences a missing env file
cmd: ./deploy.sh
```

See [`env`](#env-) <Badge text="v3.0.1+" variant="success" size="small" /> for the mapping form.
</Aside>

## Complete Schema Example

```yaml
Expand Down