@sous-io/sous 0.2.29 → 0.2.31
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/README.md +12 -0
- package/docs/markdown/README.md +52 -0
- package/docs/markdown/_sidebar.md +2 -0
- package/docs/markdown/commands.md +31 -6
- package/docs/markdown/repositories-authoring.md +7 -3
- package/docs/markdown/repositories-consuming.md +26 -12
- package/docs/markdown/repositories-file-formats.md +35 -5
- package/docs/markdown/repositories-variables.md +4 -1
- package/package.json +2 -1
- 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/base-command.ts +75 -2
- package/src/commands/lock/rebuild.ts +5 -4
- package/src/commands/recipe/show.ts +163 -64
- package/src/commands/update.ts +178 -0
- package/src/lib/project-install.d.mts +4 -0
- package/src/lib/project-install.mjs +46 -8
- package/src/lib/repos/catalog.ts +43 -55
- 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 +40 -1
- package/src/lib/repos/formats/lockfile.ts +5 -3
- package/src/lib/repos/formats/recipe-manifest.ts +6 -204
- package/src/lib/repos/formats/variable-definition.ts +268 -0
- package/src/lib/repos/providers/http.ts +37 -16
- package/src/lib/repos/release/index-builder.ts +168 -32
- package/src/lib/repos/resolver.ts +53 -12
- package/src/lib/repos/subscription-service.ts +93 -11
- package/src/lib/repos/update-plan.ts +3 -2
- package/src/lib/self-update/index.ts +14 -0
- package/src/lib/self-update/installs.ts +412 -0
- package/src/lib/self-update/managers.ts +126 -0
- package/src/lib/self-update/plan.ts +315 -0
- package/src/lib/self-update/registry.ts +99 -0
- package/src/lib/self-update/run.ts +506 -0
- package/src/lib/self-update/versions.ts +243 -0
- package/src/lib/vars/question-plan.ts +44 -21
package/src/lib/repos/catalog.ts
CHANGED
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
* This module answers all four.
|
|
9
9
|
*
|
|
10
10
|
* Everything here is a pure function over data the caller hands it: the cached
|
|
11
|
-
* indexes, the lockfile, and the subscription keys the project declares.
|
|
12
|
-
* recipe
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
11
|
+
* indexes, the lockfile, and the subscription keys the project declares. The
|
|
12
|
+
* content a recipe contributes is known only from its own manifest, inside the
|
|
13
|
+
* recipe's files, so the caller supplies a reader for it; a recipe whose files
|
|
14
|
+
* are not on this machine is described from its index alone rather than being
|
|
15
|
+
* an error, and an index recorded by a current release says how each of its
|
|
16
|
+
* dependencies was declared.
|
|
16
17
|
*
|
|
17
18
|
* Refs resolve the way `sous subscribe` resolves them: `namespace`,
|
|
18
19
|
* `namespace/recipe`, and either of those qualified with `repo:`. A one-word ref
|
|
@@ -24,11 +25,7 @@ import semver from "semver";
|
|
|
24
25
|
import { ConfigError } from "../errors.js";
|
|
25
26
|
import type { IndexDependency, IndexFile } from "./formats/index-file.js";
|
|
26
27
|
import type { Lockfile } from "./formats/lockfile.js";
|
|
27
|
-
import type {
|
|
28
|
-
ContentKind,
|
|
29
|
-
RecipeManifest,
|
|
30
|
-
VariableDefinition,
|
|
31
|
-
} from "./formats/recipe-manifest.js";
|
|
28
|
+
import type { ContentKind, RecipeManifest } from "./formats/recipe-manifest.js";
|
|
32
29
|
import {
|
|
33
30
|
isNamedReading,
|
|
34
31
|
namespaceOfKey,
|
|
@@ -38,7 +35,7 @@ import {
|
|
|
38
35
|
type ParsedRef,
|
|
39
36
|
} from "../refs/parse.js";
|
|
40
37
|
import { settleDependency } from "../refs/settle.js";
|
|
41
|
-
import {
|
|
38
|
+
import { declarationFor, type DependencyLists } from "./declarations.js";
|
|
42
39
|
import {
|
|
43
40
|
describeReference,
|
|
44
41
|
findNamespace,
|
|
@@ -174,9 +171,13 @@ export type RecipeVersionListing = {
|
|
|
174
171
|
export type RecipeDependencyListing = {
|
|
175
172
|
/** The recipe key the dependency names, `namespace/recipe`. */
|
|
176
173
|
key: string;
|
|
177
|
-
/**
|
|
174
|
+
/**
|
|
175
|
+
* The manifest entry that brings it in, exactly as written (a recipe, a whole
|
|
176
|
+
* namespace, or a locator), from the index when it records one and from the
|
|
177
|
+
* manifest otherwise.
|
|
178
|
+
*/
|
|
178
179
|
declared?: string;
|
|
179
|
-
/** Whether
|
|
180
|
+
/** Whether that entry makes it a build dependency or a co-subscription. */
|
|
180
181
|
kind?: "depends" | "subscribes";
|
|
181
182
|
/** The exact version the release resolved it to, when the index records one. */
|
|
182
183
|
resolvedVersion?: string;
|
|
@@ -186,22 +187,6 @@ export type RecipeDependencyListing = {
|
|
|
186
187
|
repo?: string;
|
|
187
188
|
};
|
|
188
189
|
|
|
189
|
-
/** One variable a recipe asks about. */
|
|
190
|
-
export type RecipeVariableListing = {
|
|
191
|
-
/** The variable's name, as the manifest declares it. */
|
|
192
|
-
name: string;
|
|
193
|
-
/** The type of answer it takes. */
|
|
194
|
-
type: string;
|
|
195
|
-
/** The environment variable an answer is stored under. */
|
|
196
|
-
env: string;
|
|
197
|
-
/** True when an answer is required before the recipe is usable. */
|
|
198
|
-
required: boolean;
|
|
199
|
-
/** True when the answer is a secret, which sous never prints. */
|
|
200
|
-
secret: boolean;
|
|
201
|
-
/** The one-line question it asks. */
|
|
202
|
-
prompt: string;
|
|
203
|
-
};
|
|
204
|
-
|
|
205
190
|
/** One content kind a recipe contributes, and where its files would land. */
|
|
206
191
|
export type RecipeContentListing = {
|
|
207
192
|
/** The content kind, as the manifest declares it. */
|
|
@@ -246,14 +231,18 @@ export type RecipeDetail = {
|
|
|
246
231
|
versions: RecipeVersionListing[];
|
|
247
232
|
/** What the described version depends on, by key. */
|
|
248
233
|
dependencies: RecipeDependencyListing[];
|
|
249
|
-
/**
|
|
250
|
-
|
|
234
|
+
/**
|
|
235
|
+
* True when the index records the described version's dependencies, even as
|
|
236
|
+
* none at all, so an empty list means "depends on nothing" rather than "not
|
|
237
|
+
* recorded".
|
|
238
|
+
*/
|
|
239
|
+
dependenciesRecorded: boolean;
|
|
251
240
|
/** What the recipe contributes, and where each kind's files land. */
|
|
252
241
|
contents: RecipeContentListing[];
|
|
253
242
|
/**
|
|
254
243
|
* True when the recipe's own manifest could be read. When it is false, the
|
|
255
|
-
*
|
|
256
|
-
*
|
|
244
|
+
* contents are unknown rather than empty, because the recipe's files are not
|
|
245
|
+
* on this machine.
|
|
257
246
|
*/
|
|
258
247
|
manifestRead: boolean;
|
|
259
248
|
};
|
|
@@ -368,7 +357,7 @@ export function describeRecipe(inputs: CatalogInputs, ref: string): RecipeDetail
|
|
|
368
357
|
...(describing === undefined ? {} : { describing }),
|
|
369
358
|
versions: versionListings(entry.versions, listing.latest, listing.pinned),
|
|
370
359
|
dependencies: dependencyListings(published?.dependencies, manifest),
|
|
371
|
-
|
|
360
|
+
dependenciesRecorded: published?.dependencies !== undefined,
|
|
372
361
|
contents: (manifest?.contents ?? []).map((content) => ({
|
|
373
362
|
kind: content.kind,
|
|
374
363
|
include: [...content.include],
|
|
@@ -680,21 +669,39 @@ function dependencyListings(
|
|
|
680
669
|
manifest: RecipeManifest | undefined
|
|
681
670
|
): RecipeDependencyListing[] {
|
|
682
671
|
const rows = new Map<string, RecipeDependencyListing>();
|
|
672
|
+
const lists: DependencyLists | undefined =
|
|
673
|
+
manifest === undefined
|
|
674
|
+
? undefined
|
|
675
|
+
: { depends: manifest.depends ?? [], subscribes: manifest.subscribes ?? [] };
|
|
683
676
|
|
|
684
677
|
for (const [key, entry] of Object.entries(resolved ?? {})) {
|
|
678
|
+
// The index says how the release that published the version saw it. An
|
|
679
|
+
// entry recorded before the index said so is answered from the manifest,
|
|
680
|
+
// by the same rule the release applies.
|
|
681
|
+
const declaration =
|
|
682
|
+
entry.declared !== undefined && entry.kind !== undefined
|
|
683
|
+
? { declared: entry.declared, kind: entry.kind }
|
|
684
|
+
: lists === undefined
|
|
685
|
+
? undefined
|
|
686
|
+
: declarationFor(lists, key, resolved);
|
|
685
687
|
rows.set(key, {
|
|
686
688
|
key,
|
|
687
689
|
...(entry.version === undefined ? {} : { resolvedVersion: entry.version }),
|
|
688
690
|
...(entry.range === undefined ? {} : { resolvedRange: entry.range }),
|
|
689
691
|
...(entry.repo === undefined ? {} : { repo: entry.repo }),
|
|
692
|
+
...(declaration ?? {}),
|
|
690
693
|
});
|
|
691
694
|
}
|
|
692
695
|
|
|
696
|
+
// A manifest entry that covers no row the index recorded is still shown, so a
|
|
697
|
+
// reader sees everything the manifest declares.
|
|
698
|
+
const covered = new Set([...rows.values()].map((row) => row.declared));
|
|
693
699
|
for (const [kind, declared] of [
|
|
694
|
-
["depends",
|
|
695
|
-
["subscribes",
|
|
700
|
+
["depends", lists?.depends ?? []],
|
|
701
|
+
["subscribes", lists?.subscribes ?? []],
|
|
696
702
|
] as Array<["depends" | "subscribes", string[]]>) {
|
|
697
703
|
for (const written of declared) {
|
|
704
|
+
if (covered.has(written.trim())) continue;
|
|
698
705
|
// A dependency that reads more than one way is listed under the key the
|
|
699
706
|
// index recorded for it. One sous cannot parse or settle is still worth
|
|
700
707
|
// showing; it is listed under what it was written as, so the reader sees
|
|
@@ -715,25 +722,6 @@ function dependencyListings(
|
|
|
715
722
|
return [...rows.values()].sort((left, right) => compare(left.key, right.key));
|
|
716
723
|
}
|
|
717
724
|
|
|
718
|
-
/**
|
|
719
|
-
* One variable row: what it is called, what kind of answer it takes, and the
|
|
720
|
-
* environment variable an answer is stored under.
|
|
721
|
-
*
|
|
722
|
-
* @param definition - The variable definition from the recipe's manifest.
|
|
723
|
-
*/
|
|
724
|
-
function variableListing(definition: VariableDefinition): RecipeVariableListing {
|
|
725
|
-
return {
|
|
726
|
-
name: definition.name,
|
|
727
|
-
type: definition.type,
|
|
728
|
-
// The manifest may bind an existing environment variable; otherwise the
|
|
729
|
-
// answer is stored under the name sous derives from the variable's own.
|
|
730
|
-
env: bareName(definition),
|
|
731
|
-
required: definition.required,
|
|
732
|
-
secret: definition.secret,
|
|
733
|
-
prompt: definition.prompt,
|
|
734
|
-
};
|
|
735
|
-
}
|
|
736
|
-
|
|
737
725
|
/**
|
|
738
726
|
* The repository a match named. Every match comes from this catalog's own
|
|
739
727
|
* repositories, so the lookup always finds one.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How one recipe brings in another: which entry of its manifest names it, and
|
|
3
|
+
* whether that entry is a co-subscription or a build dependency.
|
|
4
|
+
*
|
|
5
|
+
* A manifest lists what it brings in under `depends` and `subscribes`, and one
|
|
6
|
+
* recipe can be covered by more than one entry: `subscribes: [workflow]` covers
|
|
7
|
+
* every recipe of the `workflow` namespace, and `depends: [workflow/task-files]`
|
|
8
|
+
* names one of them again. Three places need one answer per recipe, and they
|
|
9
|
+
* must agree: a release recording the answer in the index, `sous recipe show`
|
|
10
|
+
* describing a recipe whose manifest it can read, and the resolver walking a
|
|
11
|
+
* recipe whose manifest it cannot. This module is that one answer, and its
|
|
12
|
+
* reverse.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { DependencyKind } from "./formats/common.js";
|
|
16
|
+
import type { IndexVersion } from "./formats/index-file.js";
|
|
17
|
+
import { isNamedReading, namespaceOfKey, parseRef, refKey } from "../refs/parse.js";
|
|
18
|
+
import { RefSource } from "../refs/scopes.js";
|
|
19
|
+
import { settleDependency, type RecordedDependencies } from "../refs/settle.js";
|
|
20
|
+
|
|
21
|
+
/** A manifest's two dependency lists, each entry exactly as it was written. */
|
|
22
|
+
export type DependencyLists = {
|
|
23
|
+
/** Build dependencies: fetched and addressable, never in the project's output. */
|
|
24
|
+
depends: string[];
|
|
25
|
+
/** Co-subscriptions: their files land and their questions are asked. */
|
|
26
|
+
subscribes: string[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** How one recipe is brought in by the manifest that declares it. */
|
|
30
|
+
export type DependencyDeclaration = {
|
|
31
|
+
/**
|
|
32
|
+
* The manifest entry that brings it in, as written. An entry naming the
|
|
33
|
+
* recipe wins over a namespace entry that also covers it; between two
|
|
34
|
+
* entries naming it, the later one (in `depends`, then `subscribes` order)
|
|
35
|
+
* wins, and between two namespace entries, the first.
|
|
36
|
+
*/
|
|
37
|
+
declared: string;
|
|
38
|
+
/** `subscribes` when any entry covering the recipe is a co-subscription. */
|
|
39
|
+
kind: DependencyKind;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** A declaration being gathered, entry by entry. */
|
|
43
|
+
export type GatheredDeclaration = DependencyDeclaration & {
|
|
44
|
+
/** True when an entry named the recipe itself rather than its namespace. */
|
|
45
|
+
named: boolean;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/** One manifest entry covering a recipe. */
|
|
49
|
+
export type CoveringEntry = {
|
|
50
|
+
/** The entry as written. */
|
|
51
|
+
written: string;
|
|
52
|
+
/** The list it sits in. */
|
|
53
|
+
kind: DependencyKind;
|
|
54
|
+
/** True when it names the recipe itself rather than a namespace holding it. */
|
|
55
|
+
named: boolean;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Folds one more manifest entry covering a recipe into what is known about the
|
|
60
|
+
* recipe's declaration, by the rule `DependencyDeclaration` states. Every
|
|
61
|
+
* reader applies this one function, entry by entry in manifest order
|
|
62
|
+
* (`depends` first, then `subscribes`).
|
|
63
|
+
*
|
|
64
|
+
* foldDeclaration(undefined, { written: "workflow", kind: "subscribes", named: false })
|
|
65
|
+
* // -> { declared: "workflow", kind: "subscribes", named: false }
|
|
66
|
+
*
|
|
67
|
+
* @param before - What the earlier entries said, when any covered the recipe.
|
|
68
|
+
* @param entry - The next entry covering it.
|
|
69
|
+
*/
|
|
70
|
+
export function foldDeclaration(
|
|
71
|
+
before: GatheredDeclaration | undefined,
|
|
72
|
+
entry: CoveringEntry
|
|
73
|
+
): GatheredDeclaration {
|
|
74
|
+
const keepBefore = before !== undefined && !entry.named;
|
|
75
|
+
return {
|
|
76
|
+
declared: keepBefore ? before.declared : entry.written.trim(),
|
|
77
|
+
kind: before?.kind === "subscribes" || entry.kind === "subscribes" ? "subscribes" : "depends",
|
|
78
|
+
named: entry.named || before?.named === true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The declaration that brings one recipe in, or undefined when no entry of the
|
|
84
|
+
* lists covers it. An entry that reads more than one way is settled from what
|
|
85
|
+
* the version's index entry recorded, and one that cannot be settled, or does
|
|
86
|
+
* not parse, covers nothing.
|
|
87
|
+
*
|
|
88
|
+
* declarationFor({ depends: ["workflow/a"], subscribes: ["workflow"] }, "workflow/a")
|
|
89
|
+
* // -> { declared: "workflow/a", kind: "subscribes" }
|
|
90
|
+
*
|
|
91
|
+
* @param lists - The declaring recipe's `depends` and `subscribes` lists.
|
|
92
|
+
* @param key - The recipe being asked about, `namespace/recipe`.
|
|
93
|
+
* @param recorded - What the declaring version's index entry records, for settling.
|
|
94
|
+
*/
|
|
95
|
+
export function declarationFor(
|
|
96
|
+
lists: Partial<DependencyLists>,
|
|
97
|
+
key: string,
|
|
98
|
+
recorded?: RecordedDependencies
|
|
99
|
+
): DependencyDeclaration | undefined {
|
|
100
|
+
let gathered: GatheredDeclaration | undefined;
|
|
101
|
+
|
|
102
|
+
for (const [kind, entries] of [
|
|
103
|
+
["depends", lists.depends ?? []],
|
|
104
|
+
["subscribes", lists.subscribes ?? []],
|
|
105
|
+
] as Array<[DependencyKind, string[]]>) {
|
|
106
|
+
for (const written of entries) {
|
|
107
|
+
let reading;
|
|
108
|
+
try {
|
|
109
|
+
reading = settleDependency(written, recorded === undefined ? {} : { recorded });
|
|
110
|
+
} catch {
|
|
111
|
+
// An entry that does not parse is reported wherever the manifest is
|
|
112
|
+
// validated; here it simply covers nothing.
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (reading === undefined || !isNamedReading(reading)) continue;
|
|
116
|
+
|
|
117
|
+
const named = reading.recipe !== undefined;
|
|
118
|
+
const covers = named ? refKey(reading) === key : namespaceOfKey(key) === reading.namespace;
|
|
119
|
+
if (covers) gathered = foldDeclaration(gathered, { written, kind, named });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return gathered === undefined ? undefined : { declared: gathered.declared, kind: gathered.kind };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The manifest lists a version's index entry records, or undefined when it
|
|
128
|
+
* records neither (a version recorded before the index described recipes).
|
|
129
|
+
* That is what lets the resolver walk a recipe whose files are not on this
|
|
130
|
+
* machine exactly as it would walk its manifest.
|
|
131
|
+
*
|
|
132
|
+
* indexDependencyLists({ subscribes: ["workflow"], depends: [] , ... })
|
|
133
|
+
* // -> { depends: [], subscribes: ["workflow"] }
|
|
134
|
+
*
|
|
135
|
+
* @param version - The version's index entry.
|
|
136
|
+
*/
|
|
137
|
+
export function indexDependencyLists(
|
|
138
|
+
version: Pick<IndexVersion, "depends" | "subscribes">
|
|
139
|
+
): DependencyLists | undefined {
|
|
140
|
+
if (version.depends === undefined && version.subscribes === undefined) return undefined;
|
|
141
|
+
return { depends: [...(version.depends ?? [])], subscribes: [...(version.subscribes ?? [])] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Says how a declaration brings a recipe in, the way a person would: a whole
|
|
146
|
+
* namespace is named as one, and anything else is shown as it was written.
|
|
147
|
+
*
|
|
148
|
+
* describeDeclaration("workflow") // -> "the whole 'workflow' namespace"
|
|
149
|
+
* describeDeclaration("workflow/task-files") // -> "workflow/task-files"
|
|
150
|
+
*
|
|
151
|
+
* @param declared - The manifest entry, as written.
|
|
152
|
+
*/
|
|
153
|
+
export function describeDeclaration(declared: string): string {
|
|
154
|
+
try {
|
|
155
|
+
const readings = parseRef(declared, RefSource.Manifest);
|
|
156
|
+
const only = readings[0]!;
|
|
157
|
+
if (readings.length === 1 && isNamedReading(only) && only.recipe === undefined) {
|
|
158
|
+
return only.location === undefined
|
|
159
|
+
? `the whole '${only.namespace}' namespace`
|
|
160
|
+
: `the whole '${only.namespace}' namespace of ${only.location.identity}`;
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
// Shown as it was written; the reader sees the bad entry.
|
|
164
|
+
}
|
|
165
|
+
return declared;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Plain-language wording for a dependency kind.
|
|
170
|
+
*
|
|
171
|
+
* @param kind - The kind, when it is known.
|
|
172
|
+
*/
|
|
173
|
+
export function describeDependencyKind(kind: DependencyKind | undefined): string {
|
|
174
|
+
if (kind === "depends") return "build dependency";
|
|
175
|
+
if (kind === "subscribes") return "co-subscription";
|
|
176
|
+
return "not recorded";
|
|
177
|
+
}
|
|
@@ -161,6 +161,20 @@ export const recipeKeySchema = z
|
|
|
161
161
|
"(for example 'workflow/task-files')"
|
|
162
162
|
);
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* The two ways one recipe can bring in another, named after the manifest lists
|
|
166
|
+
* that declare them: `subscribes` is a co-subscription, whose files land in the
|
|
167
|
+
* project and whose questions are asked; `depends` is a build dependency, a
|
|
168
|
+
* library whose files never reach the project's output.
|
|
169
|
+
*/
|
|
170
|
+
export const DEPENDENCY_KINDS = ["subscribes", "depends"] as const;
|
|
171
|
+
|
|
172
|
+
/** A co-subscription or a build dependency. */
|
|
173
|
+
export const dependencyKindSchema = z.enum(DEPENDENCY_KINDS);
|
|
174
|
+
|
|
175
|
+
/** A co-subscription or a build dependency. */
|
|
176
|
+
export type DependencyKind = (typeof DEPENDENCY_KINDS)[number];
|
|
177
|
+
|
|
164
178
|
// --- Value primitives ---------------------------------------------------------------------------
|
|
165
179
|
|
|
166
180
|
/** An exact semantic version, as published by a recipe. */
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import {
|
|
20
20
|
contentHashSchema,
|
|
21
|
+
dependencyKindSchema,
|
|
21
22
|
formatVersionSchema,
|
|
22
23
|
forwardCompatibleObject,
|
|
23
24
|
isoTimestampSchema,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
semverVersionSchema,
|
|
32
33
|
stableJsonStringify,
|
|
33
34
|
} from "./common.js";
|
|
35
|
+
import { publishedVariableDefinitionSchema } from "./variable-definition.js";
|
|
34
36
|
import { namespaceOfKey } from "../../refs/parse.js";
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -55,6 +57,22 @@ export const indexDependencySchema = forwardCompatibleObject({
|
|
|
55
57
|
* this same repository.
|
|
56
58
|
*/
|
|
57
59
|
repo: repoIdentitySchema.optional(),
|
|
60
|
+
/**
|
|
61
|
+
* The entry of the version's manifest that brings this dependency in, exactly
|
|
62
|
+
* as it was written: a recipe (`workflow/task-files`, with its range when it
|
|
63
|
+
* has one), a whole namespace of this repository (`workflow`), or a locator
|
|
64
|
+
* naming a recipe in another repository. When several entries cover the same
|
|
65
|
+
* recipe, the one naming it wins over a namespace. Absent on an entry recorded
|
|
66
|
+
* before sous described recipes in the index (ADR 0010).
|
|
67
|
+
*/
|
|
68
|
+
declared: z.string().min(1, "must not be empty").optional(),
|
|
69
|
+
/**
|
|
70
|
+
* Whether that brings it in as a co-subscription (`subscribes`: its files
|
|
71
|
+
* land in the project and its questions are asked) or as a build dependency
|
|
72
|
+
* (`depends`: a library only); `subscribes` when any entry covering it is a
|
|
73
|
+
* co-subscription. Recorded alongside `declared`.
|
|
74
|
+
*/
|
|
75
|
+
kind: dependencyKindSchema.optional(),
|
|
58
76
|
}).refine((entry) => entry.version !== undefined || entry.range !== undefined, {
|
|
59
77
|
message:
|
|
60
78
|
"must record either the exact version this dependency resolved to or the range " +
|
|
@@ -93,9 +111,30 @@ export const indexVersionSchema = forwardCompatibleObject({
|
|
|
93
111
|
* published version means one thing forever.
|
|
94
112
|
*
|
|
95
113
|
* The field is additive: an index written before it existed still parses, and
|
|
96
|
-
* a consumer that finds no entry falls back to the manifest's ranges.
|
|
114
|
+
* a consumer that finds no entry falls back to the manifest's ranges. A
|
|
115
|
+
* version described in full (ADR 0010) carries this field even when it is
|
|
116
|
+
* empty, so "depends on nothing" and "not recorded" read differently.
|
|
97
117
|
*/
|
|
98
118
|
dependencies: z.record(recipeKeySchema, indexDependencySchema).optional(),
|
|
119
|
+
/**
|
|
120
|
+
* The variable definitions this version's manifest publishes, in manifest
|
|
121
|
+
* order: the questions subscribing to it asks. An empty list means it asks
|
|
122
|
+
* none; an absent field means the release that recorded the version did not
|
|
123
|
+
* record them (a release made before ADR 0010), and only the recipe's own
|
|
124
|
+
* files can say.
|
|
125
|
+
*/
|
|
126
|
+
variables: z.array(publishedVariableDefinitionSchema).optional(),
|
|
127
|
+
/**
|
|
128
|
+
* The version's manifest `depends` and `subscribes` lists, exactly as
|
|
129
|
+
* written, so a consumer walks a recipe whose files it has not fetched the
|
|
130
|
+
* same way it walks its manifest: a namespace still expands to what the
|
|
131
|
+
* namespace holds, including a namespace of another repository, which no
|
|
132
|
+
* key of `dependencies` can record. Written, even when empty, beside
|
|
133
|
+
* `variables`; absent on a version recorded before ADR 0010. Kept as plain
|
|
134
|
+
* strings, so a ref form a later sous accepts never breaks this reader.
|
|
135
|
+
*/
|
|
136
|
+
depends: z.array(z.string().min(1, "must not be empty")).optional(),
|
|
137
|
+
subscribes: z.array(z.string().min(1, "must not be empty")).optional(),
|
|
99
138
|
});
|
|
100
139
|
|
|
101
140
|
/** One recipe, with every version the repo publishes of it. */
|
|
@@ -30,13 +30,15 @@ import {
|
|
|
30
30
|
repoUrlSchema,
|
|
31
31
|
semverVersionSchema,
|
|
32
32
|
stableJsonStringify,
|
|
33
|
+
DEPENDENCY_KINDS,
|
|
34
|
+
type DependencyKind,
|
|
33
35
|
} from "./common.js";
|
|
34
36
|
|
|
35
37
|
/** The literal `requestedBy` holder meaning "the project subscribed to this directly". */
|
|
36
38
|
export const PROJECT_HOLDER = "project";
|
|
37
39
|
|
|
38
|
-
/** How a locked recipe entered the project. */
|
|
39
|
-
export const LOCK_KINDS =
|
|
40
|
+
/** How a locked recipe entered the project: the dependency kinds every format shares. */
|
|
41
|
+
export const LOCK_KINDS = DEPENDENCY_KINDS;
|
|
40
42
|
|
|
41
43
|
/** One repo the project resolves against. */
|
|
42
44
|
export const lockedRepoSchema = z
|
|
@@ -138,7 +140,7 @@ export type LockedRepo = z.infer<typeof lockedRepoSchema>;
|
|
|
138
140
|
export type LockedRecipe = z.infer<typeof lockedRecipeSchema>;
|
|
139
141
|
|
|
140
142
|
/** How a locked recipe entered the project. */
|
|
141
|
-
export type LockKind =
|
|
143
|
+
export type LockKind = DependencyKind;
|
|
142
144
|
|
|
143
145
|
/**
|
|
144
146
|
* Validates a parsed lockfile, throwing a ConfigError that names the file and
|