diff --git a/src/content/docs/guides/environment.mdx b/src/content/docs/guides/environment.mdx
index 1e34e9a..eea85ad 100644
--- a/src/content/docs/guides/environment.mdx
+++ b/src/content/docs/guides/environment.mdx
@@ -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
+
+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`.
+
+
## Precedence
diff --git a/src/content/docs/reference/environment.mdx b/src/content/docs/reference/environment.mdx
index cf1d646..7aadedf 100644
--- a/src/content/docs/reference/environment.mdx
+++ b/src/content/docs/reference/environment.mdx
@@ -7,7 +7,11 @@ import { Aside, Badge } from "@astrojs/starlight/components";
## Overview
-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 .
+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
+- An array mixing file paths with `{ path, optional }` mapping entries , which mark the files that are allowed to be absent - see [Optional env files](#optional-env-files-)
## File path resolution
@@ -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.
+
+## Optional env files
+
+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
+```
+
+
## Runtime variables
diff --git a/src/content/docs/reference/yaml-schema.mdx b/src/content/docs/reference/yaml-schema.mdx
index c9908a4..4c93531 100644
--- a/src/content/docs/reference/yaml-schema.mdx
+++ b/src/content/docs/reference/yaml-schema.mdx
@@ -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
@@ -78,7 +78,7 @@ usage: "Collection of commands for the project"
### `env`
**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.
@@ -100,13 +100,28 @@ env:
- .env.override
```
+**Optional Environment Files** :
+
+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`
@@ -267,7 +282,7 @@ commands:
### `env`
**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.
@@ -295,11 +310,26 @@ commands:
cmd: ./deploy.sh
```
+**Optional Environment Files** :
+
+```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`
@@ -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:
@@ -335,6 +365,24 @@ commands:
- ./commands/optional.ahoy.yml
```
+
+
## Complete Schema Example
```yaml