@sous-io/sous 0.2.28 → 0.2.30
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/_sidebar.md +2 -0
- package/docs/markdown/commands.md +10 -6
- package/docs/markdown/repositories-authoring.md +19 -11
- package/docs/markdown/repositories-consuming.md +34 -13
- package/docs/markdown/repositories-file-formats.md +88 -13
- package/docs/markdown/repositories-providers.md +8 -6
- package/docs/markdown/repositories-troubleshooting.md +6 -0
- package/docs/markdown/repositories-variables.md +13 -3
- package/docs/markdown/repositories.md +3 -3
- package/package.json +1 -1
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +11 -7
- package/recipes/core/sous-skills/skills/about-sous-repos/SKILL.tpl.md +7 -4
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/src/commands/lock/rebuild.ts +7 -6
- package/src/commands/recipe/show.ts +163 -64
- package/src/commands/repo/contribute.ts +1 -10
- package/src/commands/repo/gc.ts +3 -2
- package/src/commands/repo/release.ts +20 -3
- package/src/commands/repo/search.ts +2 -1
- package/src/commands/vars/ask.ts +32 -10
- package/src/lib/refs/find.ts +202 -21
- package/src/lib/refs/index.ts +2 -0
- package/src/lib/refs/parse.ts +844 -0
- package/src/lib/refs/scopes.ts +34 -0
- package/src/lib/refs/settle.ts +219 -0
- package/src/lib/repos/catalog.ts +87 -80
- package/src/lib/repos/declarations.ts +177 -0
- package/src/lib/repos/formats/common.ts +14 -0
- package/src/lib/repos/formats/index-file.ts +42 -2
- package/src/lib/repos/formats/lockfile.ts +5 -3
- package/src/lib/repos/formats/recipe-manifest.ts +16 -212
- package/src/lib/repos/formats/variable-definition.ts +268 -0
- package/src/lib/repos/index.ts +0 -1
- package/src/lib/repos/lock-service.ts +3 -2
- package/src/lib/repos/locked-namespace-resolver.ts +21 -15
- package/src/lib/repos/locked-recipes.ts +2 -2
- package/src/lib/repos/namespace-resolver.ts +40 -47
- package/src/lib/repos/providers/base.ts +30 -0
- package/src/lib/repos/providers/github.ts +22 -0
- package/src/lib/repos/providers/gitlab.ts +68 -0
- package/src/lib/repos/providers/provider.ts +96 -0
- package/src/lib/repos/release/index-builder.ts +221 -47
- package/src/lib/repos/release/index.ts +1 -0
- package/src/lib/repos/release/plan.ts +8 -6
- package/src/lib/repos/release/settle.ts +237 -0
- package/src/lib/repos/release/tags.ts +3 -7
- package/src/lib/repos/resolver.ts +251 -53
- package/src/lib/repos/subscription-service.ts +355 -49
- package/src/lib/repos/update-plan.ts +3 -2
- package/src/lib/vars/mappings.ts +17 -30
- package/src/lib/vars/question-plan.ts +44 -21
- package/src/lib/repos/ref.ts +0 -513
|
@@ -26,4 +26,6 @@
|
|
|
26
26
|
- [0005: Follow-ups from the first project on the hand-off](adrs/0005-first-project-follow-ups.md)
|
|
27
27
|
- [0006: `sous repo init` follows `sous-recipes`](adrs/0006-repo-init-model-home.md)
|
|
28
28
|
- [0007: Frozen index entries and tolerant readers](adrs/0007-frozen-index-dependencies.md)
|
|
29
|
+
- [0008: One ref parser, told where the ref came from](adrs/0008-one-ref-parser.md)
|
|
29
30
|
- [0009: One whole build, everywhere](adrs/0009-whole-build-everywhere.md)
|
|
31
|
+
- [0010: The index describes each version's dependencies and questions](adrs/0010-index-describes-recipes.md)
|
|
@@ -347,8 +347,9 @@ optionally carrying an `@<range>` and a `repo:` qualifier; see
|
|
|
347
347
|
### `sous subscription add REF`
|
|
348
348
|
Subscribes this project to a recipe, or to a whole namespace of them, then builds the project so the recipe's
|
|
349
349
|
files are on disk when the command returns. Also spelled `sous subscribe`. Takes `-y, --yes`, `--no-build`, and
|
|
350
|
-
`--dry-run`, which also prints the
|
|
351
|
-
|
|
350
|
+
`--dry-run`, which also prints every question the recipes would ask. A dry run downloads nothing, so a recipe
|
|
351
|
+
not on this machine is described from its repository's index; one whose index entry predates the release that
|
|
352
|
+
began recording questions there is named instead.
|
|
352
353
|
|
|
353
354
|
- `--prerelease`: let prerelease versions take part in range matching.
|
|
354
355
|
- `--always-pull`: install a newer in-range version whenever one exists, rather than holding the lock.
|
|
@@ -409,10 +410,13 @@ Lists the recipes the trusted repositories publish, across every namespace, with
|
|
|
409
410
|
`namespace show` prints. Example: `sous recipe list --installed --latest`
|
|
410
411
|
|
|
411
412
|
### `sous recipe show REF`
|
|
412
|
-
Describes one recipe completely: its repository and location, every published version, its dependencies
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
413
|
+
Describes one recipe completely: its repository and location, every published version, its dependencies (the
|
|
414
|
+
manifest entry bringing each one in, such as a whole namespace, whether it is a co-subscription or a build
|
|
415
|
+
dependency, and the version it was released against), every recipe a subscription to it would install, every
|
|
416
|
+
question that would ask, laid out as `sous subscription add --dry-run` lays them out, and the directories its
|
|
417
|
+
files are written into. Everything but the files comes from the index, so a recipe nothing has installed yet
|
|
418
|
+
is described in full, offline. `REF` is `namespace/recipe`, a recipe name alone, or either with a
|
|
419
|
+
`repository:` qualifier. Example: `sous recipe show omakase/house`
|
|
416
420
|
|
|
417
421
|
## lock
|
|
418
422
|
|
|
@@ -151,19 +151,23 @@ depends:
|
|
|
151
151
|
- workflow/qa-helper # a sibling, released alongside me
|
|
152
152
|
- workflow/qa-helper@^1.1 # a sibling with a range; uncommon
|
|
153
153
|
- github://sous-io/sous-recipes/workflow/sat@^1.0 # another repository
|
|
154
|
-
- gitlab://gitlab.example.com/group/subgroup/proj
|
|
154
|
+
- gitlab://gitlab.example.com/group/subgroup/proj/-/qa/lint # a self-hosted host, with subgroups
|
|
155
|
+
- https://github.com/acme/recipes/tree/main/recipes/lint # a URL copied from the browser
|
|
155
156
|
subscribes:
|
|
156
157
|
- quality/code-review # co-subscribed: its files land too
|
|
157
158
|
```
|
|
158
159
|
|
|
159
160
|
A **bare ref** is a sibling in this repository; with no range it means "the version released alongside me", since
|
|
160
|
-
one run cuts both tags and records the exact version in the index. A
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
161
|
+
one run cuts both tags and records the exact version in the index. A location names another repository: a
|
|
162
|
+
locator, an HTTPS or SSH URL, or a URL copied from the browser. A ref is stored and printed as its published
|
|
163
|
+
**identity** (namespace and recipe), never as a folder path. Any spelling that settles to one identity is
|
|
164
|
+
accepted, including a folder path or a pasted browser URL: a recipe stored at `recipes/shared/sat/` and
|
|
165
|
+
published as `workflow/sat` may be written `github://owner/repo/workflow/sat` or
|
|
166
|
+
`https://github.com/owner/repo/tree/main/recipes/shared/sat`, and the release settles the second through the
|
|
167
|
+
other repository's index. The optional range after `@` follows npm's rules. `local://` locators and `repo:`
|
|
168
|
+
short names are refused: a local repository is a consumer's convenience, and a short name is one project's
|
|
169
|
+
private label. See [Dependencies named by location](repositories-file-formats.md#dependencies-named-by-location)
|
|
170
|
+
and every accepted spelling in [Ref forms](repositories-file-formats.md#ref-forms).
|
|
167
171
|
|
|
168
172
|
## Cut a release
|
|
169
173
|
|
|
@@ -239,9 +243,13 @@ this release's scope** is fine: the release depends on the last published versio
|
|
|
239
243
|
```
|
|
240
244
|
|
|
241
245
|
Each version's resolved dependencies are written into the index, so a consumer installing that version gets what
|
|
242
|
-
it was published with rather than ranges re-resolved months later.
|
|
243
|
-
|
|
244
|
-
|
|
246
|
+
it was published with rather than ranges re-resolved months later. Each dependency also records the manifest
|
|
247
|
+
entry that brought it in and whether it is a co-subscription or a build dependency, and each version records the
|
|
248
|
+
variable definitions its manifest publishes, so `sous recipe show` and `sous subscription add --dry-run` can say
|
|
249
|
+
what a recipe installs and asks before anything is fetched. A published version never changes: its content
|
|
250
|
+
hash, its dependencies and its variable definitions are carried forward exactly as published, and a
|
|
251
|
+
disagreement is an error telling you to bump rather than republish
|
|
252
|
+
([what is checked](repositories-file-formats.md#sousindexjson)). A version counts
|
|
245
253
|
as published once its tag exists, and the tags are the backstop, so a tagged version missing from the index is
|
|
246
254
|
rebuilt from it, dependencies included, whenever the index is regenerated, which only happens on a run that
|
|
247
255
|
publishes something. A bump edits the manifest in place, so comments, field order and layout survive; a folded
|
|
@@ -65,37 +65,57 @@ The cache can be behind what a repository has published since. Every one of thes
|
|
|
65
65
|
`sous recipe list --installed --latest` is the out-of-date view: every installed recipe, with
|
|
66
66
|
upstream's newest version beside the installed one.
|
|
67
67
|
|
|
68
|
-
Read `sous recipe show` before subscribing
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
Read `sous recipe show` before subscribing. It works offline, from the index alone: every published
|
|
69
|
+
version, what the version depends on (the manifest entry that brings each dependency in, whether it
|
|
70
|
+
is a co-subscription whose files land here or a build dependency, and the version it was released
|
|
71
|
+
against), every recipe a subscription would install, and every question that would ask. Only the
|
|
72
|
+
files it publishes wait until it is installed. A preset set, a recipe that only bundles others,
|
|
73
|
+
reads like this:
|
|
71
74
|
|
|
72
75
|
```term
|
|
73
|
-
$ sous recipe show
|
|
74
|
-
Latest version :
|
|
76
|
+
$ sous recipe show omakase/house
|
|
77
|
+
Latest version : 1.0.0
|
|
75
78
|
Pinned version : this project pins none
|
|
76
79
|
Subscribed : no
|
|
77
|
-
Dependency
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
Dependency Declared as Resolved to Repository Kind
|
|
81
|
+
--------------- ------------------------------ ----------- --------------- ----------------
|
|
82
|
+
tools/lint tools/lint 1.0.0 this repository build dependency
|
|
83
|
+
workflow/notes the whole 'workflow' namespace 1.0.0 this repository co-subscription
|
|
84
|
+
workflow/review the whole 'workflow' namespace 1.0.0 this repository co-subscription
|
|
85
|
+
➔ What subscribing asks you:
|
|
86
|
+
These recipes ask 3 questions, 3 of which nothing answers yet.
|
|
87
|
+
workflow/notes asks 1 question:
|
|
88
|
+
notesDir
|
|
89
|
+
about : Where the notes skill keeps its notes.
|
|
90
|
+
stored-as : SOUS_VAR_NOTES_DIR
|
|
91
|
+
answered : no, and this recipe requires an answer
|
|
82
92
|
```
|
|
83
93
|
|
|
94
|
+
?> A recipe released before sous recorded questions in the index is described from its files
|
|
95
|
+
instead, so its questions are listed once it is installed, and named as unknown until then.
|
|
96
|
+
|
|
84
97
|
## Subscribe to a recipe
|
|
85
98
|
|
|
86
99
|
```bash
|
|
87
100
|
sous subscription add workflow/qa-variables
|
|
88
101
|
sous subscription add workflow/qa-variables@^1.2.0
|
|
89
102
|
sous subscription add workflow # a whole namespace
|
|
103
|
+
sous subscription add https://github.com/acme/recipes/tree/main/recipes/workflow/alpha
|
|
90
104
|
```
|
|
91
105
|
|
|
106
|
+
A ref may be written in any form sous reads ([Ref forms](repositories-file-formats.md#ref-forms)),
|
|
107
|
+
including a URL copied from the browser. A location is matched to the trusted repository at it,
|
|
108
|
+
whatever this project calls that repository; one the project does not trust yet goes through the
|
|
109
|
+
trust question first, and nothing is fetched from it before the answer. The subscription is
|
|
110
|
+
recorded in its canonical form, `namespace/recipe`.
|
|
111
|
+
|
|
92
112
|
The whole dependency closure resolves before anything is downloaded, and only then is anything
|
|
93
113
|
written; an install is whole or not at all.
|
|
94
114
|
|
|
95
115
|
| Flag | What it does |
|
|
96
116
|
|------|--------------|
|
|
97
117
|
| `-y, --yes` | Answer yes to both questions this command can ask: the subscribe confirmation, and the trust question for a repository it has to add |
|
|
98
|
-
| `--accept-first` | When a
|
|
118
|
+
| `--accept-first` | When a ref matches several things, take the first one listed |
|
|
99
119
|
| `--answer <name>=<value>` | Answer one question ahead of time. Repeat it per answer, or read a whole file of them with `--answers-file <path>` |
|
|
100
120
|
| `--always-pull` | Install a newer in-range version whenever one exists, rather than holding the locked one |
|
|
101
121
|
| `--dry-run` | Print what would be installed, writing and downloading nothing |
|
|
@@ -199,8 +219,9 @@ stored value nothing reads. Names are camelCase, as the recipe declares them; th
|
|
|
199
219
|
`namespace/recipe.name` key works too when two recipes publish the same name, and everything after
|
|
200
220
|
the first `=` is the answer. `--answers-file answers.yaml` reads a YAML or JSON file of them.
|
|
201
221
|
|
|
202
|
-
?> A dry run downloads nothing
|
|
203
|
-
|
|
222
|
+
?> A dry run downloads nothing. A recipe your machine does not hold yet is described from the
|
|
223
|
+
questions its repository's index records; one released before sous recorded questions there cannot
|
|
224
|
+
be, so the run still succeeds and names it.
|
|
204
225
|
|
|
205
226
|
## See what you have
|
|
206
227
|
|
|
@@ -104,15 +104,60 @@ release itself is never restricted, so whatever publishes the recipe still relea
|
|
|
104
104
|
|
|
105
105
|
### Dependencies named by location
|
|
106
106
|
|
|
107
|
-
`depends` and `subscribes`
|
|
107
|
+
`depends` and `subscribes` name their targets by where they live: `workflow/qa-helper` is a sibling in this
|
|
108
108
|
same repository, and `github://sous-io/sous-recipes/workflow/sub-agent-delegation@^1.0` is a recipe in
|
|
109
|
-
another one.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
another one. Every spelling in [Ref forms](#ref-forms) that names a namespace or a recipe is accepted here,
|
|
110
|
+
except a `repo:` qualifier (one project's private name for a repository), a local path or `local://`
|
|
111
|
+
locator (a convenience, not a published location), and a location naming a whole repository with nothing
|
|
112
|
+
inside it. Names are recorded lowercase.
|
|
113
|
+
|
|
114
|
+
A ref is stored and printed as its published identity (namespace and recipe), never as a folder path. Any
|
|
115
|
+
spelling that settles to one identity is accepted, including a folder path or a pasted browser URL. Two
|
|
116
|
+
spellings need the other repository's index to settle: a GitLab URL with nested groups, which does not say
|
|
117
|
+
where the project path ends (`gitlab.com/a/b/c/d` may be project `a/b` naming the recipe `c/d`, or project
|
|
118
|
+
`a/b/c` naming the namespace `d`), and a browser URL, which names a folder. `sous repo release` settles each
|
|
119
|
+
by fetching the index of every candidate repository and keeping the reading whose index publishes what was
|
|
120
|
+
named, then records the repository it settled on in the index, beside each key the dependency reached. A
|
|
121
|
+
consumer reads that record and never probes. A network failure fails the release rather than guessing, and
|
|
122
|
+
two readings that both publish what is named fail it too, naming the spellings that read one way:
|
|
123
|
+
`gitlab://a/b/c/-/d/*` for the namespace, `gitlab://a/b/-/c/d` for the recipe.
|
|
124
|
+
|
|
125
|
+
### Ref forms
|
|
126
|
+
|
|
127
|
+
Every place a ref is written reads it with one parser, told where the ref came from. These forms are
|
|
128
|
+
recognized everywhere; the table after them says which each place allows. A refused form is an error saying
|
|
129
|
+
what to write there instead.
|
|
130
|
+
|
|
131
|
+
| Form | Example | Names |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| A bare name | `workflow` | a namespace (on the command line, also a recipe or anything else by that name) |
|
|
134
|
+
| Namespace and recipe | `workflow/alpha` | a recipe |
|
|
135
|
+
| Namespace, spelled out | `workflow/*` | a namespace |
|
|
136
|
+
| Repository-qualified | `sous-recipes:workflow/alpha` | a recipe in the repository this project calls `sous-recipes` |
|
|
137
|
+
| With a range | `workflow/alpha@^1.2` | a recipe, within an npm-style range |
|
|
138
|
+
| Provider-scheme locator | `github://owner/repo/workflow`, `.../workflow/*`, `.../workflow/alpha` | a namespace or a recipe in that repository |
|
|
139
|
+
| A host other than the provider's own | `gitlab://gitlab.example.com/group/proj/-/workflow/alpha` | the same, on a self-hosted instance |
|
|
140
|
+
| HTTPS URL | `https://github.com/owner/repo/workflow/alpha` | the same as the locator |
|
|
141
|
+
| URL with no scheme | `github.com/owner/repo/workflow/alpha` | the same |
|
|
142
|
+
| SSH remote | `git@github.com:owner/repo.git`, `ssh://git@github.com/owner/repo.git/workflow` | the repository, or something inside it |
|
|
143
|
+
| Browser URL | `https://github.com/owner/repo/tree/main/recipes/workflow/alpha` | the recipe whose index `path` holds that folder (or a file in it); a folder holding one namespace's recipes names the namespace |
|
|
144
|
+
| GitLab separator | `https://gitlab.com/group/sub/proj/-/tree/main/recipes/workflow/alpha` | the same, with `/-/` marking where the project path ends |
|
|
145
|
+
| GitLab nested group | `gitlab://group/sub/proj/workflow/alpha` | every reading the path allows, settled as described above |
|
|
146
|
+
|
|
147
|
+
A `.git` suffix on the repository is dropped in every URL form. GitLab's canonical locator always marks the
|
|
148
|
+
end of the project path with `/-/`, so it reads one way.
|
|
149
|
+
|
|
150
|
+
| Place | Allows |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| The command line | every form; names in any case |
|
|
153
|
+
| A config file's subscription keys | `namespace` or `namespace/recipe`, lowercase; the repository is recorded in the lockfile and a range in the entry's `range` field |
|
|
154
|
+
| A recipe manifest's `depends` and `subscribes` | every form naming a namespace or a recipe, except `repo:` and a local location |
|
|
155
|
+
| A key sous stores (the lockfile, the index, the store) | `namespace` or `namespace/recipe`, lowercase |
|
|
156
|
+
|
|
157
|
+
Matching tries the exact spelling first, then ignores case; a name that still matches several things is a
|
|
158
|
+
question, answered by `--accept-first` or by choosing. What sous writes (config layers, the lockfile, the
|
|
159
|
+
index) and prints is always the canonical form: `namespace/recipe`, qualified with the repository's short
|
|
160
|
+
name where one is needed.
|
|
116
161
|
|
|
117
162
|
### Variable definitions
|
|
118
163
|
|
|
@@ -156,8 +201,16 @@ fetches only this file; nothing more is downloaded until a project subscribes.
|
|
|
156
201
|
"namespaces": { "quality": { "description": "Recipes that exercise the edges." } },
|
|
157
202
|
"recipes": { "quality/qa-remote-dep": { "path": "recipes/quality/qa-remote-dep",
|
|
158
203
|
"versions": { "0.1.0": {
|
|
159
|
-
"dependencies": {
|
|
160
|
-
"workflow/
|
|
204
|
+
"dependencies": {
|
|
205
|
+
"workflow/qa-helper": { "version": "0.1.0", "declared": "workflow", "kind": "subscribes" },
|
|
206
|
+
"workflow/sub-agent-delegation": { "range": "^1.0", "repo": "github.com/sous-io/sous-recipes",
|
|
207
|
+
"declared": "github://sous-io/sous-recipes/workflow/sub-agent-delegation@^1.0",
|
|
208
|
+
"kind": "depends" } },
|
|
209
|
+
"depends": [ "github://sous-io/sous-recipes/workflow/sub-agent-delegation@^1.0" ],
|
|
210
|
+
"subscribes": [ "workflow" ],
|
|
211
|
+
"variables": [ { "name": "qaNotesDir", "type": "path", "prompt": "Where should notes go?",
|
|
212
|
+
"description": "The directory QA notes are written to.", "example": "docs/qa",
|
|
213
|
+
"default": ".sous/qa-notes", "required": true, "secret": false, "scope": "shared" } ],
|
|
161
214
|
"hash": "sha256-46e75442aeb368116c8830ad382707759f1ecd03974c4b5d42a5fabffce0324f",
|
|
162
215
|
"prerelease": false, "releasedAt": "2026-09-12T07:06:33.236Z",
|
|
163
216
|
"tag": "quality/qa-remote-dep@0.1.0"
|
|
@@ -171,12 +224,30 @@ fetches only this file; nothing more is downloaded until a project subscribes.
|
|
|
171
224
|
| `recipes` | top level | Keyed `namespace/recipe`; a recipe whose namespace is not declared is an error. Each entry carries `path` (the recipe folder), `description`, and `versions`, keyed by exact version |
|
|
172
225
|
| `hash`, `tag` | per version | The content hash verified after every fetch, and the tag, which must be exactly `namespace/recipe@version` so a version can never point at a branch |
|
|
173
226
|
| `prerelease`, `releasedAt`, `seeded` | per version | Whether ranges skip it unless a subscription opts in, when it was released, and whether it is the copy sous folds in from its own package |
|
|
227
|
+
| `dependencies` | per version | What the version was released against, keyed `namespace/recipe`; see below |
|
|
228
|
+
| `variables` | per version | The version's variable definitions, exactly as its manifest publishes them (see [Variable definitions](#variable-definitions)), with `required`, `secret` and `scope` filled in; `[]` when it asks nothing |
|
|
229
|
+
| `depends`, `subscribes` | per version | The version's manifest lists, exactly as written; `[]` when empty |
|
|
174
230
|
|
|
175
231
|
Dependencies are resolved at release time and keyed `namespace/recipe`. An entry carries `version` (a
|
|
176
232
|
sibling, resolved exactly) or `range` plus `repo` (the identity of the repository publishing it, whose own
|
|
177
233
|
index resolves the range); at least one is required. Installing a version installs these rather than
|
|
178
234
|
re-resolving the manifest's ranges.
|
|
179
235
|
|
|
236
|
+
**Each version describes itself.** So that a recipe can be described before anything is fetched, every
|
|
237
|
+
version a release records carries more than its dependencies. Each dependency records `declared`, the
|
|
238
|
+
manifest entry that brings it in exactly as written (a recipe such as `workflow/qa-helper@^1.0`, a whole
|
|
239
|
+
namespace such as `workflow`, or a locator), and `kind`: `subscribes` for a co-subscription, whose files
|
|
240
|
+
land and whose questions are asked, or `depends` for a build dependency. When several entries cover one
|
|
241
|
+
recipe, an entry naming it wins over a namespace, and it is a co-subscription when any entry covering it is
|
|
242
|
+
one. The version records `variables`, its manifest's definitions, and `depends` and `subscribes`, its
|
|
243
|
+
manifest's lists as written; the lists are what a consumer walks, because a namespace of another repository
|
|
244
|
+
has no recipe key to record it under. All of them are written even when empty (`"dependencies": {}`,
|
|
245
|
+
`"variables": []`), so "none" reads differently from "not recorded". From these, `sous recipe show` and
|
|
246
|
+
`sous subscription add --dry-run` list everything a subscription installs and every question it asks, and
|
|
247
|
+
the resolver walks a recipe whose files are not on the machine from its index entry just as it would walk
|
|
248
|
+
its manifest. A version recorded before a release wrote these fields keeps its entry as it is; its questions
|
|
249
|
+
are known only once its files are fetched, and the dry run names it as such.
|
|
250
|
+
|
|
180
251
|
**A published version's entry is frozen.** Its dependencies are resolved once, when the version is first
|
|
181
252
|
recorded, and every later release carries the entry forward exactly as published, like its hash; releasing
|
|
182
253
|
other recipes, adding a recipe to a namespace it subscribes to, or raising a sibling it names changes nothing
|
|
@@ -189,9 +260,13 @@ against the repository as it stood at that tag, then frozen the same way. An ent
|
|
|
189
260
|
recorded dependencies carries none, stays that way, and installs by resolving its manifest's ranges.
|
|
190
261
|
|
|
191
262
|
**Fields this sous does not know are kept, not refused.** A later sous may add fields to the index, at any
|
|
192
|
-
level. This one reads such an index, validates every field it
|
|
193
|
-
rest; a release it runs carries a published version's entry
|
|
194
|
-
|
|
263
|
+
level, recorded variable definitions included. This one reads such an index, validates every field it
|
|
264
|
+
defines as strictly as before, and ignores the rest; a release it runs carries a published version's entry
|
|
265
|
+
forward with those fields intact.
|
|
266
|
+
|
|
267
|
+
!> A sous older than 0.2.26, the first to tolerate unknown fields, refuses the whole index of any
|
|
268
|
+
repository released with `declared`, `kind`, `variables`, `depends` or `subscribes` in it, the official
|
|
269
|
+
one included. A project pinned to such a sous fails to read that index until it moves to 0.2.26 or later.
|
|
195
270
|
|
|
196
271
|
**The `seeded` field.** Sous ships the `core` recipe in its own npm package, so a project can build before
|
|
197
272
|
reaching the network. That copy is folded into the official repository's index in memory and resolved like
|
|
@@ -128,12 +128,14 @@ Canonicalizing also produces the two clone URLs sous hands to git, `https://<hos
|
|
|
128
128
|
and `git@<host>:<owner>/<name>.git`. For the `local` provider the HTTPS slot carries the
|
|
129
129
|
absolute directory, because that is what git is given when a recipe is fetched.
|
|
130
130
|
|
|
131
|
-
?> A published manifest names a dependency in another repository by a locator
|
|
132
|
-
scheme is the provider's identifier: `github://sous-io/sous-recipes/workflow/sat@^1.1`. A
|
|
133
|
-
segment containing a dot is read as the host, so `gitlab://gitlab.example.com/group/project
|
|
134
|
-
addresses a self-hosted instance; without one, the provider's public host is assumed.
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
?> A published manifest names a dependency in another repository by location, such as a locator
|
|
132
|
+
whose scheme is the provider's identifier: `github://sous-io/sous-recipes/workflow/sat@^1.1`. A
|
|
133
|
+
first segment containing a dot is read as the host, so `gitlab://gitlab.example.com/group/project/-/ns/recipe`
|
|
134
|
+
addresses a self-hosted instance; without one, the provider's public host is assumed. Where a
|
|
135
|
+
repository path ends, and what a browser URL's path looks like, is each provider's own rule (its
|
|
136
|
+
`readLocation`), so a URL copied from either host's file view works too. `local://` is refused,
|
|
137
|
+
because a path on your disk is not a published location. Every form is in
|
|
138
|
+
[Ref forms](repositories-file-formats.md#ref-forms).
|
|
137
139
|
|
|
138
140
|
## Private repositories
|
|
139
141
|
|
|
@@ -108,8 +108,14 @@ providers sous ships named (see [Providers](repositories-providers.md)). The leg
|
|
|
108
108
|
depends:
|
|
109
109
|
- workflow/sat # a sibling in this repository
|
|
110
110
|
- github://acme/recipes/workflow/sat@^1.1 # a recipe in another repository
|
|
111
|
+
- https://github.com/acme/recipes/tree/main/sat # the same, copied from the browser
|
|
111
112
|
```
|
|
112
113
|
|
|
114
|
+
Every accepted spelling is in [Ref forms](repositories-file-formats.md#ref-forms). A GitLab URL
|
|
115
|
+
with nested groups, or a browser URL, is settled by `sous repo release` through the other
|
|
116
|
+
repository's index; when that index cannot be read, the release stops and names it, and when two
|
|
117
|
+
readings both publish what was named, it names the `/-/` and `/*` spellings that read one way.
|
|
118
|
+
|
|
113
119
|
While developing both sides, use
|
|
114
120
|
[`sous repo link`](repositories-authoring.md#edit-a-repository-in-place); it redirects resolution
|
|
115
121
|
at a working copy without changing what you publish.
|
|
@@ -12,7 +12,10 @@ a separate, ceremony-free system; see [Variables](config-variables.md). The two
|
|
|
12
12
|
|
|
13
13
|
A definition ships inside the recipe that needs it, in its manifest's `variables:` array, so it versions,
|
|
14
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
|
+
[Variable definitions](repositories-file-formats.md#variable-definitions). `sous repo release` also copies
|
|
16
|
+
each version's definitions into the repository's index, which is how `sous recipe show` and
|
|
17
|
+
`sous subscription add --dry-run` list a recipe's questions before its files are on your machine. The index
|
|
18
|
+
holds definitions only, never an answer; answers come from the ladder below and nowhere else.
|
|
16
19
|
|
|
17
20
|
A definition is in play when your lockfile pins the recipe that declares it, whether you subscribed to that
|
|
18
21
|
recipe or a dependency pulled it in. A recipe the store does not hold yet contributes nothing rather than
|
|
@@ -241,10 +244,17 @@ sous vars ask SOUS_VAR_TASK_FILE_ROOT # the same one, by a name in use
|
|
|
241
244
|
sous vars ask workflow/task-files.taskFileRoot # the same one, spelled out in full
|
|
242
245
|
sous vars ask task-files # every question one recipe asks
|
|
243
246
|
sous vars ask sous-recipes # every question that repository's recipes ask
|
|
247
|
+
sous vars ask https://github.com/sous-io/sous-recipes/tree/main/recipes/workflow/task-files
|
|
248
|
+
# the same recipe, as a URL copied from the browser
|
|
244
249
|
```
|
|
245
250
|
|
|
246
|
-
|
|
247
|
-
|
|
251
|
+
A repository, namespace or recipe may be written in any form in
|
|
252
|
+
[Ref forms](repositories-file-formats.md#ref-forms), including a location: it is matched to the
|
|
253
|
+
trusted repository at it and settled through that repository's cached index, then narrowed to the
|
|
254
|
+
variables it covers. `--repo` and `--namespace` accept a location the same way.
|
|
255
|
+
|
|
256
|
+
Matching tries the exact spelling first, then ignores case; a name that still matches several things is a
|
|
257
|
+
question. An environment variable name resolves when a definition declares it or an env file sets it. `--repo`, `--namespace` and `--var` say which kind is meant, each narrowing what the one
|
|
248
258
|
before left, so `sous vars ask --namespace workflow apiUrl` asks about that namespace's `apiUrl` alone. When
|
|
249
259
|
a name matches several things, sous lists them and asks; `--accept-first` takes the first, and a run with no
|
|
250
260
|
terminal fails naming it.
|
|
@@ -101,9 +101,9 @@ subscribes:
|
|
|
101
101
|
- quality/code-review
|
|
102
102
|
```
|
|
103
103
|
|
|
104
|
-
Both name their targets by location, written as a **locator URL**
|
|
105
|
-
([the
|
|
106
|
-
|
|
104
|
+
Both name their targets by location, written as a **locator URL** or any other URL form
|
|
105
|
+
([the forms](repositories-file-formats.md#ref-forms)) when the target lives in another repository,
|
|
106
|
+
never by a short name a consuming project chose; a curated bundle is simply
|
|
107
107
|
a recipe made mostly of `subscribes` entries. Because both lists are
|
|
108
108
|
declarative YAML rather than code, sous can read the whole dependency closure before fetching any
|
|
109
109
|
of it, which is what makes the trust decision answerable up front.
|
package/package.json
CHANGED
|
@@ -138,13 +138,17 @@ Both lists name other recipes, and they do different things:
|
|
|
138
138
|
- `subscribes` is a **co-subscription**. The other recipe's files land and its questions are asked,
|
|
139
139
|
as if the project had subscribed to it too.
|
|
140
140
|
|
|
141
|
-
Each entry names its target by where it lives
|
|
142
|
-
|
|
143
|
-
- a bare `namespace/recipe` for a sibling in the same repository.
|
|
144
|
-
version released alongside this one; an `@` range (npm's rules) is
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
Each entry names its target by where it lives:
|
|
142
|
+
|
|
143
|
+
- a bare `namespace/recipe` (or `namespace` for all of it) for a sibling in the same repository.
|
|
144
|
+
With no range it means the version released alongside this one; an `@` range (npm's rules) is
|
|
145
|
+
allowed but uncommon.
|
|
146
|
+
- a location for a recipe in another repository, such as
|
|
147
|
+
`github://example-org/team-recipes/review/wording@^1.0`, an HTTPS or SSH URL, or a URL copied
|
|
148
|
+
from the browser. A ref is stored and printed as its published identity (namespace and recipe),
|
|
149
|
+
never as a folder path. Any spelling that settles to one identity is accepted, including a
|
|
150
|
+
folder path or a pasted browser URL; `sous repo release` settles one that needs the other
|
|
151
|
+
repository's index, and fails rather than guess.
|
|
148
152
|
|
|
149
153
|
`local://` locators and `repo:` short names are refused: both are one project's private view of a
|
|
150
154
|
repository, and a published manifest is read everywhere.
|
|
@@ -65,10 +65,13 @@ contribute: https://github.com/harbor-labs/team-recipes/blob/main/CONTRIBUTING.m
|
|
|
65
65
|
A recipe's version lives in its manifest, which is the source of truth. A version is **published**
|
|
66
66
|
once a git tag shaped `namespace/recipe@version` exists for it (for example
|
|
67
67
|
`review/checklist@1.2.0`). The index records each published version with its content hash, its
|
|
68
|
-
tag
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
tag, the exact versions of its dependencies at release time (each with the manifest entry that
|
|
69
|
+
brought it in and whether it is a co-subscription or a build dependency) and the variable
|
|
70
|
+
definitions it publishes, which is how a project lists what a recipe installs and asks before
|
|
71
|
+
fetching it; for the length of one release commit it also records the version each manifest
|
|
72
|
+
declares, whose tag that release cuts next. A published version never changes: new files need a
|
|
73
|
+
new version, and a release refuses to publish different content under a version already tagged. A
|
|
74
|
+
lost index is rebuilt from the tags.
|
|
72
75
|
|
|
73
76
|
## Releasing
|
|
74
77
|
|
|
@@ -24,7 +24,7 @@ import { recipeFilesDirectory } from "../../lib/repos/catalog-inputs.js";
|
|
|
24
24
|
import { readRecipeManifestIn } from "../../lib/repos/locked-recipes.js";
|
|
25
25
|
import { createEmptyLockfile } from "../../lib/repos/formats/lockfile.js";
|
|
26
26
|
import type { LockRepoInput } from "../../lib/repos/lock-service.js";
|
|
27
|
-
import {
|
|
27
|
+
import { RefSource, parseShortRef } from "../../lib/refs/index.js";
|
|
28
28
|
import {
|
|
29
29
|
PROJECT_REQUESTER,
|
|
30
30
|
resolveRefs,
|
|
@@ -133,7 +133,7 @@ export default class LockRebuild extends BaseCommand {
|
|
|
133
133
|
.sort()
|
|
134
134
|
.map((key) => {
|
|
135
135
|
const entry = subscriptions[key]!;
|
|
136
|
-
const parsed =
|
|
136
|
+
const parsed = parseShortRef(key, RefSource.Config);
|
|
137
137
|
return {
|
|
138
138
|
ref: {
|
|
139
139
|
...parsed,
|
|
@@ -150,7 +150,8 @@ export default class LockRebuild extends BaseCommand {
|
|
|
150
150
|
repos,
|
|
151
151
|
// A recipe's own manifest is what names its dependencies, and it is read
|
|
152
152
|
// from the files this machine already holds. Nothing is fetched here, so a
|
|
153
|
-
// recipe that is not in the store yet
|
|
153
|
+
// recipe that is not in the store yet is walked from what its index entry
|
|
154
|
+
// records, and comes back under `missingManifests` when that is nothing.
|
|
154
155
|
loadManifest: (recipe) => {
|
|
155
156
|
const directory = recipeFilesDirectory({
|
|
156
157
|
service,
|
|
@@ -208,9 +209,9 @@ export default class LockRebuild extends BaseCommand {
|
|
|
208
209
|
if (result.missingManifests.length > 0) {
|
|
209
210
|
blankLine();
|
|
210
211
|
paragraph(
|
|
211
|
-
`The files of these recipes are not on this machine,
|
|
212
|
-
`
|
|
213
|
-
|
|
212
|
+
`The files of these recipes are not on this machine, and their repository's ` +
|
|
213
|
+
`index does not record what they depend on, so their own dependencies are ` +
|
|
214
|
+
`not in the rebuilt lockfile: ${result.missingManifests.join(", ")}.`
|
|
214
215
|
);
|
|
215
216
|
}
|
|
216
217
|
|