@sous-io/sous 0.2.37 → 1.0.2
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 +14 -256
- package/dist/bin.js +11 -0
- package/dist/sous.js +1094 -0
- package/docs/markdown/README.md +5 -4
- package/docs/markdown/_sidebar.md +21 -1
- package/docs/markdown/commands.md +294 -431
- package/docs/markdown/config-discovery.md +25 -14
- package/docs/markdown/config-inspection.md +53 -31
- package/docs/markdown/config-layers.md +168 -66
- package/docs/markdown/config-variables.md +44 -11
- package/docs/markdown/configuration.md +104 -17
- package/docs/markdown/design-principles.md +4 -1
- package/docs/markdown/repositories-authoring.md +30 -0
- package/docs/markdown/repositories-consuming.md +72 -14
- package/docs/markdown/repositories-file-formats.md +31 -13
- package/docs/markdown/repositories-providers.md +48 -57
- package/docs/markdown/repositories-quickstart.md +3 -2
- package/docs/markdown/repositories-troubleshooting.md +27 -1
- package/docs/markdown/repositories-variables.md +6 -1
- package/docs/markdown/repositories.md +18 -8
- package/package.json +48 -107
- package/recipes/core/sous-skills/skills/about-liquid-templates/SKILL.tpl.md +4 -6
- package/recipes/core/sous-skills/skills/about-liquid-templates/references/liquid-filters.tpl.md +2 -0
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +10 -9
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +35 -14
- package/recipes/core/sous-skills/skills/about-sous-recipes/SKILL.tpl.md +51 -20
- package/recipes/core/sous-skills/sous.recipe.yaml +1 -1
- package/sous.config.schema.json +351 -132
- package/bin/run.js +0 -51
- package/src/base-command.ts +0 -457
- package/src/commands/build.ts +0 -181
- package/src/commands/clear.ts +0 -79
- package/src/commands/compile.ts +0 -155
- package/src/commands/config/get.ts +0 -110
- package/src/commands/config/show.ts +0 -32
- package/src/commands/config/validate.ts +0 -53
- package/src/commands/help.ts +0 -46
- package/src/commands/init.ts +0 -289
- package/src/commands/launch.ts +0 -139
- package/src/commands/lock/rebuild.ts +0 -239
- package/src/commands/lock/show.ts +0 -115
- package/src/commands/namespace/list.ts +0 -138
- package/src/commands/namespace/show.ts +0 -130
- package/src/commands/prune.ts +0 -43
- package/src/commands/recipe/list.ts +0 -112
- package/src/commands/recipe/show.ts +0 -439
- package/src/commands/repo/add.ts +0 -145
- package/src/commands/repo/contribute.ts +0 -604
- package/src/commands/repo/gc.ts +0 -173
- package/src/commands/repo/init.ts +0 -136
- package/src/commands/repo/link.ts +0 -899
- package/src/commands/repo/list.ts +0 -236
- package/src/commands/repo/release.ts +0 -685
- package/src/commands/repo/remove.ts +0 -194
- package/src/commands/repo/search.ts +0 -243
- package/src/commands/repo/submit.ts +0 -341
- package/src/commands/repo/unlink.ts +0 -461
- package/src/commands/subscription/add.ts +0 -286
- package/src/commands/subscription/list.ts +0 -208
- package/src/commands/subscription/remove.ts +0 -182
- package/src/commands/subscription/update.ts +0 -216
- package/src/commands/update.ts +0 -178
- package/src/commands/vars/ask.ts +0 -417
- package/src/commands/vars/index.ts +0 -79
- package/src/commands/vars/list.ts +0 -67
- package/src/commands/vars/show.ts +0 -77
- package/src/config-command.ts +0 -30
- package/src/lib/build-preparation.ts +0 -128
- package/src/lib/build-service.ts +0 -580
- package/src/lib/config-discovery.ts +0 -378
- package/src/lib/config-inspect.ts +0 -145
- package/src/lib/config-kernel.mjs +0 -379
- package/src/lib/config-schema.ts +0 -401
- package/src/lib/env-file.ts +0 -328
- package/src/lib/env-local.ts +0 -212
- package/src/lib/errors.ts +0 -32
- package/src/lib/glob-pattern.ts +0 -24
- package/src/lib/include-resolver.ts +0 -523
- package/src/lib/interactive.ts +0 -165
- package/src/lib/markdown-compiler.ts +0 -947
- package/src/lib/package-info.ts +0 -25
- package/src/lib/pid-service.ts +0 -99
- package/src/lib/project-install.d.mts +0 -44
- package/src/lib/project-install.mjs +0 -234
- package/src/lib/project-scaffold/index.ts +0 -332
- package/src/lib/project-scaffold/templates.ts +0 -240
- package/src/lib/repos/catalog-display.ts +0 -215
- package/src/lib/repos/catalog-inputs.ts +0 -288
- package/src/lib/repos/catalog.ts +0 -842
- package/src/lib/repos/contribute.ts +0 -452
- package/src/lib/repos/core-recipe.ts +0 -105
- package/src/lib/repos/declarations.ts +0 -187
- package/src/lib/repos/defaults.ts +0 -175
- package/src/lib/repos/formats/common.ts +0 -440
- package/src/lib/repos/formats/index-file.ts +0 -253
- package/src/lib/repos/formats/links-map.ts +0 -98
- package/src/lib/repos/formats/lockfile.ts +0 -169
- package/src/lib/repos/formats/patterns.ts +0 -57
- package/src/lib/repos/formats/recipe-manifest.ts +0 -205
- package/src/lib/repos/formats/repo-manifest.ts +0 -97
- package/src/lib/repos/formats/store-entry.ts +0 -84
- package/src/lib/repos/formats/variable-definition.ts +0 -268
- package/src/lib/repos/freshness.ts +0 -264
- package/src/lib/repos/git-clone.ts +0 -850
- package/src/lib/repos/identity.ts +0 -89
- package/src/lib/repos/index.ts +0 -57
- package/src/lib/repos/links.ts +0 -356
- package/src/lib/repos/load-manifest.ts +0 -237
- package/src/lib/repos/lock-service.ts +0 -454
- package/src/lib/repos/locked-namespace-resolver.ts +0 -232
- package/src/lib/repos/locked-recipe-lookup.ts +0 -123
- package/src/lib/repos/locked-recipes.ts +0 -254
- package/src/lib/repos/managed-layer.ts +0 -422
- package/src/lib/repos/memories-hash-name.ts +0 -33
- package/src/lib/repos/namespace-resolver.ts +0 -527
- package/src/lib/repos/providers/base.ts +0 -268
- package/src/lib/repos/providers/git.ts +0 -245
- package/src/lib/repos/providers/github.ts +0 -589
- package/src/lib/repos/providers/gitlab.ts +0 -332
- package/src/lib/repos/providers/http.ts +0 -128
- package/src/lib/repos/providers/index-cache.ts +0 -421
- package/src/lib/repos/providers/index.ts +0 -106
- package/src/lib/repos/providers/local.ts +0 -410
- package/src/lib/repos/providers/provider.ts +0 -615
- package/src/lib/repos/recipe-config-layers.ts +0 -316
- package/src/lib/repos/recipe-key-matcher.ts +0 -62
- package/src/lib/repos/recipe-memories.ts +0 -178
- package/src/lib/repos/recipe-targets.ts +0 -236
- package/src/lib/repos/ref-search.ts +0 -46
- package/src/lib/repos/release/bump.ts +0 -161
- package/src/lib/repos/release/changelog.ts +0 -461
- package/src/lib/repos/release/git-state.ts +0 -460
- package/src/lib/repos/release/index-builder.ts +0 -1108
- package/src/lib/repos/release/index.ts +0 -19
- package/src/lib/repos/release/plan.ts +0 -521
- package/src/lib/repos/release/settle.ts +0 -154
- package/src/lib/repos/release/submissions.ts +0 -214
- package/src/lib/repos/release/submit-checkout.ts +0 -290
- package/src/lib/repos/release/submit-questions.ts +0 -153
- package/src/lib/repos/release/submit-service.ts +0 -898
- package/src/lib/repos/release/tags.ts +0 -239
- package/src/lib/repos/release/validate.ts +0 -463
- package/src/lib/repos/resolver.ts +0 -1028
- package/src/lib/repos/scaffold/index.ts +0 -245
- package/src/lib/repos/scaffold/templates.ts +0 -517
- package/src/lib/repos/seed.ts +0 -473
- package/src/lib/repos/store/contract.ts +0 -64
- package/src/lib/repos/store/hash.ts +0 -191
- package/src/lib/repos/store/recipe-store.ts +0 -599
- package/src/lib/repos/store/settings.ts +0 -58
- package/src/lib/repos/subscription-service.ts +0 -3859
- package/src/lib/repos/trust.ts +0 -447
- package/src/lib/repos/update-plan.ts +0 -235
- package/src/lib/self-update/index.ts +0 -14
- package/src/lib/self-update/installs.ts +0 -413
- package/src/lib/self-update/managers.ts +0 -126
- package/src/lib/self-update/plan.ts +0 -315
- package/src/lib/self-update/registry.ts +0 -99
- package/src/lib/self-update/run.ts +0 -506
- package/src/lib/self-update/versions.ts +0 -243
- package/src/lib/settings.ts +0 -1107
- package/src/lib/sous-home.ts +0 -104
- package/src/lib/state.ts +0 -167
- package/src/lib/vars/answers.ts +0 -192
- package/src/lib/vars/ask.ts +0 -1152
- package/src/lib/vars/definition-source.ts +0 -261
- package/src/lib/vars/display.ts +0 -231
- package/src/lib/vars/index.ts +0 -20
- package/src/lib/vars/ladder.ts +0 -282
- package/src/lib/vars/mappings.ts +0 -260
- package/src/lib/vars/names.ts +0 -94
- package/src/lib/vars/preanswers.ts +0 -395
- package/src/lib/vars/question-plan.ts +0 -241
- package/src/lib/vars/report.ts +0 -228
- package/src/lib/vars/safe-regex.ts +0 -235
- package/src/lib/vars/secrets.ts +0 -131
- package/src/lib/vars/validate.ts +0 -312
- package/src/lib/version-report.ts +0 -90
- package/src/lib/watch-loop.ts +0 -148
- package/src/lib/watch-service.ts +0 -116
- package/src/services/ref-resolver/container.ts +0 -107
- package/src/services/ref-resolver/dependency.ts +0 -60
- package/src/services/ref-resolver/format.ts +0 -197
- package/src/services/ref-resolver/glob.ts +0 -115
- package/src/services/ref-resolver/hash-names.ts +0 -155
- package/src/services/ref-resolver/index.ts +0 -55
- package/src/services/ref-resolver/injectable.ts +0 -36
- package/src/services/ref-resolver/location.ts +0 -62
- package/src/services/ref-resolver/lookups/cached-index-lookup.ts +0 -53
- package/src/services/ref-resolver/lookups/catalog-lookup.ts +0 -23
- package/src/services/ref-resolver/lookups/catalog-matcher.ts +0 -261
- package/src/services/ref-resolver/lookups/chained-lookup.ts +0 -27
- package/src/services/ref-resolver/lookups/env-var-lookup.ts +0 -65
- package/src/services/ref-resolver/lookups/fetched-index-lookup.ts +0 -150
- package/src/services/ref-resolver/lookups/recorded-dependency-lookup.ts +0 -62
- package/src/services/ref-resolver/lookups/ref-lookup.ts +0 -54
- package/src/services/ref-resolver/lookups/variable-lookup.ts +0 -123
- package/src/services/ref-resolver/named-refs.ts +0 -144
- package/src/services/ref-resolver/parser/env-var-splitter.ts +0 -27
- package/src/services/ref-resolver/parser/location-splitter.ts +0 -239
- package/src/services/ref-resolver/parser/name-path-splitter.ts +0 -200
- package/src/services/ref-resolver/parser/partial-ref.ts +0 -84
- package/src/services/ref-resolver/parser/query-splitter.ts +0 -61
- package/src/services/ref-resolver/parser/range-splitter.ts +0 -52
- package/src/services/ref-resolver/parser/ref-parser.ts +0 -115
- package/src/services/ref-resolver/parser/repo-qualifier-splitter.ts +0 -51
- package/src/services/ref-resolver/parts.ts +0 -115
- package/src/services/ref-resolver/pruners/command-line-pruner.ts +0 -47
- package/src/services/ref-resolver/pruners/include-pruner.ts +0 -66
- package/src/services/ref-resolver/pruners/manifest-pruner.ts +0 -130
- package/src/services/ref-resolver/pruners/ref-pruner.ts +0 -135
- package/src/services/ref-resolver/pruners/rule-helpers.ts +0 -44
- package/src/services/ref-resolver/pruners/stored-key-pruner.ts +0 -136
- package/src/services/ref-resolver/ref-helpers.ts +0 -62
- package/src/services/ref-resolver/ref-picker-service.ts +0 -204
- package/src/services/ref-resolver/ref-report.ts +0 -84
- package/src/services/ref-resolver/ref-resolve-arguments.ts +0 -47
- package/src/services/ref-resolver/ref-resolve-result.ts +0 -36
- package/src/services/ref-resolver/ref-resolver-service.ts +0 -323
- package/src/services/ref-resolver/source.ts +0 -48
- package/src/services/ref-resolver/tokens.ts +0 -25
- package/src/services/ref-resolver/types.ts +0 -155
- package/src/templating/filters/bullet-list.ts +0 -9
- package/src/templating/filters/index.ts +0 -8
- package/src/templating/init-liquid-engine.ts +0 -139
- package/src/templating/lib/glob-files.ts +0 -74
- package/src/templating/lib/import-export.ts +0 -32
- package/src/templating/lib/secret-scope.ts +0 -41
- package/src/templating/lib/tag-args.ts +0 -19
- package/src/templating/tags/exportScalarVarsJs.ts +0 -46
- package/src/templating/tags/getFiles.ts +0 -89
- package/src/templating/tags/index.ts +0 -14
- package/src/templating/tags/listFiles.ts +0 -54
- package/src/templating/tags/showVars.ts +0 -28
- package/src/utils/choice-prompt.ts +0 -143
- package/src/utils/command-errors.ts +0 -186
- package/src/utils/command-help.ts +0 -45
- package/src/utils/confirm-prompt.ts +0 -110
- package/src/utils/flags.ts +0 -177
- package/src/utils/formatting.ts +0 -849
- package/src/utils/prompts.ts +0 -53
- package/src/utils/sous-directory.ts +0 -245
- package/src/utils/table.ts +0 -603
- package/src/utils/value-prompt.ts +0 -119
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Marking a class as injectable without decorator syntax.
|
|
3
|
-
*
|
|
4
|
-
* sous runs TypeScript through tsx, and the published `bin/run.js` registers
|
|
5
|
-
* tsx from the directory a command runs in, so the `experimentalDecorators`
|
|
6
|
-
* flag in this repository's tsconfig does not reach it; esbuild also emits no
|
|
7
|
-
* decorator metadata. Inversify's decorators are plain functions, though, so
|
|
8
|
-
* calling them here marks every constructor parameter with its token
|
|
9
|
-
* explicitly and needs neither a compiler flag nor `reflect-metadata`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { decorate, inject, injectable, multiInject } from "inversify";
|
|
13
|
-
|
|
14
|
-
/** One constructor parameter: a token bound once, or a token bound many times. */
|
|
15
|
-
export type ConstructorParameter = symbol | { multi: symbol };
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Marks a class injectable and names the token of every constructor parameter,
|
|
19
|
-
* in order.
|
|
20
|
-
*
|
|
21
|
-
* makeInjectable(RefParser, [{ multi: REF_TOKENS.Splitter }]);
|
|
22
|
-
*
|
|
23
|
-
* @param target - The class.
|
|
24
|
-
* @param parameters - The token of each constructor parameter, in order.
|
|
25
|
-
*/
|
|
26
|
-
export function makeInjectable(
|
|
27
|
-
target: new (...args: never[]) => unknown,
|
|
28
|
-
parameters: ConstructorParameter[] = []
|
|
29
|
-
): void {
|
|
30
|
-
decorate(injectable(), target);
|
|
31
|
-
parameters.forEach((parameter, index) => {
|
|
32
|
-
const decorator =
|
|
33
|
-
typeof parameter === "symbol" ? inject(parameter) : multiInject(parameter.multi);
|
|
34
|
-
decorate(decorator, target, index);
|
|
35
|
-
});
|
|
36
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Building a `RefLocation`: where a repository lives, in the form every ref
|
|
3
|
-
* and every machine-wide key agrees on.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { repoIdentity } from "../../lib/repos/identity.js";
|
|
7
|
-
import {
|
|
8
|
-
requireProvider,
|
|
9
|
-
type RepoProvider,
|
|
10
|
-
} from "../../lib/repos/providers/index.js";
|
|
11
|
-
import type { RefLocation } from "./types.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Where one repository lives, or undefined when its provider cannot take the
|
|
15
|
-
* path apart (a path of one segment, say).
|
|
16
|
-
*
|
|
17
|
-
* @param provider - The provider that reads the location.
|
|
18
|
-
* @param host - The repository's host.
|
|
19
|
-
* @param repoPath - The repository's path on that host.
|
|
20
|
-
*/
|
|
21
|
-
export function locationFor(
|
|
22
|
-
provider: RepoProvider,
|
|
23
|
-
host: string,
|
|
24
|
-
repoPath: string
|
|
25
|
-
): RefLocation | undefined {
|
|
26
|
-
const url = `https://${host}/${repoPath}`;
|
|
27
|
-
try {
|
|
28
|
-
return {
|
|
29
|
-
provider: provider.id,
|
|
30
|
-
host,
|
|
31
|
-
repoPath,
|
|
32
|
-
identity: repoIdentity(provider.canonicalize(url)),
|
|
33
|
-
url,
|
|
34
|
-
};
|
|
35
|
-
} catch {
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Where the repository at a configured URL lives, or undefined when no
|
|
42
|
-
* provider recognizes it. The location's `url` is the URL as the project
|
|
43
|
-
* configured it, which is what a person is shown.
|
|
44
|
-
*
|
|
45
|
-
* @param url - The repository's URL, as a project's config records it.
|
|
46
|
-
* @param providers - The providers to ask. Defaults to the built-ins.
|
|
47
|
-
*/
|
|
48
|
-
export function locationFromUrl(url: string, providers?: RepoProvider[]): RefLocation | undefined {
|
|
49
|
-
try {
|
|
50
|
-
const provider = requireProvider(url, undefined, providers);
|
|
51
|
-
const canonical = provider.canonicalize(url);
|
|
52
|
-
return {
|
|
53
|
-
provider: provider.id,
|
|
54
|
-
host: canonical.host,
|
|
55
|
-
repoPath: `${canonical.owner}/${canonical.name}`,
|
|
56
|
-
identity: repoIdentity(canonical),
|
|
57
|
-
url,
|
|
58
|
-
};
|
|
59
|
-
} catch {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import type { IndexFile } from "../../../lib/repos/formats/index-file.js";
|
|
2
|
-
import { locationFromUrl } from "../location.js";
|
|
3
|
-
import type { SousRef } from "../types.js";
|
|
4
|
-
import { CatalogMatcher, catalogRepoOfIndex, type CatalogRepo } from "./catalog-matcher.js";
|
|
5
|
-
import type { RefLookup, RefMatch } from "./ref-lookup.js";
|
|
6
|
-
|
|
7
|
-
/** What a `CachedIndexLookup` needs to know beyond the indexes themselves. */
|
|
8
|
-
export type CachedIndexLookupOptions = {
|
|
9
|
-
/**
|
|
10
|
-
* The repositories' short names, in the order their matches should be
|
|
11
|
-
* listed: the built-in repository first, then the ones the config names.
|
|
12
|
-
* Defaults to the order the indexes were added to the map.
|
|
13
|
-
*/
|
|
14
|
-
order?: readonly string[];
|
|
15
|
-
/**
|
|
16
|
-
* Where each repository lives, by short name. It is how a ref written as a
|
|
17
|
-
* location (a URL, a locator, a URL copied from the browser) finds the
|
|
18
|
-
* repository whatever short name this project gave it.
|
|
19
|
-
*/
|
|
20
|
-
urls?: Record<string, string | undefined>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Answers repositories, namespaces and recipes from the repository indexes
|
|
25
|
-
* already on this machine. It reads nothing, fetches nothing and never fails:
|
|
26
|
-
* a candidate no index publishes simply has no match.
|
|
27
|
-
*/
|
|
28
|
-
export class CachedIndexLookup implements RefLookup {
|
|
29
|
-
private readonly matcher: CatalogMatcher;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* @param indexes - Each repository's cached index, keyed by short name.
|
|
33
|
-
* @param options - The listing order, and where each repository lives.
|
|
34
|
-
*/
|
|
35
|
-
constructor(indexes: Map<string, IndexFile>, options: CachedIndexLookupOptions = {}) {
|
|
36
|
-
const order = options.order ?? [...indexes.keys()];
|
|
37
|
-
const repos: CatalogRepo[] = [];
|
|
38
|
-
|
|
39
|
-
for (const name of order) {
|
|
40
|
-
const index = indexes.get(name);
|
|
41
|
-
if (index === undefined) continue;
|
|
42
|
-
const url = options.urls?.[name];
|
|
43
|
-
const location = url === undefined ? undefined : locationFromUrl(url);
|
|
44
|
-
|
|
45
|
-
repos.push(catalogRepoOfIndex(index, { name, ...(location === undefined ? {} : { location }) }));
|
|
46
|
-
}
|
|
47
|
-
this.matcher = new CatalogMatcher(repos);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async find(candidate: SousRef): Promise<RefMatch[]> {
|
|
51
|
-
return this.matcher.match(candidate);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { SousRef } from "../types.js";
|
|
2
|
-
import { CatalogMatcher, type CatalogRepo } from "./catalog-matcher.js";
|
|
3
|
-
import type { RefLookup, RefMatch } from "./ref-lookup.js";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Answers repositories, namespaces and recipes from a catalog a caller has
|
|
7
|
-
* already built (which repositories it knows, and what each publishes). It
|
|
8
|
-
* reads nothing, fetches nothing and never fails.
|
|
9
|
-
*/
|
|
10
|
-
export class CatalogLookup implements RefLookup {
|
|
11
|
-
private readonly matcher: CatalogMatcher;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param repos - The repositories to search, in the order their matches are listed.
|
|
15
|
-
*/
|
|
16
|
-
constructor(repos: CatalogRepo[]) {
|
|
17
|
-
this.matcher = new CatalogMatcher(repos);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async find(candidate: SousRef): Promise<RefMatch[]> {
|
|
21
|
-
return this.matcher.match(candidate);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Matching repositories, namespaces and recipes against a catalog of what is
|
|
3
|
-
* published. Every lookup that knows repositories (cached indexes, fetched
|
|
4
|
-
* indexes, the definitions in play) builds a catalog and asks this.
|
|
5
|
-
*
|
|
6
|
-
* Names are matched the same way everywhere: the exact spelling first, then
|
|
7
|
-
* ignoring case, and a name may be a glob pattern. A match is returned in the
|
|
8
|
-
* spelling the catalog publishes.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { IndexFile } from "../../../lib/repos/formats/index-file.js";
|
|
12
|
-
import { matchName, type NameMatch } from "../glob.js";
|
|
13
|
-
import { refKey } from "../format.js";
|
|
14
|
-
import type { NamespaceRef, RecipeRef, RefLocation, RepoRef, SousRef } from "../types.js";
|
|
15
|
-
import type { RefMatch } from "./ref-lookup.js";
|
|
16
|
-
|
|
17
|
-
/** One repository, and what it publishes. */
|
|
18
|
-
export type CatalogRepo = {
|
|
19
|
-
/** The short name this project calls it, when it has one. */
|
|
20
|
-
name?: string;
|
|
21
|
-
/** Where it lives, which is how a ref written as a location finds it. */
|
|
22
|
-
location?: RefLocation;
|
|
23
|
-
/** Every namespace it publishes. */
|
|
24
|
-
namespaces: Array<{ name: string; description?: string }>;
|
|
25
|
-
/** Every recipe it publishes. */
|
|
26
|
-
recipes: Array<{ namespace: string; name: string; description?: string; path?: string }>;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The catalog entry for one repository's index.
|
|
31
|
-
*
|
|
32
|
-
* @param index - The repository's index.
|
|
33
|
-
* @param repo - What the project knows of the repository: its short name and where it lives.
|
|
34
|
-
*/
|
|
35
|
-
export function catalogRepoOfIndex(
|
|
36
|
-
index: IndexFile,
|
|
37
|
-
repo: { name?: string; location?: RefLocation } = {}
|
|
38
|
-
): CatalogRepo {
|
|
39
|
-
return {
|
|
40
|
-
...(repo.name === undefined ? {} : { name: repo.name }),
|
|
41
|
-
...(repo.location === undefined ? {} : { location: repo.location }),
|
|
42
|
-
namespaces: Object.entries(index.namespaces).map(([namespace, declared]) => ({
|
|
43
|
-
name: namespace,
|
|
44
|
-
...(declared?.description === undefined ? {} : { description: declared.description }),
|
|
45
|
-
})),
|
|
46
|
-
recipes: Object.entries(index.recipes).map(([key, recipe]) => {
|
|
47
|
-
const slash = key.indexOf("/");
|
|
48
|
-
return {
|
|
49
|
-
namespace: key.slice(0, slash),
|
|
50
|
-
name: key.slice(slash + 1),
|
|
51
|
-
path: recipe.path,
|
|
52
|
-
...(recipe.description === undefined ? {} : { description: recipe.description }),
|
|
53
|
-
};
|
|
54
|
-
}),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** The weaker of two matches: one folded part makes the whole thing folded. */
|
|
59
|
-
function both(left: NameMatch, right: NameMatch): NameMatch {
|
|
60
|
-
if (left === undefined || right === undefined) return undefined;
|
|
61
|
-
return left === "folded" || right === "folded" ? "folded" : "exact";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Matches refs against a catalog, repository by repository, in catalog order. */
|
|
65
|
-
export class CatalogMatcher {
|
|
66
|
-
/**
|
|
67
|
-
* @param repos - The repositories to search, in the order their matches are listed.
|
|
68
|
-
*/
|
|
69
|
-
constructor(private readonly repos: CatalogRepo[]) {}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Every repository, namespace or recipe in the catalog a candidate names.
|
|
73
|
-
* Candidates of any other kind match nothing.
|
|
74
|
-
*
|
|
75
|
-
* @param candidate - One reading of a written ref.
|
|
76
|
-
*/
|
|
77
|
-
match(candidate: SousRef): RefMatch[] {
|
|
78
|
-
const found: Array<RefMatch & { repoIndex: number }> = [];
|
|
79
|
-
|
|
80
|
-
this.repos.forEach((repo, repoIndex) => {
|
|
81
|
-
const add = (ref: SousRef, spelling: NameMatch): void => {
|
|
82
|
-
if (spelling === undefined) return;
|
|
83
|
-
found.push({ ref, exactSpelling: spelling === "exact", repoIndex });
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
switch (candidate.kind) {
|
|
87
|
-
case "repo": {
|
|
88
|
-
if (candidate.browsed !== undefined) {
|
|
89
|
-
for (const settled of this.settleBrowsed(candidate, repo)) add(settled, "exact");
|
|
90
|
-
} else {
|
|
91
|
-
add(...this.matchRepo(candidate, repo));
|
|
92
|
-
}
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
case "namespace": {
|
|
96
|
-
const qualifier = this.qualifies(candidate.repo, repo);
|
|
97
|
-
for (const namespace of repo.namespaces) {
|
|
98
|
-
const spelling = both(qualifier, matchName(candidate.name, namespace.name));
|
|
99
|
-
if (spelling === undefined) continue;
|
|
100
|
-
add(
|
|
101
|
-
this.carry(candidate, {
|
|
102
|
-
kind: "namespace",
|
|
103
|
-
name: namespace.name,
|
|
104
|
-
repo: this.knownRepo(repo),
|
|
105
|
-
...(namespace.description === undefined ? {} : { description: namespace.description }),
|
|
106
|
-
}),
|
|
107
|
-
spelling
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
break;
|
|
111
|
-
}
|
|
112
|
-
case "recipe": {
|
|
113
|
-
const qualifier = this.qualifies(candidate.namespace?.repo ?? candidate.repo, repo);
|
|
114
|
-
for (const recipe of repo.recipes) {
|
|
115
|
-
const inNamespace =
|
|
116
|
-
candidate.namespace === undefined
|
|
117
|
-
? "exact"
|
|
118
|
-
: matchName(candidate.namespace.name, recipe.namespace);
|
|
119
|
-
const spelling = both(
|
|
120
|
-
both(qualifier, inNamespace),
|
|
121
|
-
matchName(candidate.name, recipe.name)
|
|
122
|
-
);
|
|
123
|
-
if (spelling === undefined) continue;
|
|
124
|
-
add(this.carry(candidate, this.knownRecipe(repo, recipe)), spelling);
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
default:
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
return found
|
|
134
|
-
.sort((left, right) =>
|
|
135
|
-
left.repoIndex !== right.repoIndex
|
|
136
|
-
? left.repoIndex - right.repoIndex
|
|
137
|
-
: refKey(left.ref) < refKey(right.ref)
|
|
138
|
-
? -1
|
|
139
|
-
: refKey(left.ref) > refKey(right.ref)
|
|
140
|
-
? 1
|
|
141
|
-
: 0
|
|
142
|
-
)
|
|
143
|
-
.map(({ ref, exactSpelling }) => ({ ref, exactSpelling }));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** How a repository qualifier matches one catalog repository (no qualifier matches all). */
|
|
147
|
-
private qualifies(qualifier: RepoRef | undefined, repo: CatalogRepo): NameMatch {
|
|
148
|
-
if (qualifier === undefined) return "exact";
|
|
149
|
-
let result: NameMatch = "exact";
|
|
150
|
-
if (qualifier.name !== undefined) {
|
|
151
|
-
if (repo.name === undefined) return undefined;
|
|
152
|
-
result = matchName(qualifier.name, repo.name);
|
|
153
|
-
if (result === undefined) return undefined;
|
|
154
|
-
}
|
|
155
|
-
if (qualifier.location !== undefined) {
|
|
156
|
-
if (repo.location?.identity !== qualifier.location.identity) return undefined;
|
|
157
|
-
}
|
|
158
|
-
return result;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/** The repository ref a catalog repository is known as. */
|
|
162
|
-
private knownRepo(repo: CatalogRepo, revision?: string): RepoRef {
|
|
163
|
-
return {
|
|
164
|
-
kind: "repo",
|
|
165
|
-
...(repo.name === undefined ? {} : { name: repo.name }),
|
|
166
|
-
...(repo.location === undefined ? {} : { location: repo.location }),
|
|
167
|
-
...(revision === undefined ? {} : { revision }),
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** The recipe ref a catalog recipe is known as, parents and all. */
|
|
172
|
-
private knownRecipe(repo: CatalogRepo, recipe: CatalogRepo["recipes"][number], revision?: string): RecipeRef {
|
|
173
|
-
const namespace: NamespaceRef = {
|
|
174
|
-
kind: "namespace",
|
|
175
|
-
name: recipe.namespace,
|
|
176
|
-
repo: this.knownRepo(repo, revision),
|
|
177
|
-
};
|
|
178
|
-
return {
|
|
179
|
-
kind: "recipe",
|
|
180
|
-
name: recipe.name,
|
|
181
|
-
namespace,
|
|
182
|
-
...(recipe.description === undefined ? {} : { description: recipe.description }),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** What a candidate carries over to the known ref it matched: its range and query values. */
|
|
187
|
-
private carry(candidate: SousRef, known: SousRef): SousRef {
|
|
188
|
-
return {
|
|
189
|
-
...known,
|
|
190
|
-
...(candidate.kind === "recipe" && candidate.range !== undefined
|
|
191
|
-
? { range: candidate.range }
|
|
192
|
-
: {}),
|
|
193
|
-
...(candidate.vars === undefined ? {} : { vars: candidate.vars }),
|
|
194
|
-
} as SousRef;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** A repository ref, named by short name or by location. */
|
|
198
|
-
private matchRepo(candidate: RepoRef, repo: CatalogRepo): [SousRef, NameMatch] {
|
|
199
|
-
const known = this.knownRepo(repo);
|
|
200
|
-
if (candidate.name === undefined && candidate.location === undefined) return [known, undefined];
|
|
201
|
-
return [
|
|
202
|
-
{ ...known, ...(candidate.vars === undefined ? {} : { vars: candidate.vars }) },
|
|
203
|
-
this.qualifies(candidate, repo),
|
|
204
|
-
];
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* What a browser path names in one repository, through the folder each
|
|
209
|
-
* recipe lives in. The path still carries the branch in front of it (which
|
|
210
|
-
* may itself hold slashes), so every split is tried: the folder of a recipe,
|
|
211
|
-
* or anything inside it, names that recipe; a folder holding recipes of
|
|
212
|
-
* exactly one namespace names that namespace.
|
|
213
|
-
*
|
|
214
|
-
* settleBrowsed("main/recipes/workflow/alpha/SKILL.md")
|
|
215
|
-
* // -> the recipe workflow/alpha, with the revision "main"
|
|
216
|
-
*/
|
|
217
|
-
private settleBrowsed(candidate: RepoRef, repo: CatalogRepo): SousRef[] {
|
|
218
|
-
if (candidate.location === undefined || repo.location?.identity !== candidate.location.identity) {
|
|
219
|
-
return [];
|
|
220
|
-
}
|
|
221
|
-
const segments = (candidate.browsed ?? "").split("/").filter((segment) => segment.length > 0);
|
|
222
|
-
const recipes = repo.recipes
|
|
223
|
-
.map((recipe) => ({ recipe, path: (recipe.path ?? "").replace(/^\.?\/+|\/+$/g, "") }))
|
|
224
|
-
.filter((entry) => entry.path.length > 0);
|
|
225
|
-
|
|
226
|
-
const range = candidate.range === undefined ? {} : { range: candidate.range };
|
|
227
|
-
const found = new Map<string, SousRef>();
|
|
228
|
-
|
|
229
|
-
// A recipe's folder, or a file inside it. The deepest recipe wins, so a
|
|
230
|
-
// recipe nested inside another's folder is named by its own path.
|
|
231
|
-
for (let start = 1; start < segments.length; start++) {
|
|
232
|
-
const folder = segments.slice(start).join("/");
|
|
233
|
-
const holding = recipes
|
|
234
|
-
.filter((entry) => folder === entry.path || folder.startsWith(`${entry.path}/`))
|
|
235
|
-
.sort((left, right) => right.path.length - left.path.length);
|
|
236
|
-
const deepest = holding[0];
|
|
237
|
-
if (deepest === undefined) continue;
|
|
238
|
-
const revision = segments.slice(0, start).join("/");
|
|
239
|
-
found.set(`${deepest.recipe.namespace}/${deepest.recipe.name}`, {
|
|
240
|
-
...this.knownRecipe(repo, deepest.recipe, revision),
|
|
241
|
-
...range,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
if (found.size > 0) return [...found.values()];
|
|
245
|
-
|
|
246
|
-
// A folder above recipes: a namespace, when everything under it is one.
|
|
247
|
-
for (let start = 1; start < segments.length; start++) {
|
|
248
|
-
const folder = segments.slice(start).join("/");
|
|
249
|
-
const under = recipes.filter((entry) => entry.path.startsWith(`${folder}/`));
|
|
250
|
-
const namespaces = new Set(under.map((entry) => entry.recipe.namespace));
|
|
251
|
-
if (namespaces.size !== 1) continue;
|
|
252
|
-
const name = [...namespaces][0]!;
|
|
253
|
-
found.set(name, {
|
|
254
|
-
kind: "namespace",
|
|
255
|
-
name,
|
|
256
|
-
repo: this.knownRepo(repo, segments.slice(0, start).join("/")),
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
return [...found.values()];
|
|
260
|
-
}
|
|
261
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { SousRef } from "../types.js";
|
|
2
|
-
import type { RefLookup, RefMatch } from "./ref-lookup.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Asks several lookups in order. The first one that answers a candidate with a
|
|
6
|
-
* non-empty list wins and the rest are never asked, so a cheap, authoritative
|
|
7
|
-
* source (a release's record) can sit in front of a broad one (every cached
|
|
8
|
-
* index). A lookup that throws stops the chain: a failure is never skipped.
|
|
9
|
-
*/
|
|
10
|
-
export class ChainedLookup implements RefLookup {
|
|
11
|
-
private readonly lookups: RefLookup[];
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @param lookups - The lookups to ask, most authoritative first.
|
|
15
|
-
*/
|
|
16
|
-
constructor(...lookups: RefLookup[]) {
|
|
17
|
-
this.lookups = lookups;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async find(candidate: SousRef): Promise<RefMatch[]> {
|
|
21
|
-
for (const lookup of this.lookups) {
|
|
22
|
-
const matches = await lookup.find(candidate);
|
|
23
|
-
if (matches.length > 0) return matches;
|
|
24
|
-
}
|
|
25
|
-
return [];
|
|
26
|
-
}
|
|
27
|
-
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import type { DefinedVariable } from "../../../lib/vars/definition-source.js";
|
|
2
|
-
import { variableCandidates, type LadderContext } from "../../../lib/vars/ladder.js";
|
|
3
|
-
import { bareName } from "../../../lib/vars/names.js";
|
|
4
|
-
import type { SousRef, VariableRef } from "../types.js";
|
|
5
|
-
import type { RefLookup, RefMatch } from "./ref-lookup.js";
|
|
6
|
-
import { variableRefOf } from "./variable-lookup.js";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Answers an environment variable name with the variables it answers.
|
|
10
|
-
*
|
|
11
|
-
* A name is held whole and compared in its exact spelling only, because a
|
|
12
|
-
* variable (`apiUrl`) and an environment variable name can differ only in case
|
|
13
|
-
* and the spelling typed is the one meant. The names searched are the one each
|
|
14
|
-
* definition declares (a recipe may bind an existing variable such as
|
|
15
|
-
* `GITHUB_TOKEN`) plus every generated name on the resolution ladder that is
|
|
16
|
-
* actually in use in `.sous/.env` or `.sous/.env.local`; generated names are
|
|
17
|
-
* only searched when in use, because every variable generates several and a
|
|
18
|
-
* project would otherwise be told that a name nothing has ever set names one of
|
|
19
|
-
* its variables.
|
|
20
|
-
*/
|
|
21
|
-
export class EnvVarLookup implements RefLookup {
|
|
22
|
-
private readonly byName = new Map<string, VariableRef[]>();
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* @param variables - The variable definitions in play.
|
|
26
|
-
* @param ladder - The environment layers, which decide which generated names are in use.
|
|
27
|
-
*/
|
|
28
|
-
constructor(variables: DefinedVariable[], ladder?: LadderContext) {
|
|
29
|
-
const inUse =
|
|
30
|
-
ladder === undefined
|
|
31
|
-
? new Set<string>()
|
|
32
|
-
: new Set([...Object.keys(ladder.localEnv), ...Object.keys(ladder.sharedEnv)]);
|
|
33
|
-
|
|
34
|
-
for (const defined of variables) {
|
|
35
|
-
const names = new Set<string>([bareName(defined.definition)]);
|
|
36
|
-
if (ladder !== undefined) {
|
|
37
|
-
for (const candidate of variableCandidates(defined, ladder)) {
|
|
38
|
-
if (inUse.has(candidate.envName)) names.add(candidate.envName);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
for (const name of names) {
|
|
42
|
-
const answered = this.byName.get(name) ?? [];
|
|
43
|
-
answered.push(variableRefOf(defined));
|
|
44
|
-
this.byName.set(name, answered);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async find(candidate: SousRef): Promise<RefMatch[]> {
|
|
50
|
-
if (candidate.kind !== "envVar") return [];
|
|
51
|
-
const variables = this.byName.get(candidate.name);
|
|
52
|
-
if (variables === undefined) return [];
|
|
53
|
-
return [
|
|
54
|
-
{
|
|
55
|
-
ref: {
|
|
56
|
-
kind: "envVar",
|
|
57
|
-
name: candidate.name,
|
|
58
|
-
variables,
|
|
59
|
-
...(candidate.vars === undefined ? {} : { vars: candidate.vars }),
|
|
60
|
-
},
|
|
61
|
-
exactSpelling: true,
|
|
62
|
-
},
|
|
63
|
-
];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Settling, at release time, a dependency that reads more than one way.
|
|
3
|
-
*
|
|
4
|
-
* Most dependencies say exactly what they mean: `workflow/alpha`, or
|
|
5
|
-
* `github://owner/repo/workflow/alpha`. Two kinds do not: a GitLab URL with
|
|
6
|
-
* nested groups, which does not say where the project path ends, and a browser
|
|
7
|
-
* URL copied from a host's file view, which names a folder whose recipe only
|
|
8
|
-
* that repository's index can say.
|
|
9
|
-
*
|
|
10
|
-
* `sous repo release` settles each of them once, by fetching the index of every
|
|
11
|
-
* candidate repository through the provider layer and keeping the reading whose
|
|
12
|
-
* index publishes what was named. A network failure fails the release: a
|
|
13
|
-
* release never falls through to the next reading, because the reading it fell
|
|
14
|
-
* through to could be the wrong one. Two readings that both publish what is
|
|
15
|
-
* named are a genuine tie, and an error naming the spelling that reads one way.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { ConfigError } from "../../../lib/errors.js";
|
|
19
|
-
import { INDEX_FILENAME } from "../../../lib/repos/formats/common.js";
|
|
20
|
-
import { parseIndexFile, type IndexFile } from "../../../lib/repos/formats/index-file.js";
|
|
21
|
-
import {
|
|
22
|
-
builtInProviders,
|
|
23
|
-
providerById,
|
|
24
|
-
type ProviderOptions,
|
|
25
|
-
type RepoProvider,
|
|
26
|
-
} from "../../../lib/repos/providers/index.js";
|
|
27
|
-
import { formatRef } from "../format.js";
|
|
28
|
-
import { locationOf } from "../parts.js";
|
|
29
|
-
import type { RefLocation, SousRef } from "../types.js";
|
|
30
|
-
import { CatalogMatcher, catalogRepoOfIndex } from "./catalog-matcher.js";
|
|
31
|
-
import type { RefLookup, RefMatch } from "./ref-lookup.js";
|
|
32
|
-
|
|
33
|
-
/** Fetches the index of the repository at a location. */
|
|
34
|
-
export type IndexFetcher = (location: RefLocation) => Promise<IndexFile>;
|
|
35
|
-
|
|
36
|
-
/** What a `FetchedIndexLookup` may be given. */
|
|
37
|
-
export type FetchedIndexLookupOptions = {
|
|
38
|
-
/** The providers to fetch through. Defaults to the built-ins. */
|
|
39
|
-
providers?: RepoProvider[];
|
|
40
|
-
/** Testing seams handed to every index fetch by the default fetcher. */
|
|
41
|
-
providerOptions?: ProviderOptions;
|
|
42
|
-
/** Replaces the default fetcher, which goes through the provider layer. */
|
|
43
|
-
fetchIndex?: IndexFetcher;
|
|
44
|
-
/** What the ref being settled is called in an error, such as "dependency". Defaults to "ref". */
|
|
45
|
-
subject?: string;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** A failure, in a sentence. */
|
|
49
|
-
function describe(error: unknown): string {
|
|
50
|
-
return error instanceof Error ? error.message : String(error);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Answers a located candidate by fetching the index of the repository it names
|
|
55
|
-
* (once per repository) and matching the candidate against it. A candidate
|
|
56
|
-
* with no location names no repository to fetch from and matches nothing.
|
|
57
|
-
*/
|
|
58
|
-
export class FetchedIndexLookup implements RefLookup {
|
|
59
|
-
private readonly providers: RepoProvider[];
|
|
60
|
-
private readonly fetcher: IndexFetcher;
|
|
61
|
-
private readonly indexes = new Map<string, Promise<IndexFile>>();
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @param options - The providers and testing seams, or a replacement fetcher.
|
|
65
|
-
*/
|
|
66
|
-
constructor(private readonly options: FetchedIndexLookupOptions = {}) {
|
|
67
|
-
this.providers = options.providers ?? builtInProviders();
|
|
68
|
-
this.fetcher = options.fetchIndex ?? ((location) => this.fetchThroughProvider(location));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async find(candidate: SousRef): Promise<RefMatch[]> {
|
|
72
|
-
const location = locationOf(candidate);
|
|
73
|
-
if (location === undefined) return [];
|
|
74
|
-
|
|
75
|
-
let index: IndexFile;
|
|
76
|
-
try {
|
|
77
|
-
index = await this.indexAt(location);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
const subject = this.options.subject ?? "ref";
|
|
80
|
-
const folder = candidate.kind === "repo" && candidate.browsed !== undefined;
|
|
81
|
-
throw new ConfigError(
|
|
82
|
-
`the ${subject} reads ${folder ? "as a folder in" : "as something in"} the repository ` +
|
|
83
|
-
`at ${location.url}, and its index could not be read, so the release cannot settle ` +
|
|
84
|
-
`what it means. A release never guesses past an index it could not read.\n` +
|
|
85
|
-
` ${describe(error)}`
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return new CatalogMatcher([catalogRepoOfIndex(index, { location })]).match(candidate);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The one reading of a written ref that the fetched indexes confirm: every
|
|
94
|
-
* reading is looked up, and exactly one may publish what it names.
|
|
95
|
-
*
|
|
96
|
-
* @param candidates - Every reading of the written ref.
|
|
97
|
-
* @returns The one match.
|
|
98
|
-
* @throws A ConfigError when no repository publishes it, when several do (naming the
|
|
99
|
-
* `/*` spelling that reads one way), or when an index could not be fetched.
|
|
100
|
-
*/
|
|
101
|
-
async settle(candidates: SousRef[]): Promise<RefMatch> {
|
|
102
|
-
const choices: RefMatch[] = [];
|
|
103
|
-
for (const candidate of candidates) choices.push(...(await this.find(candidate)));
|
|
104
|
-
|
|
105
|
-
if (choices.length === 0) {
|
|
106
|
-
throw new ConfigError(
|
|
107
|
-
`no repository it could name publishes what it names. It was read as:\n` +
|
|
108
|
-
candidates.map((candidate) => ` ${formatRef(candidate, this.providers)}`).join("\n")
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
if (choices.length > 1) {
|
|
112
|
-
const namespace = choices.find((choice) => choice.ref.kind === "namespace") ?? choices[0]!;
|
|
113
|
-
const written = formatRef(namespace.ref, this.providers);
|
|
114
|
-
throw new ConfigError(
|
|
115
|
-
`it names more than one thing, and each of these publishes it:\n` +
|
|
116
|
-
choices.map((choice) => ` ${formatRef(choice.ref, this.providers)}`).join("\n") +
|
|
117
|
-
`\n Write the one you mean. A namespace is written with '/*' after it, as in ` +
|
|
118
|
-
`'${namespace.ref.kind === "namespace" ? `${written}/*` : written}', and a recipe as ` +
|
|
119
|
-
`its canonical locator above.`
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
return choices[0]!;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* The index at a location, fetched once per repository.
|
|
127
|
-
*
|
|
128
|
-
* @param location - Where the repository lives.
|
|
129
|
-
*/
|
|
130
|
-
indexAt(location: RefLocation): Promise<IndexFile> {
|
|
131
|
-
let pending = this.indexes.get(location.identity);
|
|
132
|
-
if (pending === undefined) {
|
|
133
|
-
pending = this.fetcher(location);
|
|
134
|
-
this.indexes.set(location.identity, pending);
|
|
135
|
-
}
|
|
136
|
-
return pending;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/** Fetches and validates the index at a location, through its provider. */
|
|
140
|
-
private async fetchThroughProvider(location: RefLocation): Promise<IndexFile> {
|
|
141
|
-
const provider = providerById(location.provider, this.providers);
|
|
142
|
-
/* c8 ignore next */
|
|
143
|
-
if (provider === undefined) throw new Error(`sous has no '${location.provider}' provider.`);
|
|
144
|
-
const fetched = await provider.fetchIndex(
|
|
145
|
-
provider.canonicalize(location.url),
|
|
146
|
-
this.options.providerOptions ?? {}
|
|
147
|
-
);
|
|
148
|
-
return parseIndexFile(JSON.parse(fetched.text), `${location.url}/${INDEX_FILENAME}`);
|
|
149
|
-
}
|
|
150
|
-
}
|