@sous-io/sous 0.1.1 → 0.2.0
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 +408 -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 +619 -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 +413 -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,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GitLab provider: the read path, and as much of the write path as GitLab
|
|
3
|
+
* honestly gives sous.
|
|
4
|
+
*
|
|
5
|
+
* The read shape is the same as the GitHub provider's: the index file is one raw
|
|
6
|
+
* HTTPS GET, and a recipe's subtree comes from git. A token is used when one is
|
|
7
|
+
* available, read from GITLAB_TOKEN or asked of the `glab` command line tool
|
|
8
|
+
* when that is installed and signed in; a missing `glab` is never an error on
|
|
9
|
+
* the read path.
|
|
10
|
+
*
|
|
11
|
+
* The write path is one merge request opened by `glab mr create`, from a branch
|
|
12
|
+
* the contributor can already push. Sous does not fork on GitLab: there is no
|
|
13
|
+
* fork-and-track flow here that sous can carry out without guessing, so
|
|
14
|
+
* `canPush` answers "cannot tell" rather than inventing a permission, and `fork`
|
|
15
|
+
* refuses with the manual route spelled out. Saying so is the point; pretending
|
|
16
|
+
* otherwise would strand a contributor halfway through a submission.
|
|
17
|
+
*
|
|
18
|
+
* Self-hosted instances are supported: any host whose name begins with `gitlab.`
|
|
19
|
+
* is recognized automatically, and a repository entry may always name its
|
|
20
|
+
* provider outright for a host that gives nothing away.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { ConfigError } from "../../errors.js";
|
|
24
|
+
import { INDEX_FILENAME } from "../formats/common.js";
|
|
25
|
+
import { ProviderBase, firstUrlIn } from "./base.js";
|
|
26
|
+
import { fetchSubtree, type CommandRunner } from "./git.js";
|
|
27
|
+
import { fetchText, type FetchLike } from "./http.js";
|
|
28
|
+
import {
|
|
29
|
+
buildCanonicalRepo,
|
|
30
|
+
invalidRepoUrl,
|
|
31
|
+
splitRepoUrl,
|
|
32
|
+
type AuthStatus,
|
|
33
|
+
type CanonicalRepo,
|
|
34
|
+
type ChangeProposal,
|
|
35
|
+
type FetchedIndex,
|
|
36
|
+
type ForkedRepo,
|
|
37
|
+
type ProposedChange,
|
|
38
|
+
type ProviderCli,
|
|
39
|
+
type ProviderFeature,
|
|
40
|
+
type ProviderOptions,
|
|
41
|
+
} from "./provider.js";
|
|
42
|
+
|
|
43
|
+
/** The host this provider serves when a URL does not say otherwise. */
|
|
44
|
+
export const GITLAB_HOST = "gitlab.com";
|
|
45
|
+
|
|
46
|
+
/** The environment variable a GitLab token is read from. */
|
|
47
|
+
export const GITLAB_TOKEN_ENV = "GITLAB_TOKEN";
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Finds a GitLab token: the environment first, then `glab auth token` when the
|
|
51
|
+
* `glab` command line tool is installed and signed in. Returns undefined when
|
|
52
|
+
* there is none, because public repositories need no token at all.
|
|
53
|
+
*
|
|
54
|
+
* @param options - Environment and subprocess runner overrides.
|
|
55
|
+
*/
|
|
56
|
+
export async function findGitlabToken(options: {
|
|
57
|
+
env?: NodeJS.ProcessEnv;
|
|
58
|
+
run?: CommandRunner;
|
|
59
|
+
} = {}): Promise<string | undefined> {
|
|
60
|
+
return new GitlabProvider().token(options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The GitLab provider. */
|
|
64
|
+
export class GitlabProvider extends ProviderBase {
|
|
65
|
+
readonly id = "gitlab" as const;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Reads the index and recipe subtrees, and proposes a change through
|
|
69
|
+
* the GitLab CLI ('glab').
|
|
70
|
+
*/
|
|
71
|
+
readonly features: ProviderFeature[] = ["fetch", "submit"];
|
|
72
|
+
|
|
73
|
+
/** The command line tool the write path is built on. */
|
|
74
|
+
readonly cli: ProviderCli = {
|
|
75
|
+
command: "glab",
|
|
76
|
+
label: "the GitLab CLI",
|
|
77
|
+
install: "https://gitlab.com/gitlab-org/cli",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** What GitLab calls a proposal. */
|
|
81
|
+
readonly proposalNoun = "merge request";
|
|
82
|
+
|
|
83
|
+
matches(url: string): boolean {
|
|
84
|
+
const parts = splitRepoUrl(url);
|
|
85
|
+
if (parts === undefined) return false;
|
|
86
|
+
return parts.host === GITLAB_HOST || parts.host.startsWith("gitlab.");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
canonicalize(url: string): CanonicalRepo {
|
|
90
|
+
const parts = splitRepoUrl(url);
|
|
91
|
+
if (parts === undefined) throw invalidRepoUrl(this.id, url);
|
|
92
|
+
return buildCanonicalRepo(parts.host, parts.owner, parts.name);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A GitLab token, from the environment or from `glab`.
|
|
97
|
+
*
|
|
98
|
+
* @param options - Environment and subprocess runner overrides.
|
|
99
|
+
*/
|
|
100
|
+
async token(options: ProviderOptions = {}): Promise<string | undefined> {
|
|
101
|
+
return this.findToken(GITLAB_TOKEN_ENV, ["auth", "token"], options);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Fetches the repo's index file from the instance's raw file endpoint at the
|
|
106
|
+
* repository's default branch.
|
|
107
|
+
*
|
|
108
|
+
* @param repo - The canonicalized repository.
|
|
109
|
+
* @param options - Environment, fetch and subprocess overrides.
|
|
110
|
+
*/
|
|
111
|
+
async fetchIndex(
|
|
112
|
+
repo: CanonicalRepo,
|
|
113
|
+
options: ProviderOptions = {}
|
|
114
|
+
): Promise<FetchedIndex> {
|
|
115
|
+
const token = await this.token(options);
|
|
116
|
+
const url = this.indexUrl(repo);
|
|
117
|
+
const fetched = await fetchText(url, {
|
|
118
|
+
...(token === undefined ? {} : { token }),
|
|
119
|
+
...(options.fetchImpl === undefined
|
|
120
|
+
? {}
|
|
121
|
+
: { fetchImpl: options.fetchImpl as FetchLike }),
|
|
122
|
+
label: "repo index",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return fetched.etag === undefined
|
|
126
|
+
? { text: fetched.text, ref: "HEAD" }
|
|
127
|
+
: { text: fetched.text, ref: "HEAD", etag: fetched.etag };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fetches one recipe folder at one tag. Private repositories work through
|
|
132
|
+
* git's own credential helpers, the same way a manual clone would.
|
|
133
|
+
*
|
|
134
|
+
* @param repo - The canonicalized repository.
|
|
135
|
+
* @param recipePath - The recipe folder, relative to the repository root.
|
|
136
|
+
* @param tag - The git tag carrying the version.
|
|
137
|
+
* @param destDir - Where the recipe's files should end up.
|
|
138
|
+
* @param options - Subprocess runner override.
|
|
139
|
+
*/
|
|
140
|
+
async fetchRecipeTree(
|
|
141
|
+
repo: CanonicalRepo,
|
|
142
|
+
recipePath: string,
|
|
143
|
+
tag: string,
|
|
144
|
+
destDir: string,
|
|
145
|
+
options: ProviderOptions = {}
|
|
146
|
+
): Promise<void> {
|
|
147
|
+
await fetchSubtree({
|
|
148
|
+
cloneUrl: repo.httpsUrl,
|
|
149
|
+
tag,
|
|
150
|
+
subPath: recipePath,
|
|
151
|
+
destDir,
|
|
152
|
+
...(options.run === undefined ? {} : { run: options.run }),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The raw URL of a repository's index file at its default branch.
|
|
158
|
+
*
|
|
159
|
+
* @param repo - The canonicalized repository.
|
|
160
|
+
*/
|
|
161
|
+
indexUrl(repo: CanonicalRepo): string {
|
|
162
|
+
return `https://${repo.host}/${repo.owner}/${repo.name}/-/raw/HEAD/${INDEX_FILENAME}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// --- The write path --------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Whether `glab` is installed and signed in.
|
|
169
|
+
*
|
|
170
|
+
* @param options - Subprocess runner and working directory overrides.
|
|
171
|
+
*/
|
|
172
|
+
async authStatus(options: ProviderOptions = {}): Promise<AuthStatus> {
|
|
173
|
+
const ok = await this.commandSucceeds(this.cli.command, ["auth", "status"], options);
|
|
174
|
+
if (ok) {
|
|
175
|
+
return {
|
|
176
|
+
ok: true,
|
|
177
|
+
detail: `${this.cli.label} ('${this.cli.command}') is installed and signed in.`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
ok: false,
|
|
182
|
+
detail:
|
|
183
|
+
`Sous proposes a change through ${this.cli.label} ('${this.cli.command}'), and it is ` +
|
|
184
|
+
`either not installed or not signed in.\n` +
|
|
185
|
+
` Install it from ${this.cli.install}, then run '${this.cli.command} auth login'.`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Always undefined: sous has no cheap, reliable way to ask GitLab whether the
|
|
191
|
+
* contributor may push, and guessing would send them down a fork path this
|
|
192
|
+
* provider cannot finish.
|
|
193
|
+
*
|
|
194
|
+
* @param _repo - The canonicalized repository; unused.
|
|
195
|
+
* @param _options - Unused, because nothing is run.
|
|
196
|
+
*/
|
|
197
|
+
async canPush(
|
|
198
|
+
_repo: CanonicalRepo,
|
|
199
|
+
_options: ProviderOptions = {}
|
|
200
|
+
): Promise<boolean | undefined> {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Refuses, with the manual route. Sous opens a merge request from a branch
|
|
206
|
+
* the contributor can already push; forking on their behalf is not something
|
|
207
|
+
* this provider does.
|
|
208
|
+
*
|
|
209
|
+
* @param repo - The canonicalized repository.
|
|
210
|
+
*/
|
|
211
|
+
async fork(repo: CanonicalRepo): Promise<ForkedRepo> {
|
|
212
|
+
throw new ConfigError(
|
|
213
|
+
`Sous does not fork ${repo.owner}/${repo.name} for you on ${this.cli.label}; it opens a ` +
|
|
214
|
+
`${this.proposalNoun} from a branch you can already push.\n` +
|
|
215
|
+
` Fork the project yourself, push your branch to the fork, then open the ` +
|
|
216
|
+
`${this.proposalNoun} from there.`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Opens a merge request from the branch in the checkout the command is run
|
|
222
|
+
* in, which is why the working directory matters here.
|
|
223
|
+
*
|
|
224
|
+
* @param _repo - The canonicalized repository; `glab` reads the checkout instead.
|
|
225
|
+
* @param proposal - The branch, the text and whether it is a draft.
|
|
226
|
+
* @param options - Subprocess runner and working directory overrides.
|
|
227
|
+
*/
|
|
228
|
+
async proposeChange(
|
|
229
|
+
_repo: CanonicalRepo,
|
|
230
|
+
proposal: ChangeProposal,
|
|
231
|
+
options: ProviderOptions = {}
|
|
232
|
+
): Promise<ProposedChange> {
|
|
233
|
+
const args = ["mr", "create", "--source-branch", proposal.branch];
|
|
234
|
+
if (proposal.base !== undefined) args.push("--target-branch", proposal.base);
|
|
235
|
+
args.push(
|
|
236
|
+
"--title",
|
|
237
|
+
proposal.title,
|
|
238
|
+
"--description",
|
|
239
|
+
proposal.body,
|
|
240
|
+
"--yes"
|
|
241
|
+
);
|
|
242
|
+
if (proposal.draft) args.push("--draft");
|
|
243
|
+
|
|
244
|
+
const result = await this.runCommand(this.cli.command, args, options);
|
|
245
|
+
if (result.code !== 0) {
|
|
246
|
+
const reported = result.stderr.trim() || result.stdout.trim();
|
|
247
|
+
throw new ConfigError(
|
|
248
|
+
`'${this.cli.command} mr create' did not succeed, so no proposal was opened.` +
|
|
249
|
+
(reported.length === 0 ? "" : `\n ${reported}`)
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const url = firstUrlIn(result.stdout);
|
|
254
|
+
if (url === undefined) {
|
|
255
|
+
return {
|
|
256
|
+
detail:
|
|
257
|
+
`The ${this.proposalNoun} was opened, but '${this.cli.command}' printed no address ` +
|
|
258
|
+
`for it.`,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
return { url, detail: `The ${this.proposalNoun} is at ${url}.` };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tiny HTTPS client the providers use to fetch a repo's index file.
|
|
3
|
+
*
|
|
4
|
+
* Only one kind of request is ever made: a plain GET of a raw file, optionally
|
|
5
|
+
* carrying a bearer token so private repositories work. `fetch` is Node's own
|
|
6
|
+
* global, and it is injectable so tests never reach the network.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { ConfigError } from "../../errors.js";
|
|
10
|
+
|
|
11
|
+
/** The shape of `fetch` this module needs; the global satisfies it. */
|
|
12
|
+
export type FetchLike = (
|
|
13
|
+
url: string,
|
|
14
|
+
init?: { headers?: Record<string, string> }
|
|
15
|
+
) => Promise<{
|
|
16
|
+
ok: boolean;
|
|
17
|
+
status: number;
|
|
18
|
+
statusText: string;
|
|
19
|
+
text: () => Promise<string>;
|
|
20
|
+
headers: { get: (name: string) => string | null };
|
|
21
|
+
}>;
|
|
22
|
+
|
|
23
|
+
/** What a successful raw-file GET returns. */
|
|
24
|
+
export type FetchedText = {
|
|
25
|
+
/** The file's contents. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** The entity tag the server sent, when it sent one. */
|
|
28
|
+
etag?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/** Options for a raw-file GET. */
|
|
32
|
+
export type FetchTextOptions = {
|
|
33
|
+
/** A bearer token, when one is available for the host. */
|
|
34
|
+
token?: string;
|
|
35
|
+
/** The fetch implementation to use. Defaults to the global `fetch`. */
|
|
36
|
+
fetchImpl?: FetchLike;
|
|
37
|
+
/** What the URL is, named in error messages (for example "repo index"). */
|
|
38
|
+
label?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* GETs a URL and returns its body as text. Any non-2xx response, or a transport
|
|
43
|
+
* failure, becomes a ConfigError that names the URL and the status, and says
|
|
44
|
+
* plainly what a 404 or a 401 usually means for a sous repository.
|
|
45
|
+
*
|
|
46
|
+
* @param url - The absolute HTTPS URL to fetch.
|
|
47
|
+
* @param options - Bearer token, fetch implementation and error label.
|
|
48
|
+
*/
|
|
49
|
+
export async function fetchText(
|
|
50
|
+
url: string,
|
|
51
|
+
options: FetchTextOptions = {}
|
|
52
|
+
): Promise<FetchedText> {
|
|
53
|
+
const fetchImpl = options.fetchImpl ?? (globalThis.fetch as unknown as FetchLike);
|
|
54
|
+
const label = options.label ?? "file";
|
|
55
|
+
|
|
56
|
+
if (typeof fetchImpl !== "function") {
|
|
57
|
+
throw new ConfigError(
|
|
58
|
+
`Sous cannot fetch the ${label} at ${url}: this Node runtime provides no global fetch.\n` +
|
|
59
|
+
` Sous requires Node 22 or newer.`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const headers: Record<string, string> = { Accept: "application/json" };
|
|
64
|
+
if (options.token !== undefined && options.token.length > 0) {
|
|
65
|
+
headers["Authorization"] = `Bearer ${options.token}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let response: Awaited<ReturnType<FetchLike>>;
|
|
69
|
+
try {
|
|
70
|
+
response = await fetchImpl(url, { headers });
|
|
71
|
+
} catch (error) {
|
|
72
|
+
throw new ConfigError(
|
|
73
|
+
`Sous could not reach ${url} while fetching the ${label}.\n` +
|
|
74
|
+
` ${(error as Error).message}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
const lines = [
|
|
80
|
+
`Sous could not fetch the ${label} from ${url}.`,
|
|
81
|
+
` The server answered ${response.status} ${response.statusText}.`,
|
|
82
|
+
];
|
|
83
|
+
if (response.status === 404) {
|
|
84
|
+
lines.push(
|
|
85
|
+
" Either the repository publishes no sous index yet, or the URL names a " +
|
|
86
|
+
"repository that does not exist."
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
if (response.status === 401 || response.status === 403) {
|
|
90
|
+
lines.push(
|
|
91
|
+
" The repository is private or the request was not authorized. Sous uses a " +
|
|
92
|
+
"token from the environment, or from the provider's command line tool when " +
|
|
93
|
+
"one is installed and signed in."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
throw new ConfigError(lines.join("\n"));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const text = await response.text();
|
|
100
|
+
const etag = response.headers.get("etag");
|
|
101
|
+
return etag === null ? { text } : { text, etag };
|
|
102
|
+
}
|