@sous-io/sous 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/markdown/README.md +4 -0
- package/docs/markdown/_sidebar.md +5 -1
- package/docs/markdown/commands.md +293 -274
- package/docs/markdown/configuration.md +7 -0
- package/docs/markdown/repositories-authoring.md +210 -302
- package/docs/markdown/repositories-consuming.md +221 -468
- package/docs/markdown/repositories-file-formats.md +216 -980
- package/docs/markdown/repositories-providers.md +322 -0
- package/docs/markdown/repositories-quickstart.md +340 -0
- package/docs/markdown/repositories-troubleshooting.md +336 -0
- package/docs/markdown/repositories-variables.md +250 -298
- package/docs/markdown/repositories.md +262 -228
- package/package.json +1 -1
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/src/commands/compile.ts +4 -2
- package/src/lib/build-service.ts +52 -4
- package/src/lib/markdown-compiler.ts +20 -1
- package/src/lib/repos/recipe-targets.ts +9 -1
- package/src/lib/settings.ts +51 -3
- package/src/lib/vars/answers.ts +184 -0
- package/src/lib/vars/definition-source.ts +9 -0
- package/src/lib/vars/index.ts +1 -0
|
@@ -1,387 +1,339 @@
|
|
|
1
1
|
# Recipe Variables
|
|
2
2
|
|
|
3
|
-
A recipe that needs a value from your project publishes a **variable definition**: a
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
variable and nothing in scope answers it, or when the answer in scope no longer fits.
|
|
3
|
+
A recipe that needs a value from your project publishes a **variable definition**: a name, a type, a question
|
|
4
|
+
and a set of constraints. You supply an **answer**. A "question" is only the interactive moment: sous asks one
|
|
5
|
+
when a recipe needs a variable and nothing in scope answers it, or when what is there no longer fits.
|
|
7
6
|
|
|
8
|
-
?> This page is about variables that recipes publish. Your project's own `${var}` configuration
|
|
9
|
-
|
|
10
|
-
[
|
|
7
|
+
?> This page is about variables that recipes publish. Your project's own `${var}` configuration variables are
|
|
8
|
+
a separate, ceremony-free system; see [Variables](config-variables.md). The two meet in one place only, under
|
|
9
|
+
[How a template reads an answer](#how-a-template-reads-an-answer).
|
|
11
10
|
|
|
12
|
-
##
|
|
11
|
+
## What a definition is, and where it lives
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
A definition ships inside the recipe that needs it, in its manifest's `variables:` array, so it versions,
|
|
14
|
+
resolves, pins and trusts like the rest of the recipe; every field it can carry is listed in
|
|
15
|
+
[Variable definitions](repositories-file-formats.md#variable-definitions).
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
| `.sous/.env.local` | no, gitignored | Machine-specific answers (`scope: local`) and every secret |
|
|
17
|
+
A definition is in play when your lockfile pins the recipe that declares it, whether you subscribed to that
|
|
18
|
+
recipe or a dependency pulled it in. A recipe the store does not hold yet contributes nothing rather than
|
|
19
|
+
failing, so a fresh clone lists what it can until `sous build` restores the rest.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
A project can also ask questions no recipe publishes: write the same `variables:` array into a YAML or JSON
|
|
22
|
+
file of your own and point `--file` at it. Every `sous vars` command takes that flag, and the definitions in
|
|
23
|
+
it are attributed to a pseudo-recipe in the `local` namespace named after the file, so an `apiUrl` declared in
|
|
24
|
+
`questions.yaml` generates `SOUS_VAR_LOCAL_QUESTIONS_API_URL`, `SOUS_VAR_LOCAL_API_URL` and `SOUS_VAR_API_URL`.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
SOUS_VAR_TASK_FILE_ROOT=.sous/tasks
|
|
30
|
-
```
|
|
26
|
+
!> A definition's `validate.pattern` is a regular expression published by someone else, so sous runs each
|
|
27
|
+
match on a worker under a fixed budget of 100 milliseconds, and a pattern that exceeds it fails validation
|
|
28
|
+
naming the pattern and the recipe rather than blaming your answer. See
|
|
29
|
+
[A variable pattern that runs out of time](repositories-troubleshooting.md#a-variable-pattern-that-runs-out-of-time).
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
changes nothing, and editing the value line is a perfectly normal way to change an answer.
|
|
31
|
+
## Answer the questions
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
Subscribing asks whatever is unanswered, so in normal use you rarely run `sous vars ask` yourself; reach for
|
|
34
|
+
it after editing an env file by hand, after an upgrade tightened a constraint, or to re-answer with `--all`.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
Every trust decision is settled first: a subscribe resolves the dependency closure and runs the trust
|
|
37
|
+
ceremony for each new repository before the first question prints. Questions then run one recipe at a time,
|
|
38
|
+
the one you asked for and then each recipe it depends on, with a lead-in and per-recipe counts:
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
| 1. mapping record | whatever the record names | `TEAM_API_URL` | Only when a record exists for this variable |
|
|
43
|
-
| 2. recipe scope | `SOUS_VAR_<NAMESPACE>_<RECIPE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_TASK_FILES_API_URL` | Answers this one recipe's variable and nothing else |
|
|
44
|
-
| 3. namespace scope | `SOUS_VAR_<NAMESPACE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_API_URL` | Answers every recipe in the namespace at once |
|
|
45
|
-
| 4. shared scope | `SOUS_VAR_<VARIABLE>` | `SOUS_VAR_API_URL` | Answers every recipe that declares that variable name |
|
|
46
|
-
| 5. declared name | the definition's own `env` field | `GITHUB_TOKEN` | How a recipe binds a value the environment already carries |
|
|
47
|
-
|
|
48
|
-
Read from the bottom up, the ladder is a story about sharing. One `SOUS_VAR_API_URL` answers
|
|
49
|
-
every recipe that wants an `apiUrl`, which is what you want most of the time. When two recipes
|
|
50
|
-
want the same name and mean different things, move one answer up a rung to the namespace or the
|
|
51
|
-
recipe form, and the more specific name wins for that recipe alone. When even that is not enough,
|
|
52
|
-
a mapping record settles it.
|
|
53
|
-
|
|
54
|
-
!> Candidate names are only ever GENERATED and looked up, never parsed back into scopes. The
|
|
55
|
-
underscore is both the delimiter and a legal identifier character, so no parse of a name would be
|
|
56
|
-
trustworthy: `SOUS_VAR_TASK_FILES_ROOT` could be three different things. Sous therefore builds the
|
|
57
|
-
five candidates it knows are correct and asks the environment about each one.
|
|
58
|
-
|
|
59
|
-
## The three sources, within a rung
|
|
60
|
-
|
|
61
|
-
Each rung is looked up in three places, in this order:
|
|
62
|
-
|
|
63
|
-
1. **The real shell environment.** `SOUS_VAR_API_URL=... sous build` beats both files.
|
|
64
|
-
2. **`.sous/.env.local`.** Gitignored; your machine, your secrets.
|
|
65
|
-
3. **`.sous/.env`.** Committed; the team's shared defaults.
|
|
66
|
-
|
|
67
|
-
No load ever overwrites a value that is already set, so the first writer wins. This is the same
|
|
68
|
-
precedence the rest of sous uses for env files; see
|
|
69
|
-
[Discovery and overrides](config-discovery.md).
|
|
70
|
-
|
|
71
|
-
## Mapping records
|
|
72
|
-
|
|
73
|
-
A mapping record binds one environment variable, of any name at all, to one fully qualified
|
|
74
|
-
variable. It is the top rung and the universal conflict resolver: two recipes wanting the same
|
|
75
|
-
name, or a name that already means something else in your environment.
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"varMappings": {
|
|
80
|
-
"TEAM_API_URL": "sous-recipes:misc/stuff/apiUrl"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
40
|
+
```text
|
|
41
|
+
workflow/task-files needs 4 answers, and workflow/sub-agent-delegation, which it depends on, needs 2.
|
|
84
42
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
Sous writes the ones it creates into `.sous/conf.d/520-var-mappings.jsonc`, editing one record at
|
|
88
|
-
a time so each name has exactly one, and you may hand-write `varMappings` in your primary config
|
|
89
|
-
too; the two merge like any other config layer.
|
|
43
|
+
workflow/task-files needs 4 answers before it can be used.
|
|
44
|
+
```
|
|
90
45
|
|
|
91
|
-
|
|
92
|
-
the name an answer would use already holds a value that does not fit the definition, you are
|
|
93
|
-
asked where the answer should go:
|
|
46
|
+
Each question prints its header, the publisher's description wrapped to your terminal, and four labeled facts:
|
|
94
47
|
|
|
95
48
|
```text
|
|
96
|
-
|
|
97
|
-
SOUS_VAR_TOOLING_DEPLOY_SERVICE_TOKEN, with a mapping record (recommended)
|
|
98
|
-
SERVICE_TOKEN, replacing what is there
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
Choosing the record writes both the answer under the scoped name and the record binding it, and
|
|
102
|
-
the run reports the pair. Where there is no terminal, the record is written, because overwriting
|
|
103
|
-
a value something else is already using would be the worse of the two guesses.
|
|
49
|
+
Question 1 of 4: taskFileRoot
|
|
104
50
|
|
|
105
|
-
|
|
51
|
+
This recipe mandates the creation of task files that are stored locally and, in
|
|
52
|
+
general, should not be committed. The default stores them in the project's .sous
|
|
53
|
+
directory; any local path works, relative to the project root or absolute.
|
|
106
54
|
|
|
107
|
-
|
|
108
|
-
|
|
55
|
+
default : .sous/tasks
|
|
56
|
+
example : ~/my-task-files
|
|
57
|
+
stored-as : SOUS_VAR_TASK_FILE_ROOT
|
|
58
|
+
storage-path: /home/you/project/.sous/.env
|
|
109
59
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Variable Recipe Answered by Value Source
|
|
113
|
-
apiUrl workflow/task-files SOUS_VAR_API_URL https://api.example.com shared scope, the .env file
|
|
114
|
-
taskFileRoot workflow/task-files SOUS_VAR_TASK_... .sous/tasks shared scope, the .env file
|
|
115
|
-
serviceToken tooling/deploy nothing yet
|
|
60
|
+
? Where should task files be stored? (.sous/tasks):
|
|
61
|
+
⏎ accept default • ⇥ advanced
|
|
116
62
|
```
|
|
117
63
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
project asks questions no recipe publishes yet.
|
|
64
|
+
The legend under the input line names the keys that do anything: Enter accepts what is typed, or the default
|
|
65
|
+
when there is one; a list question names the arrow keys instead, and a yes-or-no question the two letters.
|
|
121
66
|
|
|
122
|
-
|
|
67
|
+
### Tab opens the advanced view
|
|
123
68
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
advanced view of a question prints, and finally every name on the ladder with the rung that
|
|
127
|
-
actually answered.
|
|
69
|
+
Tab works at every kind of question. The advanced view repeats the header and description, prints every fact
|
|
70
|
+
about the variable through the renderer `sous vars show` uses, and offers a menu:
|
|
128
71
|
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
Recipe : workflow/task-files version 1.2.0 from sous-recipes
|
|
137
|
-
Stored in : .env
|
|
138
|
-
Value : https://api.example.com
|
|
139
|
-
|
|
140
|
-
example : https://api.example.com
|
|
72
|
+
```text
|
|
73
|
+
[Advanced Variable Settings]
|
|
74
|
+
|
|
75
|
+
Question 1 of 4: taskFileRoot
|
|
76
|
+
|
|
77
|
+
default : .sous/tasks
|
|
78
|
+
example : ~/my-task-files
|
|
141
79
|
required-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
|
|
142
80
|
defined-by : workflow/task-files https://example.com/owner/recipes/workflow/task-files
|
|
143
81
|
storage-path: /home/you/project/.sous/.env
|
|
144
|
-
stored-as :
|
|
145
|
-
constraints : • must be a value of the type
|
|
82
|
+
stored-as : SOUS_VAR_TASK_FILE_ROOT
|
|
83
|
+
constraints : • must be a value of the type path (type: path)
|
|
146
84
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
85
|
+
? What would you like to do?
|
|
86
|
+
Return to value entry
|
|
87
|
+
Change the storage file
|
|
88
|
+
Change the stored variable name
|
|
151
89
|
```
|
|
152
90
|
|
|
153
|
-
`required-by`
|
|
154
|
-
|
|
91
|
+
`required-by` names the recipe whose closure pulled this variable in, and spells out the chain when it
|
|
92
|
+
arrived through a dependency; `defined-by` names the recipe that declares it. Both carry the location beside
|
|
93
|
+
the recipe key, in muted grey: a repository URL, or a filesystem path for a repository on this machine.
|
|
155
94
|
|
|
156
|
-
|
|
157
|
-
|
|
95
|
+
**Changing the stored variable name** offers every rung the ladder looks up, sous's choice preselected, plus
|
|
96
|
+
a name you type; one the ladder never looks at is bound with a [mapping record](#when-names-collide-mapping-records).
|
|
158
97
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
98
|
+
**Changing the storage file** offers the committed `.sous/.env` and the gitignored `.sous/.env.local`.
|
|
99
|
+
Pointing a secret, or a variable the publisher declared machine-specific, at the committed file is allowed;
|
|
100
|
+
sous says what that means and asks you to confirm, which is informed consent rather than a locked door:
|
|
162
101
|
|
|
163
|
-
|
|
102
|
+
```text
|
|
103
|
+
workflow/task-files declared this variable a secret, and .env is committed to git. An answer stored
|
|
104
|
+
there enters your project's git history, is pushed with every clone, and is visible to everyone who
|
|
105
|
+
can read the repository.
|
|
164
106
|
|
|
165
|
-
|
|
107
|
+
? Store this answer in .env anyway? (y/N)
|
|
108
|
+
```
|
|
166
109
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
| `sous vars ask --all` | Asks every variable again, including the ones already answered |
|
|
176
|
-
| `sous vars ask --file <path>` | Reads definitions from a standalone definitions file |
|
|
177
|
-
| `sous vars ask --answer <name>=<value>` | Answers one question ahead of time; repeat it for each answer |
|
|
178
|
-
| `sous vars ask --answers-file <path>` | Reads answers from a YAML or JSON file of `name: value` pairs |
|
|
179
|
-
| `sous vars ask --dry-run` | Reports what would be asked and written, without writing anything |
|
|
110
|
+
Once anything has changed, the first menu item becomes "Save changes and return to value entry" and a
|
|
111
|
+
"Discard changes and return to value entry" item joins it; returning prints the question again with the
|
|
112
|
+
updated facts. When an answer is entered, two lines say what was stored and where; a secret is never printed:
|
|
113
|
+
|
|
114
|
+
```text
|
|
115
|
+
Answer : SOUS_VAR_TASK_FILE_ROOT=.sous/tasks
|
|
116
|
+
Saved to: /home/you/project/.sous/.env
|
|
117
|
+
```
|
|
180
118
|
|
|
181
|
-
|
|
119
|
+
Every run ends with a three-part report: `Answers already in scope:`, `Answers stored:`, `Left unanswered:`.
|
|
182
120
|
|
|
183
|
-
|
|
184
|
-
variable, an environment variable that answers one, a recipe, a namespace or a repository, and
|
|
185
|
-
anything larger than a variable asks every question it publishes:
|
|
121
|
+
## Where answers are stored
|
|
186
122
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
123
|
+
Answers live in your project's env files, and nowhere else:
|
|
124
|
+
|
|
125
|
+
| File | Committed | Holds |
|
|
126
|
+
|------|-----------|-------|
|
|
127
|
+
| `.sous/.env` | yes | Shared answers (`scope: shared`), the team's defaults |
|
|
128
|
+
| `.sous/.env.local` | no, gitignored | Machine-specific answers (`scope: local`) and every secret |
|
|
129
|
+
|
|
130
|
+
Sous edits them the way a careful person would: exactly one value line is rewritten or appended, and
|
|
131
|
+
comments, blank lines, ordering and quoting survive untouched. A new entry gets a generated header comment:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Set by sous for workflow/qa-variables: Where should a review keep its working files?
|
|
135
|
+
# A run writes partial output, logs and diffs somewhere before it assembles the note.
|
|
136
|
+
# Edit freely; sous only rewrites the value line.
|
|
137
|
+
SOUS_VAR_QA_SCRATCH_DIR=/var/tmp/qa-review
|
|
200
138
|
```
|
|
201
139
|
|
|
202
|
-
|
|
203
|
-
`repository:namespace/recipe.variable`, and matching is case-sensitive. An environment variable
|
|
204
|
-
name resolves when the definition declares it (a recipe may bind an existing variable such as
|
|
205
|
-
`GITHUB_TOKEN`) or when it is one of the generated ladder names that `.sous/.env` or
|
|
206
|
-
`.sous/.env.local` actually sets.
|
|
140
|
+
Comments are output only; sous never reads one back, and editing the value line changes the answer.
|
|
207
141
|
|
|
208
|
-
|
|
209
|
-
same way. Each one resolves against what the one before it left, so
|
|
210
|
-
`sous vars ask --namespace workflow apiUrl` asks about that namespace's `apiUrl` even when
|
|
211
|
-
another namespace publishes one too.
|
|
142
|
+
## How sous finds an answer: the ladder
|
|
212
143
|
|
|
213
|
-
|
|
214
|
-
you meant. `--accept-first` takes the first one listed, and a run with no terminal fails naming
|
|
215
|
-
that flag rather than guessing.
|
|
144
|
+
For each variable, sous tries these names in order; the first one holding a value wins.
|
|
216
145
|
|
|
217
|
-
|
|
146
|
+
| Rung | Name | Example | When it applies |
|
|
147
|
+
|------|------|---------|-----------------|
|
|
148
|
+
| 1. mapping record | whatever the record names | `TEAM_TOKEN` | Only when a record exists for this variable |
|
|
149
|
+
| 2. recipe scope | `SOUS_VAR_<NAMESPACE>_<RECIPE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_SERVICE_TOKEN` | Answers this one recipe's variable and nothing else |
|
|
150
|
+
| 3. namespace scope | `SOUS_VAR_<NAMESPACE>_<VARIABLE>` | `SOUS_VAR_WORKFLOW_QA_SERVICE_TOKEN` | Answers every recipe in the namespace at once |
|
|
151
|
+
| 4. shared scope | `SOUS_VAR_<VARIABLE>` | `SOUS_VAR_QA_SERVICE_TOKEN` | Answers every recipe that declares that variable name |
|
|
152
|
+
| 5. declared name | the definition's own `env` field | `QA_SERVICE_TOKEN` | How a recipe binds a value the environment already carries |
|
|
218
153
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
154
|
+
Read from the bottom up, the ladder is a story about sharing. One `SOUS_VAR_API_URL` answers every recipe
|
|
155
|
+
that wants an `apiUrl`, which is what you want most of the time; when two recipes want the same name and
|
|
156
|
+
mean different things, move one answer up a rung and the more specific name wins for that recipe alone.
|
|
222
157
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
158
|
+
Each rung is looked up in three places, in this order: the real shell environment, then `.sous/.env.local`,
|
|
159
|
+
then `.sous/.env`. So `SOUS_VAR_API_URL=... sous build` beats both files, and a machine-specific answer beats
|
|
160
|
+
the team's committed default; see [Discovery and overrides](config-discovery.md).
|
|
226
161
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```
|
|
162
|
+
!> Candidate names are only ever GENERATED and looked up, never parsed back into scopes. The underscore is
|
|
163
|
+
both the delimiter and a legal identifier character, so no parse would be trustworthy.
|
|
230
164
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
stored and under what name). The keys that do anything are named by the legend the question draws
|
|
234
|
-
under its own input line, in the style the stock prompts use. Those facts are drawn by the renderer
|
|
235
|
-
the advanced view uses, so the same label means the same thing and lines up the same way on both.
|
|
165
|
+
`sous vars show` prints the whole ladder, which is the fastest way to see why an answer did or did not take
|
|
166
|
+
effect. Here `qaServiceToken` declared `env: QA_SERVICE_TOKEN`, so the bottom rung is that name:
|
|
236
167
|
|
|
237
168
|
```term
|
|
238
|
-
|
|
169
|
+
$ sous vars show qaServiceToken
|
|
170
|
+
➔ Environment variables sous looks at, most specific first:
|
|
171
|
+
|
|
172
|
+
Environment variable Rung Status
|
|
173
|
+
----------------------------------------------- --------------- -------------
|
|
174
|
+
SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_SERVICE_TOKEN recipe scope not set
|
|
175
|
+
SOUS_VAR_WORKFLOW_QA_SERVICE_TOKEN namespace scope not set
|
|
176
|
+
SOUS_VAR_QA_SERVICE_TOKEN shared scope not set
|
|
177
|
+
QA_SERVICE_TOKEN declared name not set
|
|
178
|
+
```
|
|
239
179
|
|
|
240
|
-
|
|
180
|
+
## When names collide: mapping records
|
|
241
181
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
will store and search for your task files. The default value stores task files in
|
|
245
|
-
the project's .sous directory, but you can specify any local path, either relative
|
|
246
|
-
to the project root or absolute.
|
|
182
|
+
A mapping record binds one environment variable, of any name at all, to one fully qualified variable. It is
|
|
183
|
+
the top rung and the universal conflict resolver: two recipes wanting the same name, or a name already in use.
|
|
247
184
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
stored-as : SOUS_VAR_TASK_FILE_ROOT
|
|
251
|
-
storage-path: /home/you/project/.sous/.env
|
|
185
|
+
You rarely write one by hand, because sous offers one when the conflict appears: when the name an answer
|
|
186
|
+
would use already holds a value that does not fit the definition, you are asked where the answer should go:
|
|
252
187
|
|
|
253
|
-
|
|
254
|
-
|
|
188
|
+
```text
|
|
189
|
+
? SERVICE_TOKEN already holds a value that does not fit serviceToken. Where should this answer go?
|
|
190
|
+
> SOUS_VAR_TOOLING_DEPLOY_SERVICE_TOKEN, with a mapping record (recommended)
|
|
191
|
+
SERVICE_TOKEN, replacing what is there
|
|
255
192
|
```
|
|
256
193
|
|
|
257
|
-
|
|
258
|
-
the
|
|
259
|
-
`⇥ advanced` alone. A question answered from a list rather than typed names the arrow keys
|
|
260
|
-
instead:
|
|
194
|
+
Choosing the record writes both the answer under the scoped name and the record binding it, and the run
|
|
195
|
+
reports the pair. Where there is no terminal the record is written, since overwriting a value in use is worse.
|
|
261
196
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
linear
|
|
267
|
-
↑↓ navigate • ⏎ select • ⇥ advanced
|
|
268
|
-
```
|
|
197
|
+
Sous writes the records it creates into `.sous/conf.d/520-var-mappings.jsonc`, one at a time, so each name
|
|
198
|
+
has exactly one. The record's shape and the rest of the managed layers are under
|
|
199
|
+
[Configuration keys](repositories-file-formats.md#configuration-keys) and
|
|
200
|
+
[Managed config layers](repositories-file-formats.md#managed-config-layers); hand-write one if you prefer.
|
|
269
201
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
202
|
+
## See and answer variables from the command line
|
|
203
|
+
|
|
204
|
+
`sous vars list` prints every variable in play, what answered it, and where the value came from.
|
|
273
205
|
|
|
274
206
|
```term
|
|
275
|
-
|
|
276
|
-
|
|
207
|
+
$ sous vars list
|
|
208
|
+
Variable Recipe Answered by Value Source
|
|
209
|
+
-------------- --------------------- ----------------------- -------------- --------------------------------
|
|
210
|
+
qaScratchDir workflow/qa-variables SOUS_VAR_QA_SCRATCH_DIR /var/tmp/qa shared scope, the .env.local file
|
|
211
|
+
qaServiceToken workflow/qa-variables (unanswered) nothing yet
|
|
212
|
+
qaTaskRoot workflow/qa-variables SOUS_VAR_QA_TASK_ROOT .sous/qa-notes shared scope, the .env file
|
|
277
213
|
```
|
|
278
214
|
|
|
279
|
-
|
|
280
|
-
|
|
215
|
+
`sous vars show <name>` prints one variable in full: the question, the publisher's description and example,
|
|
216
|
+
the recipe and version that published it, the value in scope and whether it fits, the labeled facts the
|
|
217
|
+
advanced view draws, and the ladder table above. The name may be bare or the full `namespace/recipe.name`.
|
|
281
218
|
|
|
282
|
-
|
|
219
|
+
`sous vars ask [name]` asks the questions the definitions imply and stores the answers.
|
|
283
220
|
|
|
284
|
-
|
|
285
|
-
|
|
221
|
+
| Invocation | What it does |
|
|
222
|
+
|------------|--------------|
|
|
223
|
+
| `sous vars ask` | Asks only what is unanswered, or what no longer fits its definition |
|
|
224
|
+
| `sous vars ask <name>` | Asks everything the name covers; see the forms below |
|
|
225
|
+
| `sous vars ask --repo <name>` | Asks every variable one repository publishes |
|
|
226
|
+
| `sous vars ask --namespace <name>` | Asks every variable one namespace publishes |
|
|
227
|
+
| `sous vars ask --var <name>` | Asks one variable; repeat the flag for each one |
|
|
228
|
+
| `sous vars ask --all` | Asks every variable again, including the ones already answered |
|
|
229
|
+
| `sous vars ask --accept-first` | When the name matches several things, takes the first one listed |
|
|
230
|
+
| `sous vars ask --file <path>` | Reads definitions from a standalone definitions file |
|
|
231
|
+
| `sous vars ask --answer <name>=<value>` | Answers one question ahead of time; repeat it for each answer |
|
|
232
|
+
| `sous vars ask --answers-file <path>` | Reads answers from a YAML or JSON file of `name: value` pairs |
|
|
233
|
+
| `sous vars ask --dry-run` | Reports what would be asked and written, without writing anything |
|
|
234
|
+
| `sous vars ask --non-interactive` | Never asks; fails instead, naming the environment variable or flag that would answer each question |
|
|
286
235
|
|
|
287
|
-
|
|
288
|
-
[Advanced Variable Settings]
|
|
236
|
+
`<name>` is a reference, resolved like any sous ref; anything larger than a variable asks all its questions:
|
|
289
237
|
|
|
290
|
-
|
|
238
|
+
```bash
|
|
239
|
+
sous vars ask taskFileRoot # one variable, by the name its recipe gave it
|
|
240
|
+
sous vars ask SOUS_VAR_TASK_FILE_ROOT # the same one, by a name in use that answers it
|
|
241
|
+
sous vars ask workflow/task-files.taskFileRoot # the same one, spelled out in full
|
|
242
|
+
sous vars ask task-files # every question one recipe asks
|
|
243
|
+
sous vars ask sous-recipes # every question that repository's recipes ask
|
|
244
|
+
```
|
|
291
245
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
246
|
+
Matching is case-sensitive, and an environment variable name resolves when a definition declares it or an
|
|
247
|
+
env file sets it. `--repo`, `--namespace` and `--var` say which kind is meant, each narrowing what the one
|
|
248
|
+
before left, so `sous vars ask --namespace workflow apiUrl` asks about that namespace's `apiUrl` alone. When
|
|
249
|
+
a name matches several things, sous lists them and asks; `--accept-first` takes the first, and a run with no
|
|
250
|
+
terminal fails naming it.
|
|
297
251
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
storage-path: /home/you/project/.sous/.env
|
|
303
|
-
stored-as : SOUS_VAR_TASK_FILE_ROOT
|
|
304
|
-
constraints : • must be a value of the type path (type: path)
|
|
305
|
-
• must be at least 1 character long (minLength: 1)
|
|
252
|
+
?> Bare `sous vars` is shorthand for `vars list`, and `sous vars <name>` for `vars show <name>`, which prints
|
|
253
|
+
the same report (`vars show` spells the storage path out in full); `sous var list`, `sous var show` and
|
|
254
|
+
`sous var ask` work too. A variable named `list`, `show` or `ask` collides with the subcommand, so reach it
|
|
255
|
+
the long way: `sous vars show ask`.
|
|
306
256
|
|
|
307
|
-
|
|
308
|
-
Return to value entry
|
|
309
|
-
Change the storage file
|
|
310
|
-
Change the stored variable name
|
|
311
|
-
```
|
|
257
|
+
### Answering ahead of the questions
|
|
312
258
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
259
|
+
`--answer <name>=<value>` answers a question before it is asked and repeats as often as needed, and
|
|
260
|
+
`--answers-file <path>` reads the same pairs from YAML or JSON; an `--answer` wins over the same name in the
|
|
261
|
+
file. Pair either with `--dry-run` first, as in
|
|
262
|
+
`sous vars ask --var qaScratchDir --answer qaScratchDir=/var/tmp/qa-review --dry-run`. Both work the same way
|
|
263
|
+
on `sous subscription add`; see [Answer the questions](repositories-consuming.md#answer-the-questions).
|
|
317
264
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
`.sous/.env` and the gitignored `.sous/.env.local`. Once anything has changed, the first menu item
|
|
322
|
-
becomes "Save changes and return to value entry" and a "Discard changes" item joins it.
|
|
265
|
+
Every supplied answer is checked before anything is written, so a value that does not fit fails the run
|
|
266
|
+
naming the constraint, and an unknown name fails naming every variable in play. An answer replacing a stored
|
|
267
|
+
one is written where that answer lives, and a value the shell supplies is never rewritten:
|
|
323
268
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
269
|
+
```text
|
|
270
|
+
Answers stored:
|
|
271
|
+
qaParallelAgents: 4 SOUS_VAR_QA_PARALLEL_AGENTS in .env
|
|
272
|
+
replaced the answer already there: 99
|
|
273
|
+
SOUS_VAR_QA_PARALLEL_AGENTS is set in your shell environment and answers
|
|
274
|
+
this variable first; unset it for the stored answer to take effect
|
|
275
|
+
```
|
|
329
276
|
|
|
330
|
-
|
|
331
|
-
scope, what was stored and under which name in which file, and what was left unanswered and why.
|
|
277
|
+
## Answer without a terminal
|
|
332
278
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
279
|
+
Sous never hangs waiting on a prompt it cannot show; what counts as "no terminal" is listed under
|
|
280
|
+
[Run sous in CI, or from an agent](repositories-consuming.md#run-sous-in-ci-or-from-an-agent). Such a run
|
|
281
|
+
fails on the unanswered required variables, naming every environment variable that would satisfy each one:
|
|
336
282
|
|
|
337
|
-
|
|
283
|
+
```text
|
|
284
|
+
Error: 1 variable still needs an answer, and there is no terminal to ask on.
|
|
338
285
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
(comments allowed), and an `--answer` wins over the same name in the file. Both flags work the
|
|
342
|
-
same way on `sous subscription add`, which is where a run with no terminal usually meets them;
|
|
343
|
-
[Consuming recipes](repositories-consuming.md#answering-questions-ahead-of-time) walks through
|
|
344
|
-
that flow, dry run first.
|
|
286
|
+
Set one of the environment variables listed under each variable, or run
|
|
287
|
+
'sous vars ask' from a terminal.
|
|
345
288
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
289
|
+
qaTaskRoot (workflow/qa-variables): Where should the review notes be stored?
|
|
290
|
+
SOUS_VAR_WORKFLOW_QA_VARIABLES_QA_TASK_ROOT (recipe scope)
|
|
291
|
+
SOUS_VAR_WORKFLOW_QA_TASK_ROOT (namespace scope)
|
|
292
|
+
SOUS_VAR_QA_TASK_ROOT (shared scope)
|
|
349
293
|
```
|
|
350
294
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
is checked against its definition before anything is written, so a value that does not fit fails
|
|
354
|
-
the run naming the constraint and the publisher's example, and a name no recipe declares fails
|
|
355
|
-
naming every variable that is in play. An answer for a variable that already has one replaces it,
|
|
356
|
-
where that answer lives, and the report says what it replaced. Whatever is left over is asked for
|
|
357
|
-
as usual.
|
|
295
|
+
Set one of those names in your pipeline and the build proceeds. For an answer the whole team shares, and
|
|
296
|
+
nothing about it sensitive, committing it to `.sous/.env` is simpler: a fresh clone needs no pipeline setup.
|
|
358
297
|
|
|
359
|
-
##
|
|
298
|
+
## How a template reads an answer
|
|
360
299
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
300
|
+
A build lays the answers into the template scope itself. For every variable a subscribed recipe publishes,
|
|
301
|
+
the build walks the ladder above, takes the first value it finds, and adds it to the scope under the
|
|
302
|
+
variable's own name. So once `taskFileRoot` is answered, `{{ taskFileRoot }}` renders in the recipe's own
|
|
303
|
+
skills and in any template this project compiles, and `${taskFileRoot}` works in `_vars` and every other
|
|
304
|
+
config value. Nothing has to be mapped by hand.
|
|
364
305
|
|
|
365
|
-
|
|
366
|
-
|
|
306
|
+
The answers sit under your config, not over it. The scope a template renders with is assembled in this
|
|
307
|
+
order, each layer overriding the one before:
|
|
367
308
|
|
|
368
|
-
|
|
369
|
-
|
|
309
|
+
1. The auto-injected `sous*` variables.
|
|
310
|
+
2. The recipe answers, found through the ladder.
|
|
311
|
+
3. Your `_env` block.
|
|
312
|
+
4. Your `_vars` block.
|
|
370
313
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
314
|
+
So a project that already carries an answer in `_vars`, or maps one through `_env`, keeps rendering exactly
|
|
315
|
+
what it did; the answer in the env files is simply shadowed, and `sous vars list` still reports it.
|
|
316
|
+
|
|
317
|
+
When no rung answers, the definition's own `default` is what renders, because the description a publisher
|
|
318
|
+
writes promises what the default does. A required variable with no answer and no default renders as an
|
|
319
|
+
empty string, and the build says so before it compiles, naming each such variable, the recipe that asks for
|
|
320
|
+
it, and `sous vars ask` as the way to answer. The build still succeeds; an unanswered question is
|
|
321
|
+
something to tell you about, not a reason to refuse the rest of the project.
|
|
322
|
+
|
|
323
|
+
?> Two recipes may ask the same question. Their shared answer renders in both, and in your own templates.
|
|
324
|
+
When the recipe-scoped name gives one of them a different answer, that recipe's own files render its own
|
|
325
|
+
answer while everything else, your templates included, renders the first definition's; `sous vars show`
|
|
326
|
+
tells you which names are in play.
|
|
327
|
+
|
|
328
|
+
An answer is laid in exactly as it is stored: a path stays the string you typed, relative or absolute, and a
|
|
329
|
+
number stays text. A template that needs an absolute path from a relative answer composes one under another
|
|
330
|
+
name in `_vars`, for instance `taskFileDir: "${sousDir}/../${taskFileRoot}"`.
|
|
376
331
|
|
|
377
|
-
|
|
378
|
-
answer the whole team shares and nothing about it is sensitive, committing it to `.sous/.env` is
|
|
379
|
-
simpler still: a fresh clone then needs no pipeline configuration at all.
|
|
332
|
+
The `_env` block is still the way to reach any environment variable no recipe asks about.
|
|
380
333
|
|
|
381
334
|
## Where to go next
|
|
382
335
|
|
|
383
336
|
- [Consuming recipes](repositories-consuming.md): subscribing, building, and what lands where
|
|
384
|
-
- [Authoring a repository](repositories-authoring.md): declaring the definitions this page
|
|
385
|
-
|
|
386
|
-
- [Variable definitions](repositories-file-formats.md#variable-definitions): the full field table
|
|
337
|
+
- [Authoring a repository](repositories-authoring.md): declaring the definitions this page resolves
|
|
338
|
+
- [Troubleshooting](repositories-troubleshooting.md): a question sous cannot ask, a pattern that runs out of time
|
|
387
339
|
- [Command reference](commands.md): every command and flag
|