From f4b92b9eefa6143a5b3233b3e3611bee13421d33 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Wed, 2 Sep 2026 19:32:40 +1000 Subject: [PATCH 1/5] docs: env files are required by default from v3.0.1 ahoy-cli/ahoy#189 changes what happens when an env file is missing. The file is still skipped and the command still runs, but Ahoy now reports it on stderr, and `optional: true` marks the entries meant to be absent. Three statements were wrong as a result: - guides/environment.mdx said missing files are silently skipped - reference/environment.mdx said the same in an aside - reference/yaml-schema.mdx documented only the string and list forms of `env`, with no mention of the mapping form Correct all three, and cross-reference `optional` for imports, which carries the identical meaning. The layered .env.base/.env.local example keeps its plain-string form so it stays valid for the v2.5.0 readers that section is badged for, with a pointer to the new section instead. Every YAML example here was run against a binary built from #189 (2c87d93), and the warning text and `config validate` symbols are copied from that binary's output rather than transcribed. The v2 parse failure quoted in the compatibility asides was reproduced against a v2 build. Documents an unreleased feature: #189 is still open, so this should land only once it merges and v3.0.1 ships. Claude-Session: https://claude.ai/code/session_01KHtzL6Sv2wCVrXpgku2zJ2 --- src/content/docs/guides/environment.mdx | 41 +++++++++++++++++- src/content/docs/reference/environment.mdx | 49 +++++++++++++++++++++- src/content/docs/reference/yaml-schema.mdx | 40 +++++++++++++++--- 3 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/content/docs/guides/environment.mdx b/src/content/docs/guides/environment.mdx index 1e34e9a..ccc0317 100644 --- a/src/content/docs/guides/environment.mdx +++ b/src/content/docs/guides/environment.mdx @@ -67,7 +67,46 @@ commands: cmd: ./deploy.sh staging ``` -Missing files are silently skipped - no error. +A file like `.env.local` is often absent on some machines. From v3.0.1 Ahoy +reports that rather than passing over it in silence, so mark such entries +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 says so: + +``` +[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/): the file is expected not to +always be there. + +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..95b954d 100644 --- a/src/content/docs/reference/environment.mdx +++ b/src/content/docs/reference/environment.mdx @@ -24,7 +24,54 @@ 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` carries the same meaning here as it does for +[`imports`](/reference/yaml-schema/): the entry may legitimately not resolve to a 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..8a914dd 100644 --- a/src/content/docs/reference/yaml-schema.mdx +++ b/src/content/docs/reference/yaml-schema.mdx @@ -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 the entry is marked `optional: true` - see [`optional`](#optional-), which carries the same + meaning for imports - 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 the entry is marked `optional: true` - see [`optional`](#optional-), which carries the same + meaning for imports - 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 imported files are not found. Used primarily for optional command groups. The same flag marks an env file entry as expected-to-be-absent - see [`env`](#env-) . ```yaml commands: From e107b879c9b0e7e74e45668af2c0be2d9a6eb8d5 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 3 Sep 2026 09:00:17 +1000 Subject: [PATCH 2/5] Update env file guidelines --- src/content/docs/guides/environment.mdx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/content/docs/guides/environment.mdx b/src/content/docs/guides/environment.mdx index ccc0317..47e8789 100644 --- a/src/content/docs/guides/environment.mdx +++ b/src/content/docs/guides/environment.mdx @@ -67,15 +67,16 @@ commands: cmd: ./deploy.sh staging ``` -A file like `.env.local` is often absent on some machines. From v3.0.1 Ahoy -reports that rather than passing over it in silence, so mark such entries -optional - see below. +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 pleaser start to mark 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 says so: +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. @@ -94,8 +95,8 @@ env: ``` This is the same `optional` you may already use on -[imports](/guides/importing/): the file is expected not to -always be there. +[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 From 04424993aee84e554b1d16344a057c189909c937 Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 3 Sep 2026 09:48:05 +1000 Subject: [PATCH 3/5] docs: list the three env forms in the reference overview Review feedback: the Overview named only the single-path and array-of-paths forms, so the mapping entries added in v3.0.1 were absent from the first thing a reader meets. Spell out all three, badge them by the version that introduced each, and point at the section that covers optional entries. Claude-Session: https://claude.ai/code/session_01KHtzL6Sv2wCVrXpgku2zJ2 --- src/content/docs/reference/environment.mdx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/content/docs/reference/environment.mdx b/src/content/docs/reference/environment.mdx index 95b954d..5bb68d0 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 From 42922873c1ef7e60239c485b7aaa70f11415182b Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 3 Sep 2026 09:49:06 +1000 Subject: [PATCH 4/5] Correct typo --- src/content/docs/guides/environment.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/guides/environment.mdx b/src/content/docs/guides/environment.mdx index 47e8789..eea85ad 100644 --- a/src/content/docs/guides/environment.mdx +++ b/src/content/docs/guides/environment.mdx @@ -69,7 +69,7 @@ commands: 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 pleaser start to mark such entries +reports this, rather than passing over it in silence, so please start marking such entries as optional - see below. ## Files that may be absent From 086c29e2a2f36f328f750458951658df60bba3dc Mon Sep 17 00:00:00 2001 From: Drew Robinson Date: Thu, 3 Sep 2026 10:03:16 +1000 Subject: [PATCH 5/5] docs: separate command-level optional from env-entry optional Review feedback: the `optional` field description implied one flag covered both imports and env files. It does not - the two are independent, and a command carrying `optional: true` still warns about every env file it cannot find. Confirmed against a binary built from ahoy-cli/ahoy#189: a command with `optional: true` and a missing env file produces the same warning as one without the flag. In the source, `cmd.Optional` guards only the imports branch, while the env warning is guarded by each entry's own `envFile.Optional`. State that `optional` applies to imports and nothing else, add a caution contrasting it with the per-entry env form, and reword the two env cross-references that called them the same thing. Claude-Session: https://claude.ai/code/session_01KHtzL6Sv2wCVrXpgku2zJ2 --- src/content/docs/reference/environment.mdx | 8 +++--- src/content/docs/reference/yaml-schema.mdx | 30 +++++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/content/docs/reference/environment.mdx b/src/content/docs/reference/environment.mdx index 5bb68d0..7aadedf 100644 --- a/src/content/docs/reference/environment.mdx +++ b/src/content/docs/reference/environment.mdx @@ -43,7 +43,7 @@ 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 +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 @@ -58,8 +58,10 @@ env: optional: false # Same as the plain string form ``` -`optional` carries the same meaning here as it does for -[`imports`](/reference/yaml-schema/): the entry may legitimately not resolve to a file. +`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. diff --git a/src/content/docs/reference/yaml-schema.mdx b/src/content/docs/reference/yaml-schema.mdx index 8a914dd..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 @@ -120,8 +120,8 @@ env: - Blank lines and lines starting with '#' are ignored - Variables from global files can be overridden by command-specific environment files - A non-existent file is skipped and the command still runs; from v3.0.1 it is reported on stderr - unless the entry is marked `optional: true` - see [`optional`](#optional-), which carries the same - meaning for imports + 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` @@ -328,8 +328,8 @@ commands: - When using multiple files, they are loaded in order with later files overriding earlier ones - Command-specific environment files always override global environment files - A non-existent file is skipped and the command still runs; from v3.0.1 it is reported on stderr - unless the entry is marked `optional: true` - see [`optional`](#optional-), which carries the same - meaning for imports + 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` @@ -354,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. The same flag marks an env file entry as expected-to-be-absent - see [`env`](#env-) . +**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: @@ -365,6 +365,24 @@ commands: - ./commands/optional.ahoy.yml ``` + + ## Complete Schema Example ```yaml