@teambit/ci 1.0.485 → 1.0.487
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/ci.docs.mdx +344 -83
- package/commands/sync.cmd.ts +77 -0
- package/dist/ci.docs.mdx +344 -83
- package/dist/ci.main.runtime.d.ts +67 -6
- package/dist/ci.main.runtime.js +152 -25
- package/dist/ci.main.runtime.js.map +1 -1
- package/dist/commands/sync.cmd.d.ts +34 -0
- package/dist/commands/sync.cmd.js +57 -0
- package/dist/commands/sync.cmd.js.map +1 -0
- package/dist/git.d.ts +11 -1
- package/dist/git.js +11 -1
- package/dist/git.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +26 -1
- package/dist/index.js.map +1 -1
- package/dist/{preview-1785858957533.js → preview-1785883495880.js} +1 -1
- package/dist/sync/git-host-provider.d.ts +47 -0
- package/dist/sync/git-host-provider.js +54 -0
- package/dist/sync/git-host-provider.js.map +1 -0
- package/dist/sync/git-host-provider.spec.d.ts +1 -0
- package/dist/sync/git-host-provider.spec.js +171 -0
- package/dist/sync/git-host-provider.spec.js.map +1 -0
- package/dist/sync/git-ops.d.ts +171 -0
- package/dist/sync/git-ops.js +354 -0
- package/dist/sync/git-ops.js.map +1 -0
- package/dist/sync/git-ops.spec.d.ts +1 -0
- package/dist/sync/git-ops.spec.js +476 -0
- package/dist/sync/git-ops.spec.js.map +1 -0
- package/dist/sync/github-client.d.ts +79 -0
- package/dist/sync/github-client.js +198 -0
- package/dist/sync/github-client.js.map +1 -0
- package/dist/sync/github-client.spec.d.ts +1 -0
- package/dist/sync/github-client.spec.js +205 -0
- package/dist/sync/github-client.spec.js.map +1 -0
- package/dist/sync/init-scaffold.d.ts +40 -0
- package/dist/sync/init-scaffold.js +326 -0
- package/dist/sync/init-scaffold.js.map +1 -0
- package/dist/sync/init-scaffold.spec.d.ts +1 -0
- package/dist/sync/init-scaffold.spec.js +169 -0
- package/dist/sync/init-scaffold.spec.js.map +1 -0
- package/dist/sync/lane-sync-executor.d.ts +256 -0
- package/dist/sync/lane-sync-executor.js +1249 -0
- package/dist/sync/lane-sync-executor.js.map +1 -0
- package/dist/sync/lane-sync-executor.spec.d.ts +1 -0
- package/dist/sync/lane-sync-executor.spec.js +416 -0
- package/dist/sync/lane-sync-executor.spec.js.map +1 -0
- package/dist/sync/main-sync-executor.d.ts +62 -0
- package/dist/sync/main-sync-executor.js +305 -0
- package/dist/sync/main-sync-executor.js.map +1 -0
- package/dist/sync/sync-config.d.ts +83 -0
- package/dist/sync/sync-config.js +187 -0
- package/dist/sync/sync-config.js.map +1 -0
- package/dist/sync/sync-config.spec.d.ts +1 -0
- package/dist/sync/sync-config.spec.js +254 -0
- package/dist/sync/sync-config.spec.js.map +1 -0
- package/dist/sync/sync-orchestrator.d.ts +76 -0
- package/dist/sync/sync-orchestrator.js +396 -0
- package/dist/sync/sync-orchestrator.js.map +1 -0
- package/dist/sync/sync-orchestrator.spec.d.ts +1 -0
- package/dist/sync/sync-orchestrator.spec.js +155 -0
- package/dist/sync/sync-orchestrator.spec.js.map +1 -0
- package/dist/sync/sync-planner.d.ts +60 -0
- package/dist/sync/sync-planner.js +114 -0
- package/dist/sync/sync-planner.js.map +1 -0
- package/dist/sync/sync-planner.spec.d.ts +1 -0
- package/dist/sync/sync-planner.spec.js +210 -0
- package/dist/sync/sync-planner.spec.js.map +1 -0
- package/dist/sync/sync-state.d.ts +94 -0
- package/dist/sync/sync-state.js +178 -0
- package/dist/sync/sync-state.js.map +1 -0
- package/dist/sync/sync-state.spec.d.ts +1 -0
- package/dist/sync/sync-state.spec.js +271 -0
- package/dist/sync/sync-state.spec.js.map +1 -0
- package/dist/sync/workspace-lane.d.ts +13 -0
- package/dist/sync/workspace-lane.js +27 -0
- package/dist/sync/workspace-lane.js.map +1 -0
- package/package.json +20 -15
- package/sync/git-host-provider.spec.ts +197 -0
- package/sync/git-host-provider.ts +89 -0
- package/sync/git-ops.spec.ts +506 -0
- package/sync/git-ops.ts +334 -0
- package/sync/github-client.spec.ts +174 -0
- package/sync/github-client.ts +209 -0
- package/sync/init-scaffold.spec.ts +142 -0
- package/sync/init-scaffold.ts +340 -0
- package/sync/lane-sync-executor.spec.ts +389 -0
- package/sync/lane-sync-executor.ts +1363 -0
- package/sync/main-sync-executor.ts +321 -0
- package/sync/sync-config.spec.ts +283 -0
- package/sync/sync-config.ts +214 -0
- package/sync/sync-orchestrator.spec.ts +120 -0
- package/sync/sync-orchestrator.ts +386 -0
- package/sync/sync-planner.spec.ts +126 -0
- package/sync/sync-planner.ts +99 -0
- package/sync/sync-state.spec.ts +263 -0
- package/sync/sync-state.ts +176 -0
- package/sync/workspace-lane.ts +20 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teambit/ci",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.487",
|
|
4
4
|
"homepage": "https://bit.cloud/teambit/git/ci",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"componentId": {
|
|
7
7
|
"scope": "teambit.git",
|
|
8
8
|
"name": "ci",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.487"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "4.1.2",
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
"semver": "7.7.1",
|
|
16
16
|
"simple-git": "^3.28.0",
|
|
17
17
|
"@teambit/harmony": "0.4.12",
|
|
18
|
+
"@teambit/cli": "0.0.1362",
|
|
19
|
+
"@teambit/logger": "0.0.1455",
|
|
18
20
|
"@teambit/component-id": "1.2.4",
|
|
19
21
|
"@teambit/component.modules.merge-helper": "0.0.86",
|
|
20
22
|
"@teambit/component.snap-distance": "0.0.144",
|
|
@@ -24,23 +26,26 @@
|
|
|
24
26
|
"@teambit/toolbox.promise.map-pool": "0.0.19",
|
|
25
27
|
"@teambit/toolbox.string.random": "0.0.18",
|
|
26
28
|
"@teambit/bit-error": "0.0.404",
|
|
27
|
-
"@teambit/
|
|
28
|
-
"@teambit/
|
|
29
|
-
"@teambit/
|
|
30
|
-
"@teambit/
|
|
31
|
-
"@teambit/
|
|
32
|
-
"@teambit/
|
|
33
|
-
"@teambit/
|
|
34
|
-
"@teambit/
|
|
35
|
-
"@teambit/
|
|
36
|
-
"@teambit/
|
|
37
|
-
"@teambit/
|
|
38
|
-
"@teambit/
|
|
39
|
-
"@teambit/
|
|
29
|
+
"@teambit/legacy.constants": "0.0.39",
|
|
30
|
+
"@teambit/legacy.scope": "0.0.143",
|
|
31
|
+
"@teambit/legacy.bit-map": "0.0.200",
|
|
32
|
+
"@teambit/toolbox.crypto.sha1": "0.0.20",
|
|
33
|
+
"@teambit/workspace": "1.0.1095",
|
|
34
|
+
"@teambit/builder": "1.0.1095",
|
|
35
|
+
"@teambit/checkout": "1.0.1096",
|
|
36
|
+
"@teambit/config-merger": "0.0.962",
|
|
37
|
+
"@teambit/dependency-resolver": "1.0.1095",
|
|
38
|
+
"@teambit/export": "1.0.1095",
|
|
39
|
+
"@teambit/importer": "1.0.1095",
|
|
40
|
+
"@teambit/lanes": "1.0.1116",
|
|
41
|
+
"@teambit/objects": "0.0.602",
|
|
42
|
+
"@teambit/snapping": "1.0.1095",
|
|
43
|
+
"@teambit/status": "1.0.1120"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@types/lodash": "4.14.165",
|
|
43
47
|
"@types/semver": "7.5.8",
|
|
48
|
+
"yaml": "1.10.2",
|
|
44
49
|
"@teambit/harmony.envs.core-aspect-env": "2.0.6"
|
|
45
50
|
},
|
|
46
51
|
"peerDependencies": {
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { expect } from 'chai';
|
|
2
|
+
import type { GitHostProvider, PrInfo } from './git-host-provider';
|
|
3
|
+
import { selectGitHostProvider } from './git-host-provider';
|
|
4
|
+
import { GitHubHostProvider } from './github-client';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A provider stub: only `name`, `matchesRemote` and `isConfigured` participate in selection, so the
|
|
8
|
+
* PR methods reject — reaching them from `selectGitHostProvider` would be a bug worth failing on.
|
|
9
|
+
*/
|
|
10
|
+
function fakeProvider(opts: {
|
|
11
|
+
name: string;
|
|
12
|
+
hostPattern: RegExp;
|
|
13
|
+
configured: boolean | ((remoteUrl?: string) => boolean);
|
|
14
|
+
}): GitHostProvider {
|
|
15
|
+
const unreachable = () => Promise.reject(new Error(`${opts.name}: selection must not call PR methods`));
|
|
16
|
+
return {
|
|
17
|
+
name: opts.name,
|
|
18
|
+
matchesRemote: (remoteUrl: string) => opts.hostPattern.test(remoteUrl),
|
|
19
|
+
isConfigured: (remoteUrl?: string) =>
|
|
20
|
+
typeof opts.configured === 'function' ? opts.configured(remoteUrl) : opts.configured,
|
|
21
|
+
findPrByBranch: unreachable as () => Promise<PrInfo | undefined>,
|
|
22
|
+
createPr: unreachable as () => Promise<PrInfo>,
|
|
23
|
+
closePr: unreachable,
|
|
24
|
+
comment: unreachable,
|
|
25
|
+
addLabel: unreachable,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const github = (configured = true) => fakeProvider({ name: 'github', hostPattern: /github\.com/, configured });
|
|
30
|
+
const gitlab = (configured = true) => fakeProvider({ name: 'gitlab', hostPattern: /gitlab\.com/, configured });
|
|
31
|
+
const GH = 'https://github.com/acme/shop';
|
|
32
|
+
const UNCLAIMED = 'git@git.acme.internal:acme/shop.git';
|
|
33
|
+
|
|
34
|
+
const claimants = (...names: string[]) =>
|
|
35
|
+
names.map((name) => fakeProvider({ name, hostPattern: /git\.acme/, configured: false }));
|
|
36
|
+
const tied = (...names: string[]) =>
|
|
37
|
+
names.map((name) => fakeProvider({ name, hostPattern: /github\.com/, configured: true }));
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A claimed remote is NEVER routed to a different provider: acting on the wrong host's PRs is worse
|
|
41
|
+
* than degrading to git-only sync. The sole-configured fallback applies only when no provider claims
|
|
42
|
+
* the remote at all. Every selection of nothing must explain itself; a successful one must stay quiet.
|
|
43
|
+
*
|
|
44
|
+
* [case, providers, remote url, selected provider name, reason fragments].
|
|
45
|
+
*/
|
|
46
|
+
const SELECTION: Array<[string, GitHostProvider[], string | undefined, string | undefined, string[]?]> = [
|
|
47
|
+
['by matching host', [github(), gitlab()], 'git@gitlab.com:acme/shop.git', 'gitlab'],
|
|
48
|
+
['by matching host, the other way round', [github(), gitlab()], GH, 'github'],
|
|
49
|
+
[
|
|
50
|
+
'nothing when the claimant is unconfigured but another is',
|
|
51
|
+
[github(false), gitlab(true)],
|
|
52
|
+
GH,
|
|
53
|
+
undefined,
|
|
54
|
+
['"github"', 'not configured'],
|
|
55
|
+
],
|
|
56
|
+
['nothing regardless of registration order', [gitlab(true), github(false)], GH, undefined, ['"github"']],
|
|
57
|
+
['nothing when nothing at all is configured', [github(false), gitlab(false)], GH, undefined, ['"github"']],
|
|
58
|
+
[
|
|
59
|
+
'nothing, naming every claimant when several claim and none is configured',
|
|
60
|
+
claimants('ghe-a', 'ghe-b'),
|
|
61
|
+
'https://git.acme.com/acme/shop',
|
|
62
|
+
undefined,
|
|
63
|
+
['"ghe-a"', '"ghe-b"'],
|
|
64
|
+
],
|
|
65
|
+
['the sole configured provider when no remote url is known', [github(true)], undefined, 'github'],
|
|
66
|
+
['the sole configured of several when no remote url is known', [github(false), gitlab(true)], undefined, 'gitlab'],
|
|
67
|
+
// e.g. a self-hosted GitLab on a custom domain, or an `insteadOf` rewrite of the origin url
|
|
68
|
+
['the sole configured provider for a remote no provider claims', [github(false), gitlab(true)], UNCLAIMED, 'gitlab'],
|
|
69
|
+
[
|
|
70
|
+
'nothing when two configured providers both fail to claim',
|
|
71
|
+
[github(true), gitlab(true)],
|
|
72
|
+
UNCLAIMED,
|
|
73
|
+
undefined,
|
|
74
|
+
['ambiguous'],
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'nothing when two are configured and there is no remote url',
|
|
78
|
+
[github(true), gitlab(true)],
|
|
79
|
+
undefined,
|
|
80
|
+
undefined,
|
|
81
|
+
['ambiguous'],
|
|
82
|
+
],
|
|
83
|
+
['nothing when nothing is registered', [], GH, undefined, ['no git host provider is registered']],
|
|
84
|
+
[
|
|
85
|
+
'nothing when nothing is configured and there is no remote',
|
|
86
|
+
[github(false), gitlab(false)],
|
|
87
|
+
undefined,
|
|
88
|
+
undefined,
|
|
89
|
+
['no git host provider is configured'],
|
|
90
|
+
],
|
|
91
|
+
['the first registered when two configured providers claim the same remote', tied('first', 'second'), GH, 'first'],
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
describe('selectGitHostProvider', () => {
|
|
95
|
+
SELECTION.forEach(([name, providers, remote, picks, reason]) => {
|
|
96
|
+
it(`selects ${name}`, () => {
|
|
97
|
+
const selection = selectGitHostProvider(providers, remote);
|
|
98
|
+
expect(selection.provider?.name, name).to.equal(picks);
|
|
99
|
+
if (picks) {
|
|
100
|
+
expect(selection.reason, 'a successful selection stays quiet').to.equal(undefined);
|
|
101
|
+
} else {
|
|
102
|
+
expect((selection.reason ?? '').length, 'a skipped run must never be silent').to.be.greaterThan(0);
|
|
103
|
+
(reason ?? []).forEach((fragment) => expect(selection.reason).to.contain(fragment));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('passes the remote url to isConfigured, so a host can derive its repo from it', () => {
|
|
109
|
+
const seen: Array<string | undefined> = [];
|
|
110
|
+
const derivesRepoFromRemote = fakeProvider({
|
|
111
|
+
name: 'github',
|
|
112
|
+
hostPattern: /github\.com/,
|
|
113
|
+
configured: (remoteUrl) => {
|
|
114
|
+
seen.push(remoteUrl);
|
|
115
|
+
return Boolean(remoteUrl?.includes('github.com'));
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
expect(selectGitHostProvider([derivesRepoFromRemote], GH).provider?.name).to.equal('github');
|
|
119
|
+
expect(seen).to.deep.equal([GH]);
|
|
120
|
+
expect(selectGitHostProvider([derivesRepoFromRemote], undefined).provider).to.equal(undefined);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// The built-in provider, exercised through the interface it is registered as — the GitHub *client*
|
|
125
|
+
// (API shapes, auth headers, remote parsing) is covered by github-client.spec.ts.
|
|
126
|
+
describe('GitHubHostProvider', () => {
|
|
127
|
+
const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;
|
|
128
|
+
const saved: Record<string, string | undefined> = {};
|
|
129
|
+
|
|
130
|
+
beforeEach(() => {
|
|
131
|
+
envKeys.forEach((key) => {
|
|
132
|
+
saved[key] = process.env[key];
|
|
133
|
+
delete process.env[key];
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
afterEach(() => {
|
|
138
|
+
envKeys.forEach((key) => {
|
|
139
|
+
if (saved[key] === undefined) delete process.env[key];
|
|
140
|
+
else process.env[key] = saved[key];
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('is registrable without credentials: constructing it never throws and it reports unconfigured', () => {
|
|
145
|
+
const provider = new GitHubHostProvider();
|
|
146
|
+
expect(provider.name).to.equal('github');
|
|
147
|
+
expect(provider.isConfigured(GH)).to.equal(false);
|
|
148
|
+
const selection = selectGitHostProvider([provider], GH);
|
|
149
|
+
expect(selection.provider).to.equal(undefined);
|
|
150
|
+
expect(selection.reason).to.contain('"github"');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('is configured once a token is present, deriving the repo from the remote url', () => {
|
|
154
|
+
process.env.BIT_GITHUB_TOKEN = 'tok';
|
|
155
|
+
expect(new GitHubHostProvider().isConfigured('git@github.com:acme/shop.git')).to.equal(true);
|
|
156
|
+
// no repo resolvable (no GITHUB_REPOSITORY, no parseable remote) => still unconfigured
|
|
157
|
+
expect(new GitHubHostProvider().isConfigured(undefined)).to.equal(false);
|
|
158
|
+
// a look-alike host must not be parsed into a repo aimed at api.github.com
|
|
159
|
+
expect(new GitHubHostProvider().isConfigured('https://mygithub.com/acme/shop')).to.equal(false);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('remembers the remote url it was asked about, and only if that remote was ours', () => {
|
|
163
|
+
process.env.BIT_GITHUB_TOKEN = 'tok';
|
|
164
|
+
const ours = new GitHubHostProvider();
|
|
165
|
+
expect(ours.matchesRemote('git@github.com:acme/shop.git')).to.equal(true);
|
|
166
|
+
expect(ours.isConfigured()).to.equal(true);
|
|
167
|
+
// without that hint the same call cannot resolve a repository
|
|
168
|
+
expect(new GitHubHostProvider().isConfigured()).to.equal(false);
|
|
169
|
+
// …and a non-github remote must not poison the hint
|
|
170
|
+
const theirs = new GitHubHostProvider();
|
|
171
|
+
expect(theirs.matchesRemote('https://gitlab.com/acme/shop.git')).to.equal(false);
|
|
172
|
+
expect(theirs.isConfigured()).to.equal(false);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// On Actions the env is always set, so answering "configured" for an unclaimed remote would make
|
|
176
|
+
// this the sole-configured fallback and aim PRs at whatever GITHUB_REPOSITORY named.
|
|
177
|
+
it('is NOT configured or selected for a remote it does not claim, however complete the environment', () => {
|
|
178
|
+
process.env.BIT_GITHUB_TOKEN = 'tok';
|
|
179
|
+
process.env.GITHUB_REPOSITORY = 'acme/shop';
|
|
180
|
+
expect(new GitHubHostProvider().isConfigured('https://gitlab.com/acme/shop.git')).to.equal(false);
|
|
181
|
+
// non-vacuous: the very same environment DOES configure it for a github remote, and for no remote
|
|
182
|
+
expect(new GitHubHostProvider().isConfigured(GH)).to.equal(true);
|
|
183
|
+
expect(new GitHubHostProvider().isConfigured()).to.equal(true);
|
|
184
|
+
const selection = selectGitHostProvider([new GitHubHostProvider()], 'https://gitlab.com/acme/shop.git');
|
|
185
|
+
expect(selection.provider).to.equal(undefined);
|
|
186
|
+
expect(selection.reason).to.contain('no git host provider is configured');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('reports what is missing rather than failing obscurely when a PR call happens unconfigured', async () => {
|
|
190
|
+
let message = '';
|
|
191
|
+
await new GitHubHostProvider().findPrByBranch('lane-x').catch((err) => {
|
|
192
|
+
message = err.message;
|
|
193
|
+
});
|
|
194
|
+
expect(message).to.contain('GITHUB_TOKEN');
|
|
195
|
+
expect(message).to.contain('GITHUB_REPOSITORY');
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The git-host (pull request) contract `bit ci sync` is written against; nothing in the engine may
|
|
3
|
+
* reach past it. Other hosts register via `ci.registerGitHostProvider(...)`. Adding a method is a
|
|
4
|
+
* breaking change for out-of-tree providers.
|
|
5
|
+
*/
|
|
6
|
+
export type PrInfo = {
|
|
7
|
+
number: number;
|
|
8
|
+
state: 'open' | 'closed';
|
|
9
|
+
labels: string[];
|
|
10
|
+
headRef: string;
|
|
11
|
+
htmlUrl: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export interface GitHostProvider {
|
|
15
|
+
/** Stable identifier, used in logs and to tell registered providers apart. E.g. `'github'`. */
|
|
16
|
+
name: string;
|
|
17
|
+
|
|
18
|
+
/** Does this provider serve the repository at `remoteUrl`? A host test, not a credentials test. */
|
|
19
|
+
matchesRemote(remoteUrl: string): boolean;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Token and repository resolvable? Must not throw (unconfigured is a normal state — the engine
|
|
23
|
+
* degrades to git-only sync), must be cheap and idempotent, and must never perform network I/O.
|
|
24
|
+
*/
|
|
25
|
+
isConfigured(remoteUrl?: string): boolean;
|
|
26
|
+
|
|
27
|
+
findPrByBranch(branch: string): Promise<PrInfo | undefined>;
|
|
28
|
+
|
|
29
|
+
createPr(opts: { head: string; base: string; title: string; body: string }): Promise<PrInfo>;
|
|
30
|
+
|
|
31
|
+
closePr(prNumber: number, comment?: string): Promise<void>;
|
|
32
|
+
|
|
33
|
+
comment(prNumber: number, body: string): Promise<void>;
|
|
34
|
+
|
|
35
|
+
addLabel(prNumber: number, label: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The git host to use this run, or nothing plus the reason PR operations are being skipped. */
|
|
39
|
+
export type GitHostSelection = {
|
|
40
|
+
/** The host to use. undefined => PR-less (git-only) sync. */
|
|
41
|
+
provider?: GitHostProvider;
|
|
42
|
+
/** Set exactly when `provider` is undefined; surfaced once per run so PR-less is never silent. */
|
|
43
|
+
reason?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Pick the provider that serves `remoteUrl`, or nothing (PR-less sync) plus the reason why.
|
|
48
|
+
* A claim is exclusive: configuration never overrides `matchesRemote`, because routing to whichever
|
|
49
|
+
* provider holds a token would act on the wrong host's PRs. A sole configured provider is the fallback
|
|
50
|
+
* only when no provider claims the remote; two or more unclaimed-but-configured is too ambiguous.
|
|
51
|
+
*/
|
|
52
|
+
export function selectGitHostProvider(providers: GitHostProvider[], remoteUrl: string | undefined): GitHostSelection {
|
|
53
|
+
if (!providers.length) {
|
|
54
|
+
return { reason: 'no git host provider is registered — skipping all pull request operations' };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const claimants = remoteUrl ? providers.filter((provider) => provider.matchesRemote(remoteUrl)) : [];
|
|
58
|
+
if (claimants.length) {
|
|
59
|
+
const configured = claimants.find((provider) => provider.isConfigured(remoteUrl));
|
|
60
|
+
if (configured) return { provider: configured };
|
|
61
|
+
return {
|
|
62
|
+
reason:
|
|
63
|
+
`the ${listNames(claimants)} git host provider${claimants.length > 1 ? 's' : ''} serve${
|
|
64
|
+
claimants.length > 1 ? '' : 's'
|
|
65
|
+
} this repository's "origin" remote but ${claimants.length > 1 ? 'none is' : 'is not'} configured ` +
|
|
66
|
+
`(missing credentials or repository) — skipping all pull request operations. No other provider is ` +
|
|
67
|
+
`used in its place: that would act on the wrong host.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const configured = providers.filter((provider) => provider.isConfigured(remoteUrl));
|
|
72
|
+
if (configured.length === 1) return { provider: configured[0] };
|
|
73
|
+
if (configured.length > 1) {
|
|
74
|
+
return {
|
|
75
|
+
reason:
|
|
76
|
+
`no git host provider claims this repository's "origin" remote, and ${configured.length} are ` +
|
|
77
|
+
`configured (${listNames(configured)}) — too ambiguous to pick one, skipping all pull request operations`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
reason:
|
|
82
|
+
`no git host provider is configured${remoteUrl ? '' : ' (and the "origin" remote could not be read)'} — ` +
|
|
83
|
+
`skipping all pull request operations`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function listNames(providers: GitHostProvider[]): string {
|
|
88
|
+
return providers.map((provider) => `"${provider.name}"`).join(', ');
|
|
89
|
+
}
|