@sous-io/sous 0.1.1 → 0.2.1
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 +115 -35
- package/bin/run.js +10 -1
- package/docs/markdown/README.md +27 -0
- package/docs/markdown/_sidebar.md +18 -0
- package/docs/markdown/commands.md +308 -0
- package/docs/markdown/config-discovery.md +74 -0
- package/docs/markdown/config-inspection.md +69 -0
- package/docs/markdown/config-layers.md +92 -0
- package/docs/markdown/config-variables.md +79 -0
- package/docs/markdown/configuration.md +71 -0
- package/docs/markdown/design-principles.md +59 -0
- package/docs/markdown/repositories-authoring.md +409 -0
- package/docs/markdown/repositories-consuming.md +580 -0
- package/docs/markdown/repositories-file-formats.md +1084 -0
- package/docs/markdown/repositories-variables.md +387 -0
- package/docs/markdown/repositories.md +303 -0
- package/docs/markdown/skill-categories.md +58 -0
- package/package.json +72 -8
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/SKILL.tpl.md +20 -20
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/about-something.md +2 -2
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/examples/do-something.md +1 -1
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/advanced-patterns.md +6 -6
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/commands.md +5 -5
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/frontmatter.md +3 -3
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/SKILL.tpl.md +40 -25
- package/recipes/core/sous-skills/skills/about-sous/SKILL.tpl.md +70 -0
- package/recipes/core/sous-skills/skills/about-sous-configuration/SKILL.tpl.md +75 -0
- package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/create-skill/SKILL.tpl.md +8 -9
- package/recipes/core/sous-skills/sous.recipe.yaml +45 -0
- package/sous.config.schema.json +337 -0
- package/src/base-command.ts +220 -67
- package/src/commands/build.ts +150 -73
- package/src/commands/clear.ts +23 -15
- package/src/commands/compile.ts +74 -16
- package/src/commands/config/get.ts +110 -0
- package/src/commands/config/show.ts +32 -0
- package/src/commands/config/validate.ts +53 -0
- package/src/commands/help.ts +46 -0
- package/src/commands/launch.ts +36 -14
- package/src/commands/lock/rebuild.ts +241 -0
- package/src/commands/lock/show.ts +115 -0
- package/src/commands/namespace/list.ts +117 -0
- package/src/commands/namespace/show.ts +110 -0
- package/src/commands/prune.ts +3 -11
- package/src/commands/recipe/list.ts +95 -0
- package/src/commands/recipe/show.ts +301 -0
- package/src/commands/repo/add.ts +145 -0
- package/src/commands/repo/gc.ts +172 -0
- package/src/commands/repo/init.ts +136 -0
- package/src/commands/repo/link.ts +500 -0
- package/src/commands/repo/list.ts +179 -0
- package/src/commands/repo/release.ts +625 -0
- package/src/commands/repo/remove.ts +193 -0
- package/src/commands/repo/search.ts +189 -0
- package/src/commands/repo/submit.ts +133 -0
- package/src/commands/repo/unlink.ts +147 -0
- package/src/commands/subscription/add.ts +285 -0
- package/src/commands/subscription/list.ts +129 -0
- package/src/commands/subscription/remove.ts +181 -0
- package/src/commands/vars/ask.ts +374 -0
- package/src/commands/vars/index.ts +79 -0
- package/src/commands/vars/list.ts +67 -0
- package/src/commands/vars/show.ts +77 -0
- package/src/config-command.ts +30 -0
- package/src/lib/build-service.ts +206 -54
- package/src/lib/config-discovery.ts +220 -27
- package/src/lib/config-inspect.ts +145 -0
- package/src/lib/config-kernel.mjs +377 -0
- package/src/lib/config-schema.ts +361 -0
- package/src/lib/env-file.ts +328 -0
- package/src/lib/env-local.ts +18 -1
- package/src/lib/errors.ts +32 -0
- package/src/lib/include-resolver.ts +108 -15
- package/src/lib/interactive.ts +165 -0
- package/src/lib/markdown-compiler.ts +118 -37
- package/src/lib/package-info.ts +25 -0
- package/src/lib/pid-service.ts +32 -21
- package/src/lib/refs/find.ts +589 -0
- package/src/lib/refs/index.ts +12 -0
- package/src/lib/refs/pick.ts +147 -0
- package/src/lib/refs/scopes.ts +61 -0
- package/src/lib/repos/catalog-display.ts +116 -0
- package/src/lib/repos/catalog-inputs.ts +160 -0
- package/src/lib/repos/catalog.ts +722 -0
- package/src/lib/repos/core-recipe.ts +105 -0
- package/src/lib/repos/defaults.ts +175 -0
- package/src/lib/repos/formats/common.ts +389 -0
- package/src/lib/repos/formats/index-file.ts +215 -0
- package/src/lib/repos/formats/links-map.ts +96 -0
- package/src/lib/repos/formats/lockfile.ts +167 -0
- package/src/lib/repos/formats/patterns.ts +57 -0
- package/src/lib/repos/formats/recipe-manifest.ts +395 -0
- package/src/lib/repos/formats/repo-manifest.ts +88 -0
- package/src/lib/repos/formats/store-entry.ts +84 -0
- package/src/lib/repos/freshness.ts +208 -0
- package/src/lib/repos/git-clone.ts +312 -0
- package/src/lib/repos/identity.ts +89 -0
- package/src/lib/repos/index.ts +58 -0
- package/src/lib/repos/links.ts +353 -0
- package/src/lib/repos/load-manifest.ts +236 -0
- package/src/lib/repos/lock-service.ts +453 -0
- package/src/lib/repos/locked-namespace-resolver.ts +90 -0
- package/src/lib/repos/locked-recipes.ts +254 -0
- package/src/lib/repos/managed-layer.ts +422 -0
- package/src/lib/repos/namespace-resolver.ts +370 -0
- package/src/lib/repos/providers/base.ts +206 -0
- package/src/lib/repos/providers/git.ts +233 -0
- package/src/lib/repos/providers/github.ts +294 -0
- package/src/lib/repos/providers/gitlab.ts +263 -0
- package/src/lib/repos/providers/http.ts +102 -0
- package/src/lib/repos/providers/index-cache.ts +382 -0
- package/src/lib/repos/providers/index.ts +106 -0
- package/src/lib/repos/providers/local.ts +391 -0
- package/src/lib/repos/providers/provider.ts +401 -0
- package/src/lib/repos/recipe-config-layers.ts +287 -0
- package/src/lib/repos/recipe-targets.ts +223 -0
- package/src/lib/repos/ref-search.ts +46 -0
- package/src/lib/repos/ref.ts +513 -0
- package/src/lib/repos/reference-report.ts +122 -0
- package/src/lib/repos/release/bump.ts +161 -0
- package/src/lib/repos/release/git-state.ts +305 -0
- package/src/lib/repos/release/index-builder.ts +635 -0
- package/src/lib/repos/release/index.ts +16 -0
- package/src/lib/repos/release/plan.ts +512 -0
- package/src/lib/repos/release/submit-service.ts +496 -0
- package/src/lib/repos/release/tags.ts +243 -0
- package/src/lib/repos/release/validate.ts +463 -0
- package/src/lib/repos/resolver.ts +789 -0
- package/src/lib/repos/scaffold/index.ts +238 -0
- package/src/lib/repos/scaffold/templates.ts +415 -0
- package/src/lib/repos/seed.ts +414 -0
- package/src/lib/repos/store/contract.ts +64 -0
- package/src/lib/repos/store/hash.ts +114 -0
- package/src/lib/repos/store/recipe-store.ts +599 -0
- package/src/lib/repos/store/settings.ts +58 -0
- package/src/lib/repos/subscription-service.ts +2678 -0
- package/src/lib/repos/trust.ts +447 -0
- package/src/lib/settings.ts +546 -189
- package/src/lib/sous-home.ts +104 -0
- package/src/lib/state.ts +52 -20
- package/src/lib/vars/ask.ts +1152 -0
- package/src/lib/vars/definition-source.ts +252 -0
- package/src/lib/vars/display.ts +233 -0
- package/src/lib/vars/index.ts +18 -0
- package/src/lib/vars/ladder.ts +282 -0
- package/src/lib/vars/mappings.ts +265 -0
- package/src/lib/vars/names.ts +94 -0
- package/src/lib/vars/preanswers.ts +395 -0
- package/src/lib/vars/question-plan.ts +218 -0
- package/src/lib/vars/report.ts +228 -0
- package/src/lib/vars/safe-regex.ts +235 -0
- package/src/lib/vars/validate.ts +312 -0
- package/src/lib/watch-loop.ts +148 -0
- package/src/templating/init-liquid-engine.ts +58 -16
- package/src/utils/choice-prompt.ts +143 -0
- package/src/utils/command-errors.ts +186 -0
- package/src/utils/command-help.ts +45 -0
- package/src/utils/confirm-prompt.ts +110 -0
- package/src/utils/flags.ts +153 -0
- package/src/utils/formatting.ts +540 -55
- package/src/utils/prompts.ts +35 -1
- package/src/utils/sous-directory.ts +245 -0
- package/src/utils/table.ts +603 -0
- package/src/utils/value-prompt.ts +119 -0
- package/shared-prompts/_partials/resume-task.md +0 -51
- package/shared-prompts/_partials/sub-agent-delegation.md +0 -32
- package/shared-prompts/_partials/update-task-file.md +0 -52
- package/shared-prompts/memories/automated-browser-tasks/INDEX.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/SKILL.tpl.md +0 -102
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/auth-failure-handling.mjs +0 -81
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/chained-workflow.mjs +0 -126
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/examples/simple-fetch.mjs +0 -92
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/architecture.md +0 -61
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/auth-and-sessions.md +0 -65
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/ctx-api.md +0 -96
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/installation.md +0 -104
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/references/script-conventions.md +0 -243
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/chrome-state.mjs +0 -148
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.mjs +0 -383
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/debug.spec.mjs +0 -267
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/eslint.config.mjs +0 -56
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/harness.mjs +0 -169
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/keyring.mjs +0 -59
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/logger.mjs +0 -25
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/params.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/run.mjs +0 -140
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/settings.tpl.mjs +0 -1
- package/shared-prompts/skills/automated-browser-tasks/about-automated-browser-tasks/scripts/utils.mjs +0 -185
- package/shared-prompts/skills/automated-browser-tasks/create-automated-browser-task/SKILL.tpl.md +0 -52
- package/shared-prompts/skills/automated-browser-tasks/running-automated-browser-tasks/SKILL.tpl.md +0 -59
- package/shared-prompts/skills/automated-browser-tasks/update-automated-browser-task/SKILL.tpl.md +0 -47
- package/shared-prompts/skills/control-flow/approve/SKILL.tpl.md +0 -26
- package/shared-prompts/skills/control-flow/opine/SKILL.tpl.md +0 -58
- package/shared-prompts/skills/control-flow/repeat/SKILL.tpl.md +0 -27
- package/shared-prompts/skills/control-flow/research/SKILL.tpl.md +0 -34
- package/shared-prompts/skills/sous-skills/about-sous/SKILL.tpl.md +0 -51
- package/shared-prompts/skills/task-files/about-task-files/SKILL.tpl.md +0 -122
- package/shared-prompts/skills/task-files/continue-task-in-new-branch/SKILL.tpl.md +0 -80
- package/shared-prompts/skills/task-files/go/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/resume-task/SKILL.tpl.md +0 -13
- package/shared-prompts/skills/task-files/start-task/SKILL.tpl.md +0 -93
- package/shared-prompts/skills/task-files/update/SKILL.tpl.md +0 -14
- package/shared-prompts/skills/task-files/update-task-file/SKILL.tpl.md +0 -13
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-agent-skills/references/substitutions.md +0 -0
- /package/{shared-prompts/skills/sous-skills → recipes/core/sous-skills/skills}/about-liquid-templates/references/liquid-filters.md +0 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ref parser.
|
|
3
|
+
*
|
|
4
|
+
* A "ref" is how everything in the Repositories system names a namespace or a
|
|
5
|
+
* recipe: on the command line, in a project's subscriptions, and in a recipe
|
|
6
|
+
* manifest's `depends` and `subscribes` lists. The grammar is deliberately
|
|
7
|
+
* small and prefix-free:
|
|
8
|
+
*
|
|
9
|
+
* ref := [ repo ":" ] namespace [ "/" recipe [ "@" range ] ]
|
|
10
|
+
*
|
|
11
|
+
* workflow a whole namespace
|
|
12
|
+
* workflow/task-files one recipe, any version
|
|
13
|
+
* workflow/task-files@^1.2.0 one recipe, constrained
|
|
14
|
+
* sous-recipes:workflow/task-files the same recipe in a named repo
|
|
15
|
+
*
|
|
16
|
+
* The repo qualifier is only needed when the same ref resolves in more than one
|
|
17
|
+
* added repo; refs otherwise resolve across every added repo's cached index.
|
|
18
|
+
* A version range applies to a recipe, never to a namespace, because namespaces
|
|
19
|
+
* are not versioned.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import semver from "semver";
|
|
23
|
+
import { ConfigError } from "../errors.js";
|
|
24
|
+
import {
|
|
25
|
+
NAMESPACE_NAME_PATTERN,
|
|
26
|
+
RECIPE_NAME_PATTERN,
|
|
27
|
+
REPO_NAME_PATTERN,
|
|
28
|
+
} from "./formats/patterns.js";
|
|
29
|
+
|
|
30
|
+
/** A parsed ref. `recipe` is absent for a namespace ref; `range` needs a recipe. */
|
|
31
|
+
export type ParsedRef = {
|
|
32
|
+
/** The repo short name from a `repo:` qualifier, when one was given. */
|
|
33
|
+
repo?: string;
|
|
34
|
+
/** The namespace. Always present. */
|
|
35
|
+
namespace: string;
|
|
36
|
+
/** The recipe name, when the ref names a recipe rather than a whole namespace. */
|
|
37
|
+
recipe?: string;
|
|
38
|
+
/** The semantic version range, when one was given. Only legal with a recipe. */
|
|
39
|
+
range?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** The one-line reminder appended to every ref error. */
|
|
43
|
+
const SYNTAX_HELP =
|
|
44
|
+
"A ref is written as 'namespace', 'namespace/recipe', 'namespace/recipe@<range>' or " +
|
|
45
|
+
"'repo:namespace/recipe@<range>'.";
|
|
46
|
+
|
|
47
|
+
/** Builds a ConfigError that quotes the offending input and shows the grammar. */
|
|
48
|
+
function refError(input: string, problem: string): ConfigError {
|
|
49
|
+
return new ConfigError(`Invalid ref '${input}': ${problem}\n ${SYNTAX_HELP}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parses a ref string into its parts, throwing a ConfigError that quotes the
|
|
54
|
+
* input and shows the grammar when it does not fit.
|
|
55
|
+
*
|
|
56
|
+
* @param input - The ref as written by a user or a manifest.
|
|
57
|
+
*/
|
|
58
|
+
export function parseRef(input: string): ParsedRef {
|
|
59
|
+
if (typeof input !== "string") {
|
|
60
|
+
throw refError(String(input), "a ref must be a string.");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const trimmed = input.trim();
|
|
64
|
+
if (trimmed.length === 0) {
|
|
65
|
+
throw refError(input, "a ref must not be empty.");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (trimmed.startsWith("@")) {
|
|
69
|
+
throw refError(
|
|
70
|
+
input,
|
|
71
|
+
"refs take no '@' prefix. The '@' character introduces a version range only, " +
|
|
72
|
+
"as in 'workflow/task-files@^1.2.0'."
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (trimmed.startsWith("~")) {
|
|
76
|
+
throw refError(
|
|
77
|
+
input,
|
|
78
|
+
"refs take no '~' prefix. The '~' sigil belongs to template include lines " +
|
|
79
|
+
"('@~workflow/file.md'); a ref itself is written without it."
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Split the version range off first, so the rest is pure path and qualifier.
|
|
84
|
+
let body = trimmed;
|
|
85
|
+
let range: string | undefined;
|
|
86
|
+
const atIndex = body.indexOf("@");
|
|
87
|
+
if (atIndex !== -1) {
|
|
88
|
+
if (body.indexOf("@", atIndex + 1) !== -1) {
|
|
89
|
+
throw refError(input, "a ref may carry at most one '@' version range.");
|
|
90
|
+
}
|
|
91
|
+
range = body.slice(atIndex + 1).trim();
|
|
92
|
+
body = body.slice(0, atIndex);
|
|
93
|
+
if (range.length === 0) {
|
|
94
|
+
throw refError(input, "the '@' is not followed by a version range.");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Then the repo qualifier.
|
|
99
|
+
let repo: string | undefined;
|
|
100
|
+
const colonIndex = body.indexOf(":");
|
|
101
|
+
if (colonIndex !== -1) {
|
|
102
|
+
if (body.indexOf(":", colonIndex + 1) !== -1) {
|
|
103
|
+
throw refError(input, "a ref may carry at most one 'repo:' qualifier.");
|
|
104
|
+
}
|
|
105
|
+
repo = body.slice(0, colonIndex);
|
|
106
|
+
body = body.slice(colonIndex + 1);
|
|
107
|
+
if (repo.length === 0) {
|
|
108
|
+
throw refError(input, "the repo qualifier before ':' is empty.");
|
|
109
|
+
}
|
|
110
|
+
if (!REPO_NAME_PATTERN.test(repo)) {
|
|
111
|
+
throw refError(
|
|
112
|
+
input,
|
|
113
|
+
`the repo qualifier '${repo}' must be lowercase kebab-case: a letter, then ` +
|
|
114
|
+
"letters, digits or hyphens."
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// What is left is the namespace, optionally followed by a recipe.
|
|
120
|
+
const segments = body.split("/");
|
|
121
|
+
if (segments.length > 2) {
|
|
122
|
+
throw refError(
|
|
123
|
+
input,
|
|
124
|
+
"a ref has at most two path segments, a namespace and a recipe."
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [namespace, recipe] = segments;
|
|
129
|
+
if (namespace === undefined || namespace.length === 0) {
|
|
130
|
+
throw refError(input, "the namespace is empty.");
|
|
131
|
+
}
|
|
132
|
+
if (!NAMESPACE_NAME_PATTERN.test(namespace)) {
|
|
133
|
+
throw refError(
|
|
134
|
+
input,
|
|
135
|
+
`the namespace '${namespace}' must be lowercase kebab-case: a letter, then ` +
|
|
136
|
+
"letters, digits or hyphens."
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (recipe !== undefined) {
|
|
141
|
+
if (recipe.length === 0) {
|
|
142
|
+
throw refError(input, "the recipe name after '/' is empty.");
|
|
143
|
+
}
|
|
144
|
+
if (!RECIPE_NAME_PATTERN.test(recipe)) {
|
|
145
|
+
throw refError(
|
|
146
|
+
input,
|
|
147
|
+
`the recipe name '${recipe}' must be lowercase kebab-case: a letter, then ` +
|
|
148
|
+
"letters, digits or hyphens."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (range !== undefined) {
|
|
154
|
+
if (recipe === undefined) {
|
|
155
|
+
throw refError(
|
|
156
|
+
input,
|
|
157
|
+
"a version range applies to a recipe, and namespaces are not versioned. " +
|
|
158
|
+
"Name a recipe, as in 'workflow/task-files@^1.2.0'."
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (semver.validRange(range) === null) {
|
|
162
|
+
throw refError(
|
|
163
|
+
input,
|
|
164
|
+
`'${range}' is not a version range. Ranges follow npm's rules, such as ` +
|
|
165
|
+
"'^1.2.0', '~2.1', '>=1.0.0 <2.0.0' or '*'."
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parsed: ParsedRef = { namespace };
|
|
171
|
+
if (repo !== undefined) parsed.repo = repo;
|
|
172
|
+
if (recipe !== undefined) parsed.recipe = recipe;
|
|
173
|
+
if (range !== undefined) parsed.range = range;
|
|
174
|
+
return parsed;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Parses a ref, returning undefined instead of throwing. Use this where a bad
|
|
179
|
+
* ref is reported through another mechanism, such as a zod issue.
|
|
180
|
+
*
|
|
181
|
+
* @param input - The ref as written.
|
|
182
|
+
*/
|
|
183
|
+
export function tryParseRef(input: string): ParsedRef | undefined {
|
|
184
|
+
try {
|
|
185
|
+
return parseRef(input);
|
|
186
|
+
} catch {
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* True when the input parses as a ref.
|
|
193
|
+
*
|
|
194
|
+
* @param input - The ref as written.
|
|
195
|
+
*/
|
|
196
|
+
export function isValidRef(input: string): boolean {
|
|
197
|
+
return tryParseRef(input) !== undefined;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Renders a parsed ref back into its canonical written form. Round-trips with
|
|
202
|
+
* parseRef, apart from surrounding whitespace.
|
|
203
|
+
*
|
|
204
|
+
* @param parsed - The ref parts.
|
|
205
|
+
*/
|
|
206
|
+
export function formatRef(parsed: ParsedRef): string {
|
|
207
|
+
const qualifier = parsed.repo === undefined ? "" : `${parsed.repo}:`;
|
|
208
|
+
const recipe = parsed.recipe === undefined ? "" : `/${parsed.recipe}`;
|
|
209
|
+
const range = parsed.range === undefined ? "" : `@${parsed.range}`;
|
|
210
|
+
return `${qualifier}${parsed.namespace}${recipe}${range}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* The ref's identity, with the repo qualifier and the version range dropped:
|
|
215
|
+
* `namespace` for a namespace ref, `namespace/recipe` for a recipe ref. This is
|
|
216
|
+
* the key everything else is stored under (subscriptions, the index, the
|
|
217
|
+
* lockfile), so the same recipe is never recorded twice under two spellings.
|
|
218
|
+
*
|
|
219
|
+
* @param parsed - The ref parts.
|
|
220
|
+
*/
|
|
221
|
+
export function refKey(parsed: ParsedRef): string {
|
|
222
|
+
return parsed.recipe === undefined
|
|
223
|
+
? parsed.namespace
|
|
224
|
+
: `${parsed.namespace}/${parsed.recipe}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** True when the ref names a whole namespace rather than a single recipe. */
|
|
228
|
+
export function isNamespaceRef(parsed: ParsedRef): boolean {
|
|
229
|
+
return parsed.recipe === undefined;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Dependency refs ----------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The default host of each provider that can appear as a dependency locator's
|
|
236
|
+
* scheme. A locator whose repository path does not begin with a host segment
|
|
237
|
+
* (a segment carrying a dot) means the provider's own public host.
|
|
238
|
+
*
|
|
239
|
+
* The values match the `GITHUB_HOST` and `GITLAB_HOST` constants the providers
|
|
240
|
+
* themselves use; they are repeated here because the ref parser deliberately
|
|
241
|
+
* imports nothing from the provider layer, which is built on top of it.
|
|
242
|
+
*/
|
|
243
|
+
export const DEPENDENCY_PROVIDER_HOSTS: Readonly<Record<string, string>> = {
|
|
244
|
+
github: "github.com",
|
|
245
|
+
gitlab: "gitlab.com",
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* A dependency as a recipe manifest writes it, in either of the two spellings
|
|
250
|
+
* `depends` and `subscribes` accept.
|
|
251
|
+
*
|
|
252
|
+
* A SIBLING is a recipe in the same repository, written as a bare ref; a REMOTE
|
|
253
|
+
* is a recipe in another repository, written as a locator URL whose scheme is
|
|
254
|
+
* the provider's identifier.
|
|
255
|
+
*/
|
|
256
|
+
export type DependencyRef = {
|
|
257
|
+
/** Whether the target lives in this repository or in another one. */
|
|
258
|
+
kind: "sibling" | "remote";
|
|
259
|
+
/** The provider identifier the locator's scheme named. Remote refs only. */
|
|
260
|
+
provider?: string;
|
|
261
|
+
/** The host the repository lives on, explicit or the provider's default. */
|
|
262
|
+
host?: string;
|
|
263
|
+
/** The repository's path on that host, such as `sous-io/sous-recipes`. */
|
|
264
|
+
repoPath?: string;
|
|
265
|
+
/** The repository's canonical identity, `<host>/<repoPath>`. Remote refs only. */
|
|
266
|
+
canonicalRepo?: string;
|
|
267
|
+
/** The namespace the target recipe belongs to. */
|
|
268
|
+
namespace: string;
|
|
269
|
+
/**
|
|
270
|
+
* The recipe's name. A sibling ref may name a whole namespace and leave this
|
|
271
|
+
* out; a locator URL always names a recipe, because its last two path
|
|
272
|
+
* segments are by rule the namespace and the recipe.
|
|
273
|
+
*/
|
|
274
|
+
recipe?: string;
|
|
275
|
+
/** The version range, when one was written. */
|
|
276
|
+
range?: string;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
/** The one-line reminder appended to every dependency ref error. */
|
|
280
|
+
const DEPENDENCY_SYNTAX_HELP =
|
|
281
|
+
"A dependency is written either as a bare ref naming a recipe in this same repository " +
|
|
282
|
+
"('workflow/task-files', optionally with a range such as 'workflow/task-files@^1.1'), " +
|
|
283
|
+
"or as a locator URL naming a recipe in another repository " +
|
|
284
|
+
"('github://sous-io/sous-recipes/workflow/task-files@^1.1').";
|
|
285
|
+
|
|
286
|
+
/** Builds a ConfigError that quotes the offending dependency and shows the grammar. */
|
|
287
|
+
function dependencyError(input: string, problem: string): ConfigError {
|
|
288
|
+
return new ConfigError(
|
|
289
|
+
`Invalid dependency '${input}': ${problem}\n ${DEPENDENCY_SYNTAX_HELP}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** The scheme a locator URL leads with, when it leads with one. */
|
|
294
|
+
const LOCATOR_SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):\/\/(.*)$/i;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parses one entry of a recipe manifest's `depends` or `subscribes` list.
|
|
298
|
+
*
|
|
299
|
+
* parseDependencyRef("workflow/task-files");
|
|
300
|
+
* // -> { kind: "sibling", namespace: "workflow", recipe: "task-files" }
|
|
301
|
+
*
|
|
302
|
+
* parseDependencyRef("github://sous-io/sous-recipes/workflow/sat@^1.1");
|
|
303
|
+
* // -> { kind: "remote", provider: "github", host: "github.com",
|
|
304
|
+
* // repoPath: "sous-io/sous-recipes",
|
|
305
|
+
* // canonicalRepo: "github.com/sous-io/sous-recipes",
|
|
306
|
+
* // namespace: "workflow", recipe: "sat", range: "^1.1" }
|
|
307
|
+
*
|
|
308
|
+
* @param input - The dependency exactly as the manifest wrote it.
|
|
309
|
+
*/
|
|
310
|
+
export function parseDependencyRef(input: string): DependencyRef {
|
|
311
|
+
if (typeof input !== "string") {
|
|
312
|
+
throw dependencyError(String(input), "a dependency must be a string.");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const trimmed = input.trim();
|
|
316
|
+
if (trimmed.length === 0) {
|
|
317
|
+
throw dependencyError(input, "a dependency must not be empty.");
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const locator = LOCATOR_SCHEME_PATTERN.exec(trimmed);
|
|
321
|
+
if (locator !== null) return parseRemoteDependency(input, locator[1]!, locator[2]!);
|
|
322
|
+
|
|
323
|
+
// Anything without a scheme is a sibling, so it follows the ordinary ref
|
|
324
|
+
// grammar; the one thing a manifest may no longer write is the consumer-side
|
|
325
|
+
// 'repo:' qualifier, which names a short name only that project knows.
|
|
326
|
+
if (trimmed.includes(":")) {
|
|
327
|
+
throw dependencyError(
|
|
328
|
+
input,
|
|
329
|
+
"a 'repo:' qualifier names a short name that only the consuming project knows, so " +
|
|
330
|
+
"it cannot appear in a published manifest. Name the other repository by its " +
|
|
331
|
+
"location instead, as in 'github://owner/repository/namespace/recipe'."
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const parsed = parseRef(trimmed);
|
|
336
|
+
const sibling: DependencyRef = { kind: "sibling", namespace: parsed.namespace };
|
|
337
|
+
if (parsed.recipe !== undefined) sibling.recipe = parsed.recipe;
|
|
338
|
+
if (parsed.range !== undefined) sibling.range = parsed.range;
|
|
339
|
+
return sibling;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parses the locator form, whose scheme is the provider's identifier.
|
|
344
|
+
*
|
|
345
|
+
* The path is read from the RIGHT: the last two segments are always the
|
|
346
|
+
* namespace and the recipe, because they are the recipe's published identity
|
|
347
|
+
* and never a filesystem path. Everything before them is the repository, whose
|
|
348
|
+
* first segment is the host when it carries a dot and otherwise the provider's
|
|
349
|
+
* own public host.
|
|
350
|
+
*
|
|
351
|
+
* @param input - The dependency as written, for error messages.
|
|
352
|
+
* @param scheme - The scheme, which names the provider.
|
|
353
|
+
* @param rest - Everything after the `://`.
|
|
354
|
+
*/
|
|
355
|
+
function parseRemoteDependency(
|
|
356
|
+
input: string,
|
|
357
|
+
scheme: string,
|
|
358
|
+
rest: string
|
|
359
|
+
): DependencyRef {
|
|
360
|
+
const provider = scheme.toLowerCase();
|
|
361
|
+
|
|
362
|
+
if (provider === "local") {
|
|
363
|
+
throw dependencyError(
|
|
364
|
+
input,
|
|
365
|
+
"a local repository is a consumer's convenience, not a published location, so a " +
|
|
366
|
+
"manifest cannot depend on one. Publish the recipe and depend on it by its " +
|
|
367
|
+
"published location."
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const defaultHost = DEPENDENCY_PROVIDER_HOSTS[provider];
|
|
372
|
+
if (defaultHost === undefined) {
|
|
373
|
+
const known = Object.keys(DEPENDENCY_PROVIDER_HOSTS).sort().join(", ");
|
|
374
|
+
throw dependencyError(
|
|
375
|
+
input,
|
|
376
|
+
`'${provider}' is not a provider sous can fetch from. The scheme of a locator URL ` +
|
|
377
|
+
`is the provider's own identifier; sous ships these: ${known}.`
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
let body = rest.trim();
|
|
382
|
+
let range: string | undefined;
|
|
383
|
+
const atIndex = body.indexOf("@");
|
|
384
|
+
if (atIndex !== -1) {
|
|
385
|
+
if (body.indexOf("@", atIndex + 1) !== -1) {
|
|
386
|
+
throw dependencyError(input, "a dependency may carry at most one '@' version range.");
|
|
387
|
+
}
|
|
388
|
+
range = body.slice(atIndex + 1).trim();
|
|
389
|
+
body = body.slice(0, atIndex);
|
|
390
|
+
if (range.length === 0) {
|
|
391
|
+
throw dependencyError(input, "the '@' is not followed by a version range.");
|
|
392
|
+
}
|
|
393
|
+
if (semver.validRange(range) === null) {
|
|
394
|
+
throw dependencyError(
|
|
395
|
+
input,
|
|
396
|
+
`'${range}' is not a version range. Ranges follow npm's rules, such as '^1.2.0', ` +
|
|
397
|
+
"'~2.1', '>=1.0.0 <2.0.0' or '*'."
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const segments = body.split("/").filter((segment) => segment.length > 0);
|
|
403
|
+
|
|
404
|
+
// The recipe is two segments and a repository is at least an owner and a
|
|
405
|
+
// name, so four is the shortest locator that can mean anything.
|
|
406
|
+
if (segments.length < 4) {
|
|
407
|
+
throw dependencyError(
|
|
408
|
+
input,
|
|
409
|
+
"a locator URL names a repository and then the recipe inside it, as in " +
|
|
410
|
+
"'github://owner/repository/namespace/recipe'. The last two segments are always " +
|
|
411
|
+
"the namespace and the recipe."
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const recipe = segments.pop()!;
|
|
416
|
+
const namespace = segments.pop()!;
|
|
417
|
+
|
|
418
|
+
if (!NAMESPACE_NAME_PATTERN.test(namespace)) {
|
|
419
|
+
throw dependencyError(
|
|
420
|
+
input,
|
|
421
|
+
`the namespace '${namespace}' must be lowercase kebab-case: a letter, then letters, ` +
|
|
422
|
+
"digits or hyphens."
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
if (!RECIPE_NAME_PATTERN.test(recipe)) {
|
|
426
|
+
throw dependencyError(
|
|
427
|
+
input,
|
|
428
|
+
`the recipe name '${recipe}' must be lowercase kebab-case: a letter, then letters, ` +
|
|
429
|
+
"digits or hyphens."
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const first = segments[0]!;
|
|
434
|
+
const hasHost = first.includes(".");
|
|
435
|
+
const host = (hasHost ? first : defaultHost).toLowerCase();
|
|
436
|
+
const pathSegments = hasHost ? segments.slice(1) : segments;
|
|
437
|
+
|
|
438
|
+
if (pathSegments.length < 2) {
|
|
439
|
+
const what =
|
|
440
|
+
pathSegments.length === 0 ? "nothing" : `only '${pathSegments.join("/")}'`;
|
|
441
|
+
throw dependencyError(
|
|
442
|
+
input,
|
|
443
|
+
`the host '${host}' is followed by ${what}, and a repository is named by an owner ` +
|
|
444
|
+
"and a repository name, as in " +
|
|
445
|
+
"'gitlab://gitlab.example.com/group/subgroup/project/namespace/recipe'."
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const repoPath = pathSegments.join("/").replace(/\.git$/i, "");
|
|
450
|
+
|
|
451
|
+
return {
|
|
452
|
+
kind: "remote",
|
|
453
|
+
provider,
|
|
454
|
+
host,
|
|
455
|
+
repoPath,
|
|
456
|
+
canonicalRepo: `${host}/${repoPath}`.toLowerCase(),
|
|
457
|
+
namespace,
|
|
458
|
+
recipe,
|
|
459
|
+
...(range === undefined ? {} : { range }),
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* The recipe key a dependency names: `namespace/recipe`, or the bare namespace
|
|
465
|
+
* when a sibling ref named a whole namespace. This is what the index, the
|
|
466
|
+
* lockfile and the store all file a recipe under.
|
|
467
|
+
*
|
|
468
|
+
* @param parsed - The parsed dependency.
|
|
469
|
+
*/
|
|
470
|
+
export function dependencyRefKey(parsed: DependencyRef): string {
|
|
471
|
+
return parsed.recipe === undefined
|
|
472
|
+
? parsed.namespace
|
|
473
|
+
: `${parsed.namespace}/${parsed.recipe}`;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Renders a parsed dependency back into the form a manifest writes. Round-trips
|
|
478
|
+
* with parseDependencyRef, apart from surrounding whitespace and a host that was
|
|
479
|
+
* left implicit.
|
|
480
|
+
*
|
|
481
|
+
* @param parsed - The parsed dependency.
|
|
482
|
+
*/
|
|
483
|
+
export function formatDependencyRef(parsed: DependencyRef): string {
|
|
484
|
+
const key = dependencyRefKey(parsed);
|
|
485
|
+
const range = parsed.range === undefined ? "" : `@${parsed.range}`;
|
|
486
|
+
if (parsed.kind === "sibling") return `${key}${range}`;
|
|
487
|
+
return `${parsed.provider}://${parsed.host}/${parsed.repoPath}/${key}${range}`;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Parses a dependency, returning undefined instead of throwing. Use this where
|
|
492
|
+
* a bad dependency is reported through another mechanism, such as a zod issue.
|
|
493
|
+
*
|
|
494
|
+
* @param input - The dependency as written.
|
|
495
|
+
*/
|
|
496
|
+
export function tryParseDependencyRef(input: string): DependencyRef | undefined {
|
|
497
|
+
try {
|
|
498
|
+
return parseDependencyRef(input);
|
|
499
|
+
} catch {
|
|
500
|
+
return undefined;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* The HTTPS location a remote dependency points at, which is what `sous repo
|
|
506
|
+
* add` would be given for it.
|
|
507
|
+
*
|
|
508
|
+
* @param parsed - A parsed remote dependency.
|
|
509
|
+
*/
|
|
510
|
+
export function dependencyRepoUrl(parsed: DependencyRef): string | undefined {
|
|
511
|
+
if (parsed.kind !== "remote") return undefined;
|
|
512
|
+
return `https://${parsed.host}/${parsed.repoPath}`;
|
|
513
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Saying what a reference resolved to.
|
|
3
|
+
*
|
|
4
|
+
* A word on the command line can name a repository, a namespace, a recipe or a
|
|
5
|
+
* variable, and when sous settles which one it means it has to say so. What it
|
|
6
|
+
* resolved to is a set of facts, so it is written as the same key and value
|
|
7
|
+
* list every other set of facts in the CLI is written as, with one short
|
|
8
|
+
* sentence after it saying why that candidate won. One function builds it, so
|
|
9
|
+
* every command that resolves a reference reports it the same way; `pickReference`
|
|
10
|
+
* in `src/lib/refs/pick.ts` is the single caller, which is how every command gets it.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
formatVariable,
|
|
15
|
+
indent,
|
|
16
|
+
wrapColumns,
|
|
17
|
+
wrapText,
|
|
18
|
+
type VariableEntry,
|
|
19
|
+
} from "../../utils/formatting.js";
|
|
20
|
+
import { SCOPE_LABELS, SousScope } from "../refs/scopes.js";
|
|
21
|
+
import type { ReferenceMatch } from "../refs/find.js";
|
|
22
|
+
|
|
23
|
+
/** What a resolved reference is, in the words the report shows. */
|
|
24
|
+
export interface ResolvedReferenceFacts {
|
|
25
|
+
/** The reference exactly as the person wrote it. */
|
|
26
|
+
search: string;
|
|
27
|
+
/** The fully qualified spelling the run proceeds with. */
|
|
28
|
+
resolvedTo: string;
|
|
29
|
+
/** What kind of thing it turned out to be: "recipe", "namespace", "repository". */
|
|
30
|
+
kind: string;
|
|
31
|
+
/** The repository publishing it. */
|
|
32
|
+
repository?: string;
|
|
33
|
+
/** The namespace it lives in, when it has one. */
|
|
34
|
+
namespace?: string;
|
|
35
|
+
/** The recipe itself, when the reference named one. */
|
|
36
|
+
recipe?: string;
|
|
37
|
+
/** The variable itself, when the reference named one. */
|
|
38
|
+
variable?: string;
|
|
39
|
+
/** Where a repository lives, when the reference named a repository. */
|
|
40
|
+
location?: string;
|
|
41
|
+
/** The publisher's one-line summary, when there is one. */
|
|
42
|
+
description?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Why this candidate won, as the closing sentence. The default says the
|
|
45
|
+
* reference named one thing and nothing else; a caller that settled it some
|
|
46
|
+
* other way (taking the first of several, say) supplies its own.
|
|
47
|
+
*/
|
|
48
|
+
reason?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The report as lines to write: the facts, a blank line, and the sentence.
|
|
53
|
+
*
|
|
54
|
+
* @param facts - What the reference resolved to.
|
|
55
|
+
* @returns The lines to write, ready for the console.
|
|
56
|
+
*/
|
|
57
|
+
export function formatResolvedReference(facts: ResolvedReferenceFacts): string[] {
|
|
58
|
+
const entries: VariableEntry[] = [
|
|
59
|
+
{ label: "Resolved to", value: facts.resolvedTo },
|
|
60
|
+
...(facts.variable === undefined ? [] : [{ label: "Variable", value: facts.variable }]),
|
|
61
|
+
...(facts.recipe === undefined ? [] : [{ label: "Recipe", value: facts.recipe }]),
|
|
62
|
+
...(facts.namespace === undefined ? [] : [{ label: "Namespace", value: facts.namespace }]),
|
|
63
|
+
...(facts.repository === undefined
|
|
64
|
+
? []
|
|
65
|
+
: [{ label: "Repository", value: facts.repository }]),
|
|
66
|
+
...(facts.location === undefined ? [] : [{ label: "Location", value: facts.location }]),
|
|
67
|
+
...(facts.description === undefined
|
|
68
|
+
? []
|
|
69
|
+
: [{ label: "Description", value: facts.description }]),
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const labelWidth = Math.max(...entries.map((entry) => entry.label.length));
|
|
73
|
+
const lines = entries.flatMap((entry) => formatVariable(entry, { labelWidth }));
|
|
74
|
+
|
|
75
|
+
const reason =
|
|
76
|
+
facts.reason ??
|
|
77
|
+
`'${facts.search}' named one ${facts.kind}, and nothing else, so that is what ` +
|
|
78
|
+
`is being used.`;
|
|
79
|
+
|
|
80
|
+
lines.push("");
|
|
81
|
+
for (const line of wrapText(reason, wrapColumns() - 4)) {
|
|
82
|
+
lines.push(indent(line));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return lines;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The facts one match carries, ready for `formatResolvedReference`.
|
|
90
|
+
*
|
|
91
|
+
* The match knows what it is; this decides which of its fields are facts worth
|
|
92
|
+
* showing. A repository's detail is where it lives rather than a summary of it,
|
|
93
|
+
* and a repository's own name is already the resolved spelling, so neither is
|
|
94
|
+
* repeated as a line of its own. What a match resolves to is always its fully
|
|
95
|
+
* qualified key, including for an environment variable name, because the key is
|
|
96
|
+
* what the rest of the run proceeds with.
|
|
97
|
+
*
|
|
98
|
+
* @param match - The match the run proceeds with.
|
|
99
|
+
* @param search - The reference exactly as it was written.
|
|
100
|
+
* @param reason - The closing sentence, when the caller has one of its own.
|
|
101
|
+
*/
|
|
102
|
+
export function resolvedReferenceFacts(
|
|
103
|
+
match: ReferenceMatch,
|
|
104
|
+
search: string,
|
|
105
|
+
reason?: string
|
|
106
|
+
): ResolvedReferenceFacts {
|
|
107
|
+
const isRepository = match.scope === SousScope.Repository;
|
|
108
|
+
const resolvedTo = match.key;
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
search,
|
|
112
|
+
resolvedTo,
|
|
113
|
+
kind: SCOPE_LABELS[match.scope],
|
|
114
|
+
...(match.variable === undefined ? {} : { variable: match.variable }),
|
|
115
|
+
...(match.recipe === undefined ? {} : { recipe: match.recipe }),
|
|
116
|
+
...(match.namespace === undefined ? {} : { namespace: match.namespace }),
|
|
117
|
+
...(match.repo === undefined || match.repo === resolvedTo ? {} : { repository: match.repo }),
|
|
118
|
+
...(isRepository && match.detail !== undefined ? { location: match.detail } : {}),
|
|
119
|
+
...(!isRepository && match.detail !== undefined ? { description: match.detail } : {}),
|
|
120
|
+
...(reason === undefined ? {} : { reason }),
|
|
121
|
+
};
|
|
122
|
+
}
|