@sous-io/sous 0.2.32 → 0.2.33
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 +7 -6
- package/docs/markdown/_sidebar.md +1 -0
- package/docs/markdown/commands.md +8 -4
- package/docs/markdown/configuration.md +28 -4
- package/docs/markdown/repositories-authoring.md +22 -2
- package/docs/markdown/repositories-consuming.md +40 -11
- package/docs/markdown/repositories-file-formats.md +28 -13
- package/docs/markdown/repositories-quickstart.md +1 -1
- package/docs/markdown/repositories.md +8 -4
- package/package.json +2 -1
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +25 -17
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +4 -0
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +21 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/sous.config.schema.json +27 -15
- package/src/commands/lock/rebuild.ts +3 -6
- package/src/commands/namespace/show.ts +2 -2
- package/src/commands/recipe/show.ts +26 -9
- package/src/commands/repo/contribute.ts +32 -23
- package/src/commands/repo/gc.ts +1 -1
- package/src/commands/repo/remove.ts +1 -1
- package/src/commands/repo/search.ts +1 -1
- package/src/commands/vars/ask.ts +75 -54
- package/src/lib/build-service.ts +85 -11
- package/src/lib/config-schema.ts +59 -19
- package/src/lib/include-resolver.ts +307 -69
- package/src/lib/markdown-compiler.ts +273 -81
- package/src/lib/project-scaffold/templates.ts +19 -11
- package/src/lib/repos/catalog-inputs.ts +1 -1
- package/src/lib/repos/catalog.ts +98 -72
- package/src/lib/repos/declarations.ts +22 -12
- package/src/lib/repos/formats/index-file.ts +1 -1
- package/src/lib/repos/formats/recipe-manifest.ts +3 -4
- package/src/lib/repos/lock-service.ts +1 -1
- package/src/lib/repos/locked-namespace-resolver.ts +14 -6
- package/src/lib/repos/locked-recipe-lookup.ts +123 -0
- package/src/lib/repos/locked-recipes.ts +1 -1
- package/src/lib/repos/memories-hash-name.ts +33 -0
- package/src/lib/repos/namespace-resolver.ts +198 -119
- package/src/lib/repos/recipe-config-layers.ts +29 -1
- package/src/lib/repos/recipe-key-matcher.ts +62 -0
- package/src/lib/repos/recipe-memories.ts +175 -0
- package/src/lib/repos/recipe-targets.ts +27 -28
- package/src/lib/repos/ref-search.ts +1 -1
- package/src/lib/repos/release/index-builder.ts +23 -15
- package/src/lib/repos/release/plan.ts +14 -7
- package/src/lib/repos/release/settle.ts +38 -121
- package/src/lib/repos/release/submit-checkout.ts +42 -23
- package/src/lib/repos/release/tags.ts +1 -1
- package/src/lib/repos/resolver.ts +90 -72
- package/src/lib/repos/subscription-service.ts +192 -170
- package/src/lib/settings.ts +37 -33
- package/src/lib/vars/ask.ts +3 -3
- package/src/lib/vars/mappings.ts +17 -9
- package/src/services/ref-resolver/container.ts +107 -0
- package/src/services/ref-resolver/dependency.ts +60 -0
- package/src/services/ref-resolver/format.ts +197 -0
- package/src/services/ref-resolver/glob.ts +115 -0
- package/src/services/ref-resolver/hash-names.ts +155 -0
- package/src/services/ref-resolver/index.ts +55 -0
- package/src/services/ref-resolver/injectable.ts +36 -0
- package/src/services/ref-resolver/location.ts +62 -0
- package/src/services/ref-resolver/lookups/cached-index-lookup.ts +53 -0
- package/src/services/ref-resolver/lookups/catalog-lookup.ts +23 -0
- package/src/services/ref-resolver/lookups/catalog-matcher.ts +261 -0
- package/src/services/ref-resolver/lookups/chained-lookup.ts +27 -0
- package/src/services/ref-resolver/lookups/env-var-lookup.ts +65 -0
- package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +150 -0
- package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +62 -0
- package/src/services/ref-resolver/lookups/ref-lookup.ts +54 -0
- package/src/services/ref-resolver/lookups/variable-lookup.ts +123 -0
- package/src/services/ref-resolver/named-refs.ts +144 -0
- package/src/services/ref-resolver/parser/env-var-splitter.ts +27 -0
- package/src/services/ref-resolver/parser/location-splitter.ts +239 -0
- package/src/services/ref-resolver/parser/name-path-splitter.ts +200 -0
- package/src/services/ref-resolver/parser/partial-ref.ts +84 -0
- package/src/services/ref-resolver/parser/query-splitter.ts +61 -0
- package/src/services/ref-resolver/parser/range-splitter.ts +52 -0
- package/src/services/ref-resolver/parser/ref-parser.ts +115 -0
- package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +51 -0
- package/src/services/ref-resolver/parts.ts +115 -0
- package/src/services/ref-resolver/pruners/command-line-pruner.ts +47 -0
- package/src/services/ref-resolver/pruners/include-pruner.ts +66 -0
- package/src/services/ref-resolver/pruners/manifest-pruner.ts +130 -0
- package/src/services/ref-resolver/pruners/ref-pruner.ts +135 -0
- package/src/services/ref-resolver/pruners/rule-helpers.ts +44 -0
- package/src/services/ref-resolver/pruners/stored-key-pruner.ts +136 -0
- package/src/services/ref-resolver/ref-helpers.ts +62 -0
- package/src/services/ref-resolver/ref-picker-service.ts +204 -0
- package/src/{lib/repos/reference-report.ts → services/ref-resolver/ref-report.ts} +2 -40
- package/src/services/ref-resolver/ref-resolve-arguments.ts +47 -0
- package/src/services/ref-resolver/ref-resolve-result.ts +36 -0
- package/src/services/ref-resolver/ref-resolver-service.ts +323 -0
- package/src/services/ref-resolver/source.ts +48 -0
- package/src/services/ref-resolver/tokens.ts +25 -0
- package/src/services/ref-resolver/types.ts +155 -0
- package/src/templating/init-liquid-engine.ts +25 -18
- package/src/lib/refs/find.ts +0 -770
- package/src/lib/refs/index.ts +0 -14
- package/src/lib/refs/parse.ts +0 -844
- package/src/lib/refs/pick.ts +0 -147
- package/src/lib/refs/scopes.ts +0 -95
- package/src/lib/refs/settle.ts +0 -219
|
@@ -58,27 +58,31 @@ import {
|
|
|
58
58
|
} from "./formats/lockfile.js";
|
|
59
59
|
import type { RecipeManifest, VariableDefinition } from "./formats/recipe-manifest.js";
|
|
60
60
|
import {
|
|
61
|
+
CachedIndexLookup,
|
|
62
|
+
RefPickArguments,
|
|
63
|
+
RefResolveArguments,
|
|
61
64
|
RefSource,
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
catalogRepoOfIndex,
|
|
66
|
+
describeRef,
|
|
64
67
|
formatRef,
|
|
65
|
-
|
|
68
|
+
isNamedRef,
|
|
69
|
+
locationFromUrl,
|
|
70
|
+
locationOf,
|
|
66
71
|
matchNames,
|
|
72
|
+
namespaceOf,
|
|
67
73
|
namespaceOfKey,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
74
|
+
parseNamedRef,
|
|
75
|
+
rangeOf,
|
|
76
|
+
repoOf,
|
|
77
|
+
sharedRefPicker,
|
|
78
|
+
sharedRefResolver,
|
|
79
|
+
shortKey,
|
|
71
80
|
splitRecipeKey,
|
|
72
|
-
type
|
|
73
|
-
type
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
referenceReposFromIndexes,
|
|
78
|
-
referenceToRef,
|
|
79
|
-
type ReferenceMatch,
|
|
80
|
-
type ReferenceRepo,
|
|
81
|
-
} from "../refs/index.js";
|
|
81
|
+
type CatalogRepo,
|
|
82
|
+
type NamedRef,
|
|
83
|
+
type RefKind,
|
|
84
|
+
type SousRef,
|
|
85
|
+
} from "../../services/ref-resolver/index.js";
|
|
82
86
|
import { describeIndexSearch } from "./ref-search.js";
|
|
83
87
|
import {
|
|
84
88
|
PROJECT_REQUESTER,
|
|
@@ -206,10 +210,7 @@ export type SubscriptionServiceOptions = {
|
|
|
206
210
|
/** How a yes or no question is asked. Injected in tests. */
|
|
207
211
|
ask?: (message: string) => Promise<boolean>;
|
|
208
212
|
/** How a choice between candidate refs is asked. Injected in tests. */
|
|
209
|
-
choose?: (
|
|
210
|
-
message: string,
|
|
211
|
-
candidates: ReferenceMatch[]
|
|
212
|
-
) => Promise<ReferenceMatch>;
|
|
213
|
+
choose?: (message: string, candidates: SousRef[]) => Promise<SousRef>;
|
|
213
214
|
/** The clock, so a recorded timestamp is predictable in tests. */
|
|
214
215
|
now?: () => Date;
|
|
215
216
|
};
|
|
@@ -361,7 +362,7 @@ export type RemoveRepoOptions = {
|
|
|
361
362
|
dryRun?: boolean;
|
|
362
363
|
/**
|
|
363
364
|
* The resolved settings scope, used to work out which output files the
|
|
364
|
-
* removed recipes wrote. Without it a `
|
|
365
|
+
* removed recipes wrote. Without it a `recipes.skills.outputs` destination holding a
|
|
365
366
|
* `${var}` cannot be resolved, and the file list is left out of the report.
|
|
366
367
|
*/
|
|
367
368
|
scope?: VarScope;
|
|
@@ -457,7 +458,7 @@ export type UpstreamCheckReport = {
|
|
|
457
458
|
export type UpdateOptions = {
|
|
458
459
|
/**
|
|
459
460
|
* What to update: a repository, a namespace or a recipe, written at any level
|
|
460
|
-
* of qualification and resolved through `src/
|
|
461
|
+
* of qualification and resolved through `src/services/ref-resolver/`. Left out, every
|
|
461
462
|
* subscription is updated.
|
|
462
463
|
*/
|
|
463
464
|
ref?: string;
|
|
@@ -542,10 +543,7 @@ export class SubscriptionService {
|
|
|
542
543
|
|
|
543
544
|
private readonly ask: (message: string) => Promise<boolean>;
|
|
544
545
|
|
|
545
|
-
private readonly choose: (
|
|
546
|
-
message: string,
|
|
547
|
-
candidates: ReferenceMatch[]
|
|
548
|
-
) => Promise<ReferenceMatch>;
|
|
546
|
+
private readonly choose: (message: string, candidates: SousRef[]) => Promise<SousRef>;
|
|
549
547
|
|
|
550
548
|
private readonly now: () => Date;
|
|
551
549
|
|
|
@@ -606,7 +604,7 @@ export class SubscriptionService {
|
|
|
606
604
|
askChoice(
|
|
607
605
|
message,
|
|
608
606
|
candidates.map((candidate) => ({
|
|
609
|
-
name:
|
|
607
|
+
name: describeRef(candidate),
|
|
610
608
|
value: candidate,
|
|
611
609
|
}))
|
|
612
610
|
));
|
|
@@ -681,16 +679,16 @@ export class SubscriptionService {
|
|
|
681
679
|
* this project calls it. A repository whose index has never been fetched can
|
|
682
680
|
* still be named, just not searched inside.
|
|
683
681
|
*/
|
|
684
|
-
cachedReferenceRepos():
|
|
682
|
+
cachedReferenceRepos(): CatalogRepo[] {
|
|
685
683
|
const repos = this.resolverRepos();
|
|
686
684
|
return Object.keys(repos).map((name) => {
|
|
687
|
-
const { url
|
|
685
|
+
const { url } = repos[name]!;
|
|
688
686
|
const index = this.cachedIndex(name);
|
|
689
|
-
const
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
687
|
+
const location = locationFromUrl(url, this.providers);
|
|
688
|
+
const known = { name, ...(location === undefined ? {} : { location }) };
|
|
689
|
+
return index === undefined
|
|
690
|
+
? { ...known, namespaces: [], recipes: [] }
|
|
691
|
+
: catalogRepoOfIndex(index, known);
|
|
694
692
|
});
|
|
695
693
|
}
|
|
696
694
|
|
|
@@ -859,7 +857,7 @@ export class SubscriptionService {
|
|
|
859
857
|
// step after this works with a fully qualified ref, so what is confirmed
|
|
860
858
|
// is exactly what is installed.
|
|
861
859
|
const { parsed, trusted: trustedForRef } = await this.resolveWrittenRef(options.ref, options);
|
|
862
|
-
const key =
|
|
860
|
+
const key = shortKey(parsed);
|
|
863
861
|
|
|
864
862
|
// The last gate before anything is fetched or written: what this will do to
|
|
865
863
|
// the project, in plain sentences, and a question.
|
|
@@ -955,23 +953,18 @@ export class SubscriptionService {
|
|
|
955
953
|
private async resolveWrittenRef(
|
|
956
954
|
written: string,
|
|
957
955
|
options: SubscribeOptions
|
|
958
|
-
): Promise<{ parsed:
|
|
959
|
-
const readings =
|
|
960
|
-
const
|
|
961
|
-
|
|
962
|
-
|
|
956
|
+
): Promise<{ parsed: NamedRef; trusted: string[] }> {
|
|
957
|
+
const { refs: readings } = sharedRefResolver().parse(written);
|
|
958
|
+
const short = readings.find(
|
|
959
|
+
(reading): reading is NamedRef => isNamedRef(reading) && locationOf(reading) === undefined
|
|
960
|
+
);
|
|
961
|
+
if (short !== undefined) {
|
|
962
|
+
return { parsed: await this.resolveBareRef(short, options), trusted: [] };
|
|
963
963
|
}
|
|
964
964
|
|
|
965
965
|
const trusted = await this.trustLocation(written, readings, options);
|
|
966
966
|
const chosen = await this.pickLocated(written, options.acceptFirst);
|
|
967
|
-
|
|
968
|
-
return {
|
|
969
|
-
parsed: referenceToRef(chosen, {
|
|
970
|
-
namespace: chosen.namespace!,
|
|
971
|
-
...(range === undefined ? {} : { range }),
|
|
972
|
-
}),
|
|
973
|
-
trusted,
|
|
974
|
-
};
|
|
967
|
+
return { parsed: chosen, trusted };
|
|
975
968
|
}
|
|
976
969
|
|
|
977
970
|
/**
|
|
@@ -982,46 +975,88 @@ export class SubscriptionService {
|
|
|
982
975
|
* @param written - The location as written.
|
|
983
976
|
* @param acceptFirst - Whether `--accept-first` was passed.
|
|
984
977
|
*/
|
|
985
|
-
private async pickLocated(written: string, acceptFirst?: boolean): Promise<
|
|
978
|
+
private async pickLocated(written: string, acceptFirst?: boolean): Promise<NamedRef> {
|
|
986
979
|
const repoOrder = this.repoSearchOrder();
|
|
987
980
|
const indexes = await this.loadIndexes(repoOrder);
|
|
988
981
|
this.indexesSnapshot = indexes;
|
|
989
982
|
|
|
990
|
-
const matches =
|
|
991
|
-
written,
|
|
992
|
-
[SousScope.Namespace, SousScope.Recipe],
|
|
993
|
-
{ repos: this.referenceRepos(repoOrder, indexes) }
|
|
994
|
-
);
|
|
983
|
+
const matches = await this.findNamed(written, repoOrder, indexes);
|
|
995
984
|
|
|
996
|
-
return
|
|
985
|
+
return this.pickNamed(matches, {
|
|
997
986
|
search: written.trim(),
|
|
998
|
-
interactive: this.interactive,
|
|
999
987
|
...(acceptFirst === undefined ? {} : { acceptFirst }),
|
|
1000
988
|
details: [
|
|
1001
989
|
` The repository at that location is trusted, but its index publishes no namespace ` +
|
|
1002
990
|
`or recipe there.`,
|
|
1003
991
|
` Run 'sous repo list --latest' to read what it publishes now.`,
|
|
1004
992
|
],
|
|
1005
|
-
write: (message: string) => this.write(message),
|
|
1006
|
-
choose: (message, offered) => this.choose(message, offered),
|
|
1007
993
|
});
|
|
1008
994
|
}
|
|
1009
995
|
|
|
1010
996
|
/**
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
997
|
+
* What a ref names among the trusted repositories' cached indexes, of the
|
|
998
|
+
* kinds given. Each repository carries the location it lives at, so a ref
|
|
999
|
+
* written as a location finds it whatever this project calls it.
|
|
1013
1000
|
*
|
|
1001
|
+
* @param input - The ref exactly as it was written.
|
|
1014
1002
|
* @param repoOrder - The repositories, in search order.
|
|
1015
1003
|
* @param indexes - Their indexes.
|
|
1004
|
+
* @param kinds - The kinds of ref this step accepts.
|
|
1016
1005
|
*/
|
|
1017
|
-
private
|
|
1006
|
+
private async findRefs(
|
|
1007
|
+
input: string,
|
|
1008
|
+
repoOrder: string[],
|
|
1009
|
+
indexes: Map<string, IndexFile>,
|
|
1010
|
+
kinds: readonly RefKind[]
|
|
1011
|
+
): Promise<SousRef[]> {
|
|
1018
1012
|
const repos = this.resolverRepos();
|
|
1019
1013
|
const urls: Record<string, string | undefined> = {};
|
|
1020
1014
|
for (const name of repoOrder) urls[name] = repos[name]?.url;
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1015
|
+
const lookup = new CachedIndexLookup(indexes, { order: repoOrder, urls });
|
|
1016
|
+
const result = await sharedRefResolver().resolve(
|
|
1017
|
+
new RefResolveArguments({ input, lookup, kinds, refusedIsEmpty: true })
|
|
1018
|
+
);
|
|
1019
|
+
return result.refs;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/** What a ref names among the trusted repositories, as a namespace or a recipe. */
|
|
1023
|
+
private async findNamed(
|
|
1024
|
+
input: string,
|
|
1025
|
+
repoOrder: string[],
|
|
1026
|
+
indexes: Map<string, IndexFile>
|
|
1027
|
+
): Promise<NamedRef[]> {
|
|
1028
|
+
return (await this.findRefs(input, repoOrder, indexes, ["namespace", "recipe"])) as NamedRef[];
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Settles on one of several refs, the way every command does: one proceeds,
|
|
1033
|
+
* several are chosen between through this service's own prompt, and
|
|
1034
|
+
* `--accept-first` takes the first.
|
|
1035
|
+
*
|
|
1036
|
+
* @param matches - What the ref could have meant, in listing order.
|
|
1037
|
+
* @param args - What the shared picker is told, less this service's own seams.
|
|
1038
|
+
*/
|
|
1039
|
+
private async pickRef(
|
|
1040
|
+
matches: SousRef[],
|
|
1041
|
+
args: Omit<ConstructorParameters<typeof RefPickArguments>[0], "interactive" | "write" | "choose">
|
|
1042
|
+
): Promise<SousRef> {
|
|
1043
|
+
return sharedRefPicker().pick(
|
|
1044
|
+
matches,
|
|
1045
|
+
new RefPickArguments({
|
|
1046
|
+
...args,
|
|
1047
|
+
interactive: this.interactive,
|
|
1048
|
+
write: (message: string) => this.write(message),
|
|
1049
|
+
choose: (message, offered) => this.choose(message, offered),
|
|
1050
|
+
})
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/** `pickRef` for a step that only offers namespaces and recipes. */
|
|
1055
|
+
private async pickNamed(
|
|
1056
|
+
matches: NamedRef[],
|
|
1057
|
+
args: Omit<ConstructorParameters<typeof RefPickArguments>[0], "interactive" | "write" | "choose">
|
|
1058
|
+
): Promise<NamedRef> {
|
|
1059
|
+
return (await this.pickRef(matches, args)) as NamedRef;
|
|
1025
1060
|
}
|
|
1026
1061
|
|
|
1027
1062
|
/**
|
|
@@ -1039,43 +1074,25 @@ export class SubscriptionService {
|
|
|
1039
1074
|
*/
|
|
1040
1075
|
private async trustLocation(
|
|
1041
1076
|
written: string,
|
|
1042
|
-
readings:
|
|
1077
|
+
readings: SousRef[],
|
|
1043
1078
|
options: SubscribeOptions
|
|
1044
1079
|
): Promise<string[]> {
|
|
1045
1080
|
const repos = this.resolverRepos();
|
|
1046
1081
|
const known = new Set(Object.values(repos).map((repo) => repo.identity));
|
|
1047
|
-
const located = readings.filter((reading) => reading
|
|
1048
|
-
if (located.some((reading) => known.has(reading
|
|
1082
|
+
const located = readings.filter((reading) => locationOf(reading) !== undefined);
|
|
1083
|
+
if (located.some((reading) => known.has(locationOf(reading)!.identity))) return [];
|
|
1049
1084
|
|
|
1050
1085
|
let chosen = located[0]!;
|
|
1051
|
-
const identities = new Set(located.map((reading) => reading
|
|
1086
|
+
const identities = new Set(located.map((reading) => locationOf(reading)!.identity));
|
|
1052
1087
|
if (identities.size > 1) {
|
|
1053
|
-
|
|
1054
|
-
scope:
|
|
1055
|
-
isNamedReading(reading) && reading.recipe !== undefined
|
|
1056
|
-
? SousScope.Recipe
|
|
1057
|
-
: SousScope.Namespace,
|
|
1058
|
-
key: formatRef(reading, this.providers),
|
|
1059
|
-
label: formatRef(reading, this.providers),
|
|
1060
|
-
repo: reading.location!.url,
|
|
1061
|
-
...(isNamedReading(reading) ? { namespace: reading.namespace } : {}),
|
|
1062
|
-
...(isNamedReading(reading) && reading.recipe !== undefined
|
|
1063
|
-
? { recipe: reading.recipe }
|
|
1064
|
-
: {}),
|
|
1065
|
-
qualification: Qualification.Full,
|
|
1066
|
-
}));
|
|
1067
|
-
const picked = await pickReference(offered, {
|
|
1088
|
+
chosen = await this.pickRef(located, {
|
|
1068
1089
|
search: written.trim(),
|
|
1069
|
-
interactive: this.interactive,
|
|
1070
1090
|
...(options.acceptFirst === undefined ? {} : { acceptFirst: options.acceptFirst }),
|
|
1071
1091
|
prompt: `'${written.trim()}' can be read as more than one repository. Which one?`,
|
|
1072
|
-
write: (message: string) => this.write(message),
|
|
1073
|
-
choose: (message, list) => this.choose(message, list),
|
|
1074
1092
|
});
|
|
1075
|
-
chosen = located[offered.indexOf(picked)]!;
|
|
1076
1093
|
}
|
|
1077
1094
|
|
|
1078
|
-
const location = chosen
|
|
1095
|
+
const location = locationOf(chosen)!;
|
|
1079
1096
|
const outcome = await this.trust.confirmTrust(
|
|
1080
1097
|
[
|
|
1081
1098
|
{
|
|
@@ -1098,8 +1115,8 @@ export class SubscriptionService {
|
|
|
1098
1115
|
* Settles what a ref names, reading nothing but the cached indexes.
|
|
1099
1116
|
*
|
|
1100
1117
|
* A ref with two segments already says what it names and is handed back
|
|
1101
|
-
* untouched. A ref with one segment is resolved through the
|
|
1102
|
-
*
|
|
1118
|
+
* untouched. A ref with one segment is resolved through the ref resolver
|
|
1119
|
+
* (`src/services/ref-resolver/`), over namespaces and recipes only:
|
|
1103
1120
|
* nothing found is an error naming what was searched, one match is used and
|
|
1104
1121
|
* reported, and several are chosen between. `--accept-first` takes the first
|
|
1105
1122
|
* match in the documented order; a run that cannot ask fails and says so.
|
|
@@ -1108,49 +1125,54 @@ export class SubscriptionService {
|
|
|
1108
1125
|
* @param options - The accept-first flag.
|
|
1109
1126
|
*/
|
|
1110
1127
|
private async resolveBareRef(
|
|
1111
|
-
written:
|
|
1128
|
+
written: NamedRef,
|
|
1112
1129
|
options: SubscribeOptions
|
|
1113
|
-
): Promise<
|
|
1130
|
+
): Promise<NamedRef> {
|
|
1114
1131
|
// A ref that names a recipe, spelled as it is published, says what it
|
|
1115
1132
|
// names already. One word, or a spelling in another case, is searched.
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
|
|
1133
|
+
const writtenRepo = repoOf(written)?.name;
|
|
1134
|
+
const key = shortKey(written);
|
|
1135
|
+
const search = writtenRepo === undefined ? key : `${writtenRepo}:${key}`;
|
|
1136
|
+
if (written.kind === "recipe" && key === key.toLowerCase()) {
|
|
1119
1137
|
return written;
|
|
1120
1138
|
}
|
|
1121
1139
|
|
|
1122
1140
|
const qualifier =
|
|
1123
|
-
|
|
1141
|
+
writtenRepo === undefined
|
|
1124
1142
|
? undefined
|
|
1125
|
-
: (matchNames(
|
|
1143
|
+
: (matchNames(writtenRepo, Object.keys(this.currentRepos()))[0] ?? writtenRepo);
|
|
1126
1144
|
const repoOrder = this.repoSearchOrder(qualifier);
|
|
1127
1145
|
const indexes = await this.loadIndexes(repoOrder);
|
|
1128
|
-
if (
|
|
1146
|
+
if (writtenRepo === undefined) this.indexesSnapshot = indexes;
|
|
1129
1147
|
|
|
1130
|
-
const
|
|
1131
|
-
const matches =
|
|
1148
|
+
const lookup = new CachedIndexLookup(indexes, { order: repoOrder });
|
|
1149
|
+
const matches = (
|
|
1150
|
+
await sharedRefResolver().resolve(
|
|
1151
|
+
new RefResolveArguments({ input: search, lookup, kinds: ["namespace", "recipe"] })
|
|
1152
|
+
)
|
|
1153
|
+
).refs as NamedRef[];
|
|
1132
1154
|
|
|
1133
1155
|
if (matches.length === 0) {
|
|
1134
1156
|
throw new ConfigError(
|
|
1135
1157
|
[
|
|
1136
|
-
`Nothing called '${
|
|
1158
|
+
`Nothing called '${key}' was found: no namespace has that name, ` +
|
|
1137
1159
|
`and no recipe does either.`,
|
|
1138
|
-
...describeIndexSearch({ name:
|
|
1139
|
-
` Run 'sous repo search ${
|
|
1160
|
+
...describeIndexSearch({ name: key, repoOrder, indexes }),
|
|
1161
|
+
` Run 'sous repo search ${key}' to look for something like it, or ` +
|
|
1140
1162
|
`'sous repo add <url>' to add the repository that publishes it.`,
|
|
1141
1163
|
].join("\n")
|
|
1142
1164
|
);
|
|
1143
1165
|
}
|
|
1144
1166
|
|
|
1145
|
-
const chosen = await
|
|
1167
|
+
const chosen = await this.pickNamed(matches, {
|
|
1146
1168
|
search,
|
|
1147
|
-
interactive: this.interactive,
|
|
1148
1169
|
...(options.acceptFirst === undefined ? {} : { acceptFirst: options.acceptFirst }),
|
|
1149
|
-
write: (message: string) => this.write(message),
|
|
1150
|
-
choose: (message, offered) => this.choose(message, offered),
|
|
1151
1170
|
});
|
|
1152
1171
|
|
|
1153
|
-
|
|
1172
|
+
// The range the ref was written with says which version to use, never
|
|
1173
|
+
// which thing is meant, so it is carried over to whatever was chosen.
|
|
1174
|
+
const range = rangeOf(written);
|
|
1175
|
+
return chosen.kind === "recipe" && range !== undefined ? { ...chosen, range } : chosen;
|
|
1154
1176
|
}
|
|
1155
1177
|
|
|
1156
1178
|
/**
|
|
@@ -1207,7 +1229,7 @@ export class SubscriptionService {
|
|
|
1207
1229
|
* @param options - The yes and dry-run flags.
|
|
1208
1230
|
*/
|
|
1209
1231
|
private async confirmSubscription(
|
|
1210
|
-
parsed:
|
|
1232
|
+
parsed: NamedRef,
|
|
1211
1233
|
options: SubscribeOptions
|
|
1212
1234
|
): Promise<void> {
|
|
1213
1235
|
const indexes = await this.indexSnapshot();
|
|
@@ -1243,7 +1265,7 @@ export class SubscriptionService {
|
|
|
1243
1265
|
* @param options - The prerelease flag, for the dependency peek.
|
|
1244
1266
|
*/
|
|
1245
1267
|
private async subscriptionPlan(
|
|
1246
|
-
parsed:
|
|
1268
|
+
parsed: NamedRef,
|
|
1247
1269
|
options: SubscribeOptions,
|
|
1248
1270
|
indexes: Map<string, IndexFile>
|
|
1249
1271
|
): Promise<string[]> {
|
|
@@ -1261,12 +1283,12 @@ export class SubscriptionService {
|
|
|
1261
1283
|
palette.warning(line)
|
|
1262
1284
|
);
|
|
1263
1285
|
|
|
1264
|
-
if (parsed.
|
|
1286
|
+
if (parsed.kind === "namespace") {
|
|
1265
1287
|
const published = this.namespaceRecipes(parsed, indexes);
|
|
1266
1288
|
lines.push(
|
|
1267
1289
|
...sentence(
|
|
1268
1290
|
`Subscribing to '${target}' subscribes this project to the whole ` +
|
|
1269
|
-
`namespace '${parsed.
|
|
1291
|
+
`namespace '${parsed.name}', which means ${palette.highlight(
|
|
1270
1292
|
"every recipe in it, including ones published later"
|
|
1271
1293
|
)}.`
|
|
1272
1294
|
)
|
|
@@ -1279,8 +1301,8 @@ export class SubscriptionService {
|
|
|
1279
1301
|
} else {
|
|
1280
1302
|
lines.push(
|
|
1281
1303
|
...sentence(
|
|
1282
|
-
`Subscribing to '${target}' installs the recipe '${parsed.
|
|
1283
|
-
`the namespace '${parsed
|
|
1304
|
+
`Subscribing to '${target}' installs the recipe '${parsed.name}' from ` +
|
|
1305
|
+
`the namespace '${namespaceOf(parsed)!.name}'.`
|
|
1284
1306
|
)
|
|
1285
1307
|
);
|
|
1286
1308
|
}
|
|
@@ -1344,11 +1366,12 @@ export class SubscriptionService {
|
|
|
1344
1366
|
*
|
|
1345
1367
|
* @param parsed - The fully qualified namespace ref.
|
|
1346
1368
|
*/
|
|
1347
|
-
private namespaceRecipes(parsed:
|
|
1348
|
-
const
|
|
1369
|
+
private namespaceRecipes(parsed: NamedRef, indexes: Map<string, IndexFile>): string[] {
|
|
1370
|
+
const repo = repoOf(parsed)?.name;
|
|
1371
|
+
const index = repo === undefined ? undefined : indexes.get(repo);
|
|
1349
1372
|
if (index === undefined) return [];
|
|
1350
1373
|
return Object.keys(index.recipes)
|
|
1351
|
-
.filter((key) => key.startsWith(`${parsed
|
|
1374
|
+
.filter((key) => key.startsWith(`${namespaceOf(parsed)!.name}/`))
|
|
1352
1375
|
.sort();
|
|
1353
1376
|
}
|
|
1354
1377
|
|
|
@@ -1366,7 +1389,7 @@ export class SubscriptionService {
|
|
|
1366
1389
|
* @param options - The prerelease flag.
|
|
1367
1390
|
*/
|
|
1368
1391
|
private async knownUntrustedDependencyRepos(
|
|
1369
|
-
parsed:
|
|
1392
|
+
parsed: NamedRef,
|
|
1370
1393
|
options: SubscribeOptions,
|
|
1371
1394
|
indexes: Map<string, IndexFile>
|
|
1372
1395
|
): Promise<string[]> {
|
|
@@ -1413,7 +1436,7 @@ export class SubscriptionService {
|
|
|
1413
1436
|
* @param options - The prerelease and trust flags.
|
|
1414
1437
|
*/
|
|
1415
1438
|
private async resolveClosure(
|
|
1416
|
-
parsed:
|
|
1439
|
+
parsed: NamedRef,
|
|
1417
1440
|
options: SubscribeOptions
|
|
1418
1441
|
): Promise<{
|
|
1419
1442
|
resolved: ResolvedRecipe[];
|
|
@@ -1501,14 +1524,16 @@ export class SubscriptionService {
|
|
|
1501
1524
|
* @param keys - The subscription keys in force.
|
|
1502
1525
|
*/
|
|
1503
1526
|
private async subscriptionKeyFor(written: string, keys: string[]): Promise<string> {
|
|
1504
|
-
const readings =
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1527
|
+
const { refs: readings } = sharedRefResolver().parse(written);
|
|
1528
|
+
const short = readings.find(
|
|
1529
|
+
(reading): reading is NamedRef => isNamedRef(reading) && locationOf(reading) === undefined
|
|
1530
|
+
);
|
|
1531
|
+
if (short !== undefined) {
|
|
1532
|
+
const key = shortKey(short);
|
|
1508
1533
|
return matchNames(key, new Set(keys))[0] ?? key;
|
|
1509
1534
|
}
|
|
1510
1535
|
const chosen = await this.pickLocated(written);
|
|
1511
|
-
return
|
|
1536
|
+
return shortKey(chosen);
|
|
1512
1537
|
}
|
|
1513
1538
|
|
|
1514
1539
|
/**
|
|
@@ -1762,7 +1787,7 @@ export class SubscriptionService {
|
|
|
1762
1787
|
for (const key of Object.keys(subscriptions).sort()) {
|
|
1763
1788
|
let qualifier: string | undefined;
|
|
1764
1789
|
try {
|
|
1765
|
-
qualifier =
|
|
1790
|
+
qualifier = repoOf(parseNamedRef(key, RefSource.Config))?.name;
|
|
1766
1791
|
} catch {
|
|
1767
1792
|
// A ref that does not parse cannot name this repository, and reporting
|
|
1768
1793
|
// it here would bury the removal under an unrelated complaint.
|
|
@@ -1921,7 +1946,7 @@ export class SubscriptionService {
|
|
|
1921
1946
|
*
|
|
1922
1947
|
* - Every trusted repository's index is fetched fresh first. One that
|
|
1923
1948
|
* cannot be reached is reported, and its pins stay exactly where they are.
|
|
1924
|
-
* - The reference, when there is one, is resolved through `src/
|
|
1949
|
+
* - The reference, when there is one, is resolved through `src/services/ref-resolver/`
|
|
1925
1950
|
* against those indexes, and it narrows which pins may move. Everything
|
|
1926
1951
|
* outside it is held at its locked version for as long as its ranges
|
|
1927
1952
|
* allow, so dependencies move with the closure and nothing else does.
|
|
@@ -2118,34 +2143,31 @@ export class SubscriptionService {
|
|
|
2118
2143
|
const urls: Record<string, string | undefined> = {};
|
|
2119
2144
|
for (const name of repoOrder) urls[name] = repos[name]?.url;
|
|
2120
2145
|
|
|
2121
|
-
const matches =
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
);
|
|
2146
|
+
const matches = await this.findRefs(options.ref, repoOrder, indexes, [
|
|
2147
|
+
"repo",
|
|
2148
|
+
"namespace",
|
|
2149
|
+
"recipe",
|
|
2150
|
+
]);
|
|
2126
2151
|
|
|
2127
|
-
const chosen = await
|
|
2152
|
+
const chosen = await this.pickRef(matches, {
|
|
2128
2153
|
search: options.ref,
|
|
2129
|
-
interactive: this.interactive,
|
|
2130
2154
|
...(options.acceptFirst === undefined ? {} : { acceptFirst: options.acceptFirst }),
|
|
2131
2155
|
details: [
|
|
2132
2156
|
` No trusted repository, namespace or recipe has that name.`,
|
|
2133
2157
|
...describeIndexSearch({ name: options.ref, repoOrder, indexes }),
|
|
2134
2158
|
],
|
|
2135
|
-
write: (message: string) => this.write(message),
|
|
2136
|
-
choose: (message, offered) => this.choose(message, offered),
|
|
2137
2159
|
});
|
|
2138
2160
|
|
|
2139
|
-
switch (chosen.
|
|
2140
|
-
case
|
|
2141
|
-
return { kind: "repository", repo: chosen.
|
|
2142
|
-
case
|
|
2143
|
-
return { kind: "namespace", repo: chosen
|
|
2161
|
+
switch (chosen.kind) {
|
|
2162
|
+
case "repo":
|
|
2163
|
+
return { kind: "repository", repo: chosen.name! };
|
|
2164
|
+
case "namespace":
|
|
2165
|
+
return { kind: "namespace", repo: repoOf(chosen)!.name!, namespace: chosen.name };
|
|
2144
2166
|
default:
|
|
2145
2167
|
return {
|
|
2146
2168
|
kind: "recipe",
|
|
2147
|
-
repo: chosen
|
|
2148
|
-
key:
|
|
2169
|
+
repo: repoOf(chosen)!.name!,
|
|
2170
|
+
key: shortKey(chosen as NamedRef),
|
|
2149
2171
|
};
|
|
2150
2172
|
}
|
|
2151
2173
|
}
|
|
@@ -2215,9 +2237,9 @@ export class SubscriptionService {
|
|
|
2215
2237
|
const entry = subscriptions[key]!;
|
|
2216
2238
|
let request: RefRequest;
|
|
2217
2239
|
try {
|
|
2218
|
-
const parsed = parseShortRef(key, RefSource.Config);
|
|
2219
2240
|
request = {
|
|
2220
|
-
ref:
|
|
2241
|
+
ref: parseNamedRef(key, RefSource.Config),
|
|
2242
|
+
...(entry.range === undefined ? {} : { range: entry.range }),
|
|
2221
2243
|
requestedBy: PROJECT_REQUESTER,
|
|
2222
2244
|
kind: "subscribes",
|
|
2223
2245
|
...(entry.prerelease === true ? { prerelease: true } : {}),
|
|
@@ -2321,23 +2343,24 @@ export class SubscriptionService {
|
|
|
2321
2343
|
): boolean {
|
|
2322
2344
|
if (scope.kind === "all") return true;
|
|
2323
2345
|
|
|
2324
|
-
let parsed:
|
|
2346
|
+
let parsed: NamedRef;
|
|
2325
2347
|
try {
|
|
2326
|
-
parsed =
|
|
2348
|
+
parsed = parseNamedRef(key, RefSource.Config);
|
|
2327
2349
|
} catch {
|
|
2328
2350
|
return false;
|
|
2329
2351
|
}
|
|
2352
|
+
const parsedRepo = repoOf(parsed)?.name;
|
|
2330
2353
|
|
|
2331
|
-
const closure = lockedClosure(lock, keysHeldBySubscription(lock,
|
|
2354
|
+
const closure = lockedClosure(lock, keysHeldBySubscription(lock, shortKey(parsed)));
|
|
2332
2355
|
if (closure.some((held) => movable(held, lock.recipes[held]!.repo))) return true;
|
|
2333
2356
|
|
|
2334
2357
|
// Nothing locked reaches the scope, but the subscription may still name it:
|
|
2335
2358
|
// a namespace subscription gains the recipes a namespace publishes later.
|
|
2336
|
-
if (
|
|
2337
|
-
const covered =
|
|
2359
|
+
if (parsedRepo !== undefined && parsedRepo !== scope.repo) return false;
|
|
2360
|
+
const covered = shortKey(parsed);
|
|
2338
2361
|
switch (scope.kind) {
|
|
2339
2362
|
case "repository":
|
|
2340
|
-
return
|
|
2363
|
+
return parsedRepo === scope.repo;
|
|
2341
2364
|
case "namespace":
|
|
2342
2365
|
return covered === scope.namespace || covered.startsWith(`${scope.namespace}/`);
|
|
2343
2366
|
case "recipe":
|
|
@@ -2368,7 +2391,7 @@ export class SubscriptionService {
|
|
|
2368
2391
|
): Lockfile {
|
|
2369
2392
|
const coverOf = (key: string): string | undefined => {
|
|
2370
2393
|
try {
|
|
2371
|
-
return
|
|
2394
|
+
return shortKey(parseNamedRef(key, RefSource.Config));
|
|
2372
2395
|
} catch {
|
|
2373
2396
|
return undefined;
|
|
2374
2397
|
}
|
|
@@ -2456,16 +2479,16 @@ export class SubscriptionService {
|
|
|
2456
2479
|
.filter((key) => declared[key]?.enabled === false)
|
|
2457
2480
|
.filter((key) => {
|
|
2458
2481
|
if (scope.kind === "all") return true;
|
|
2459
|
-
let parsed:
|
|
2482
|
+
let parsed: NamedRef;
|
|
2460
2483
|
try {
|
|
2461
|
-
parsed =
|
|
2484
|
+
parsed = parseNamedRef(key, RefSource.Config);
|
|
2462
2485
|
} catch {
|
|
2463
2486
|
return false;
|
|
2464
2487
|
}
|
|
2465
|
-
const covered =
|
|
2488
|
+
const covered = shortKey(parsed);
|
|
2466
2489
|
switch (scope.kind) {
|
|
2467
2490
|
case "repository":
|
|
2468
|
-
return parsed
|
|
2491
|
+
return repoOf(parsed)?.name === scope.repo;
|
|
2469
2492
|
case "namespace":
|
|
2470
2493
|
return covered === scope.namespace || covered.startsWith(`${scope.namespace}/`);
|
|
2471
2494
|
case "recipe":
|
|
@@ -2642,9 +2665,9 @@ export class SubscriptionService {
|
|
|
2642
2665
|
const entry = subscriptions[key]!;
|
|
2643
2666
|
if (this.subscriptionIsLocked(key, entry, before)) continue;
|
|
2644
2667
|
|
|
2645
|
-
let parsed:
|
|
2668
|
+
let parsed: NamedRef;
|
|
2646
2669
|
try {
|
|
2647
|
-
parsed =
|
|
2670
|
+
parsed = parseNamedRef(key, RefSource.Config);
|
|
2648
2671
|
} catch (error) {
|
|
2649
2672
|
report.failed.push({ key, reason: describeError(error) });
|
|
2650
2673
|
continue;
|
|
@@ -2653,10 +2676,8 @@ export class SubscriptionService {
|
|
|
2653
2676
|
pending.push({
|
|
2654
2677
|
key,
|
|
2655
2678
|
request: {
|
|
2656
|
-
ref:
|
|
2657
|
-
|
|
2658
|
-
...(entry.range === undefined ? {} : { range: entry.range }),
|
|
2659
|
-
},
|
|
2679
|
+
ref: parsed,
|
|
2680
|
+
...(entry.range === undefined ? {} : { range: entry.range }),
|
|
2660
2681
|
requestedBy: PROJECT_REQUESTER,
|
|
2661
2682
|
kind: "subscribes",
|
|
2662
2683
|
...(entry.prerelease === true ? { prerelease: true } : {}),
|
|
@@ -3371,11 +3392,12 @@ export class SubscriptionService {
|
|
|
3371
3392
|
*/
|
|
3372
3393
|
private writeSubscriptionEntry(
|
|
3373
3394
|
key: string,
|
|
3374
|
-
parsed:
|
|
3395
|
+
parsed: NamedRef,
|
|
3375
3396
|
options: SubscribeOptions
|
|
3376
3397
|
): void {
|
|
3398
|
+
const range = rangeOf(parsed);
|
|
3377
3399
|
const entry: SubscriptionEntry = {
|
|
3378
|
-
...(
|
|
3400
|
+
...(range === undefined ? {} : { range }),
|
|
3379
3401
|
...(options.prerelease === true ? { prerelease: true } : {}),
|
|
3380
3402
|
...(options.alwaysPull === true ? { alwaysPull: true } : {}),
|
|
3381
3403
|
addedAt: this.now().toISOString(),
|
|
@@ -3513,17 +3535,17 @@ export class SubscriptionService {
|
|
|
3513
3535
|
for (const dependency of manifest.depends ?? []) {
|
|
3514
3536
|
// Every reading counts: a dependency that reads more than one way still
|
|
3515
3537
|
// declares its range for whichever reading the lockfile settled on.
|
|
3516
|
-
let readings:
|
|
3538
|
+
let readings: SousRef[];
|
|
3517
3539
|
try {
|
|
3518
|
-
readings =
|
|
3540
|
+
readings = sharedRefResolver().parse(dependency, RefSource.Manifest).refs;
|
|
3519
3541
|
} catch {
|
|
3520
3542
|
continue;
|
|
3521
3543
|
}
|
|
3522
3544
|
for (const reading of readings) {
|
|
3523
|
-
if (!
|
|
3524
|
-
const dependencyKey =
|
|
3545
|
+
if (!isNamedRef(reading)) continue;
|
|
3546
|
+
const dependencyKey = shortKey(reading);
|
|
3525
3547
|
if (dependencyKey !== key && dependencyKey !== namespace) continue;
|
|
3526
|
-
return reading
|
|
3548
|
+
return rangeOf(reading) ?? "*";
|
|
3527
3549
|
}
|
|
3528
3550
|
}
|
|
3529
3551
|
|
|
@@ -3658,7 +3680,7 @@ export class SubscriptionService {
|
|
|
3658
3680
|
* @param options - The indexes to read, and whether prereleases may match.
|
|
3659
3681
|
*/
|
|
3660
3682
|
async previewSubscription(
|
|
3661
|
-
parsed:
|
|
3683
|
+
parsed: NamedRef,
|
|
3662
3684
|
options: PreviewSubscriptionOptions = {}
|
|
3663
3685
|
): Promise<SubscriptionPreview> {
|
|
3664
3686
|
const repos = this.resolverRepos();
|