@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
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.GitHubHostProvider = exports.GitHubClient = void 0;
|
|
7
|
+
exports.isGitHubRemote = isGitHubRemote;
|
|
8
|
+
exports.parseGitHubRepo = parseGitHubRepo;
|
|
9
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
10
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
11
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
12
|
+
/**
|
|
13
|
+
* The `owner/repo` out of a github.com remote URL, anchored to the URL authority. `:digits` is a port
|
|
14
|
+
* only under a scheme; in the scp-like form (`git@github.com:12345/repo`) the colon starts the PATH
|
|
15
|
+
* and an all-digits owner is a legal GitHub username.
|
|
16
|
+
*/
|
|
17
|
+
function parseGitHubRepo(remoteUrl) {
|
|
18
|
+
const url = remoteUrl.trim();
|
|
19
|
+
const hasScheme = /^[a-z+]+:\/\//i.test(url);
|
|
20
|
+
const match = hasScheme ? url.match(/(?:\/\/|@)github\.com(?::\d+)?\/([^/]+\/[^/]+?)(?:\.git)?$/i) : url.match(/(?:^|@)github\.com[:/]([^/]+\/[^/]+?)(?:\.git)?$/i);
|
|
21
|
+
return match?.[1];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The host a git remote URL points at, lower-cased, or undefined when the URL has no authority. */
|
|
25
|
+
function remoteHost(remoteUrl) {
|
|
26
|
+
return remoteUrl.trim().match(/^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?([^:/]+)/i)?.[1]?.toLowerCase();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Does this remote point at GitHub? A host test only — an unparseable github.com remote is still
|
|
31
|
+
* GitHub's to claim. Host equality, not substring: `github.com` can be a path segment on another host.
|
|
32
|
+
*/
|
|
33
|
+
function isGitHubRemote(remoteUrl) {
|
|
34
|
+
return remoteHost(remoteUrl) === 'github.com';
|
|
35
|
+
}
|
|
36
|
+
const API = 'https://api.github.com';
|
|
37
|
+
|
|
38
|
+
/** Warning sink; a plain callback rather than a `Logger` so this module needs no logger aspect. */
|
|
39
|
+
|
|
40
|
+
const noopWarn = () => {};
|
|
41
|
+
class GitHubClient {
|
|
42
|
+
constructor(opts) {
|
|
43
|
+
_defineProperty(this, "name", 'github');
|
|
44
|
+
_defineProperty(this, "token", void 0);
|
|
45
|
+
/** `owner/repo`. Readable so callers (and tests) can see *which* repository a resolved client speaks to. */
|
|
46
|
+
_defineProperty(this, "repo", void 0);
|
|
47
|
+
_defineProperty(this, "fetchImpl", void 0);
|
|
48
|
+
this.token = opts.token;
|
|
49
|
+
this.repo = opts.repo;
|
|
50
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The client this environment describes, or undefined when it describes none. `origin` outranks
|
|
55
|
+
* `GITHUB_REPOSITORY`: every PR operation names a branch that only exists on `origin`, so if the two
|
|
56
|
+
* disagree the origin-parsed repository wins and the mismatch is warned about once.
|
|
57
|
+
*/
|
|
58
|
+
static fromEnv(gitRemoteUrl, warn = noopWarn) {
|
|
59
|
+
// BIT_GITHUB_TOKEN is the override; GITHUB_TOKEN is the ambient default Actions injects everywhere.
|
|
60
|
+
const token = process.env.BIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;
|
|
61
|
+
// A remote that is demonstrably not GitHub's is a hard no, whatever the environment says —
|
|
62
|
+
// GITHUB_REPOSITORY is set on every Actions job, including ones whose origin is another host.
|
|
63
|
+
if (gitRemoteUrl && !isGitHubRemote(gitRemoteUrl)) return undefined;
|
|
64
|
+
const repoFromRemote = gitRemoteUrl ? parseGitHubRepo(gitRemoteUrl) : undefined;
|
|
65
|
+
const repoFromEnv = process.env.GITHUB_REPOSITORY;
|
|
66
|
+
const repo = repoFromRemote || repoFromEnv;
|
|
67
|
+
if (!token || !repo) return undefined;
|
|
68
|
+
// GitHub owner/repo names are case-insensitive.
|
|
69
|
+
if (repoFromRemote && repoFromEnv && repoFromRemote.toLowerCase() !== repoFromEnv.toLowerCase()) {
|
|
70
|
+
warn(`bit ci sync: GITHUB_REPOSITORY is "${repoFromEnv}" but the "origin" remote points at ` + `"${repoFromRemote}" — using "${repoFromRemote}", the repository this run pushes its branches to.`);
|
|
71
|
+
}
|
|
72
|
+
return new GitHubClient({
|
|
73
|
+
token,
|
|
74
|
+
repo
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
matchesRemote(remoteUrl) {
|
|
78
|
+
return isGitHubRemote(remoteUrl);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A constructed client always has both halves — it cannot be built without them. */
|
|
82
|
+
isConfigured() {
|
|
83
|
+
return Boolean(this.token && this.repo);
|
|
84
|
+
}
|
|
85
|
+
async request(method, path, body) {
|
|
86
|
+
const res = await this.fetchImpl(`${API}/repos/${this.repo}${path}`, {
|
|
87
|
+
method,
|
|
88
|
+
headers: {
|
|
89
|
+
authorization: `Bearer ${this.token}`,
|
|
90
|
+
accept: 'application/vnd.github+json',
|
|
91
|
+
'content-type': 'application/json',
|
|
92
|
+
'x-github-api-version': '2022-11-28'
|
|
93
|
+
},
|
|
94
|
+
body: body === undefined ? undefined : JSON.stringify(body)
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
const text = await res.text().catch(() => '');
|
|
98
|
+
throw new Error(`GitHub API ${method} ${path} failed: ${res.status} ${text}`);
|
|
99
|
+
}
|
|
100
|
+
return res.status === 204 ? undefined : res.json();
|
|
101
|
+
}
|
|
102
|
+
toPrInfo(raw) {
|
|
103
|
+
return {
|
|
104
|
+
number: raw.number,
|
|
105
|
+
state: raw.state,
|
|
106
|
+
labels: (raw.labels ?? []).map(l => typeof l === 'string' ? l : l.name),
|
|
107
|
+
headRef: raw.head?.ref,
|
|
108
|
+
htmlUrl: raw.html_url
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async findPrByBranch(branch) {
|
|
112
|
+
const owner = this.repo.split('/')[0];
|
|
113
|
+
const list = await this.request('GET', `/pulls?state=open&head=${encodeURIComponent(`${owner}:${branch}`)}`);
|
|
114
|
+
return list.length ? this.toPrInfo(list[0]) : undefined;
|
|
115
|
+
}
|
|
116
|
+
async createPr(opts) {
|
|
117
|
+
return this.toPrInfo(await this.request('POST', '/pulls', opts));
|
|
118
|
+
}
|
|
119
|
+
async closePr(prNumber, comment) {
|
|
120
|
+
if (comment) await this.comment(prNumber, comment);
|
|
121
|
+
await this.request('PATCH', `/pulls/${prNumber}`, {
|
|
122
|
+
state: 'closed'
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
async comment(prNumber, body) {
|
|
126
|
+
await this.request('POST', `/issues/${prNumber}/comments`, {
|
|
127
|
+
body
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async addLabel(prNumber, label) {
|
|
131
|
+
await this.request('POST', `/issues/${prNumber}/labels`, {
|
|
132
|
+
labels: [label]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The registrable GitHub provider: resolves its `GitHubClient` from the environment lazily, since
|
|
139
|
+
* registration happens at aspect load, before credentials are known. A resolved client is memoized;
|
|
140
|
+
* an unresolved one is retried so a later call that does carry the remote URL can still succeed.
|
|
141
|
+
*/
|
|
142
|
+
exports.GitHubClient = GitHubClient;
|
|
143
|
+
class GitHubHostProvider {
|
|
144
|
+
constructor(onWarning = noopWarn) {
|
|
145
|
+
this.onWarning = onWarning;
|
|
146
|
+
_defineProperty(this, "name", 'github');
|
|
147
|
+
_defineProperty(this, "client", void 0);
|
|
148
|
+
/** The last `origin` URL seen, so a PR method can still recover `owner/repo` from it. */
|
|
149
|
+
_defineProperty(this, "remoteHint", void 0);
|
|
150
|
+
}
|
|
151
|
+
matchesRemote(remoteUrl) {
|
|
152
|
+
const matches = isGitHubRemote(remoteUrl);
|
|
153
|
+
// Only remember a remote that is ours — a foreign hint could later pair with GITHUB_REPOSITORY
|
|
154
|
+
// into a client for the wrong repository.
|
|
155
|
+
if (matches) this.remoteHint = remoteUrl;
|
|
156
|
+
return matches;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A known non-GitHub remote can never count as configured, however complete the environment looks:
|
|
161
|
+
* on Actions the env is always set, so answering "configured" for a remote this provider declined to
|
|
162
|
+
* claim would make it the sole-configured fallback and aim PRs at the wrong host's repository.
|
|
163
|
+
*/
|
|
164
|
+
isConfigured(remoteUrl) {
|
|
165
|
+
if (remoteUrl && !isGitHubRemote(remoteUrl)) return false;
|
|
166
|
+
if (remoteUrl) this.remoteHint = remoteUrl;
|
|
167
|
+
return Boolean(this.resolveClient(remoteUrl));
|
|
168
|
+
}
|
|
169
|
+
async findPrByBranch(branch) {
|
|
170
|
+
return this.requireClient().findPrByBranch(branch);
|
|
171
|
+
}
|
|
172
|
+
async createPr(opts) {
|
|
173
|
+
return this.requireClient().createPr(opts);
|
|
174
|
+
}
|
|
175
|
+
async closePr(prNumber, comment) {
|
|
176
|
+
return this.requireClient().closePr(prNumber, comment);
|
|
177
|
+
}
|
|
178
|
+
async comment(prNumber, body) {
|
|
179
|
+
return this.requireClient().comment(prNumber, body);
|
|
180
|
+
}
|
|
181
|
+
async addLabel(prNumber, label) {
|
|
182
|
+
return this.requireClient().addLabel(prNumber, label);
|
|
183
|
+
}
|
|
184
|
+
resolveClient(remoteUrl) {
|
|
185
|
+
if (!this.client) this.client = GitHubClient.fromEnv(remoteUrl ?? this.remoteHint, this.onWarning);
|
|
186
|
+
return this.client;
|
|
187
|
+
}
|
|
188
|
+
requireClient() {
|
|
189
|
+
const client = this.resolveClient();
|
|
190
|
+
if (!client) {
|
|
191
|
+
throw new Error(`the github host provider is not configured: set BIT_GITHUB_TOKEN (or GITHUB_TOKEN), and either ` + `GITHUB_REPOSITORY or a github.com "origin" remote`);
|
|
192
|
+
}
|
|
193
|
+
return client;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.GitHubHostProvider = GitHubHostProvider;
|
|
197
|
+
|
|
198
|
+
//# sourceMappingURL=github-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["parseGitHubRepo","remoteUrl","url","trim","hasScheme","test","match","remoteHost","toLowerCase","isGitHubRemote","API","noopWarn","GitHubClient","constructor","opts","_defineProperty","token","repo","fetchImpl","fetch","fromEnv","gitRemoteUrl","warn","process","env","BIT_GITHUB_TOKEN","GITHUB_TOKEN","undefined","repoFromRemote","repoFromEnv","GITHUB_REPOSITORY","matchesRemote","isConfigured","Boolean","request","method","path","body","res","headers","authorization","accept","JSON","stringify","ok","text","catch","Error","status","json","toPrInfo","raw","number","state","labels","map","l","name","headRef","head","ref","htmlUrl","html_url","findPrByBranch","branch","owner","split","list","encodeURIComponent","length","createPr","closePr","prNumber","comment","addLabel","label","exports","GitHubHostProvider","onWarning","matches","remoteHint","resolveClient","requireClient","client"],"sources":["github-client.ts"],"sourcesContent":["import type { GitHostProvider, PrInfo } from './git-host-provider';\n\n/**\n * The `owner/repo` out of a github.com remote URL, anchored to the URL authority. `:digits` is a port\n * only under a scheme; in the scp-like form (`git@github.com:12345/repo`) the colon starts the PATH\n * and an all-digits owner is a legal GitHub username.\n */\nexport function parseGitHubRepo(remoteUrl: string): string | undefined {\n const url = remoteUrl.trim();\n const hasScheme = /^[a-z+]+:\\/\\//i.test(url);\n const match = hasScheme\n ? url.match(/(?:\\/\\/|@)github\\.com(?::\\d+)?\\/([^/]+\\/[^/]+?)(?:\\.git)?$/i)\n : url.match(/(?:^|@)github\\.com[:/]([^/]+\\/[^/]+?)(?:\\.git)?$/i);\n return match?.[1];\n}\n\n/** The host a git remote URL points at, lower-cased, or undefined when the URL has no authority. */\nfunction remoteHost(remoteUrl: string): string | undefined {\n return remoteUrl\n .trim()\n .match(/^(?:[a-z+]+:\\/\\/)?(?:[^@/]+@)?([^:/]+)/i)?.[1]\n ?.toLowerCase();\n}\n\n/**\n * Does this remote point at GitHub? A host test only — an unparseable github.com remote is still\n * GitHub's to claim. Host equality, not substring: `github.com` can be a path segment on another host.\n */\nexport function isGitHubRemote(remoteUrl: string): boolean {\n return remoteHost(remoteUrl) === 'github.com';\n}\n\nconst API = 'https://api.github.com';\n\n/** Warning sink; a plain callback rather than a `Logger` so this module needs no logger aspect. */\nexport type WarnFn = (message: string) => void;\n\nconst noopWarn: WarnFn = () => {};\n\nexport class GitHubClient implements GitHostProvider {\n readonly name = 'github';\n\n private token: string;\n /** `owner/repo`. Readable so callers (and tests) can see *which* repository a resolved client speaks to. */\n readonly repo: string;\n private fetchImpl: typeof fetch;\n\n constructor(opts: { token: string; repo: string; fetchImpl?: typeof fetch }) {\n this.token = opts.token;\n this.repo = opts.repo;\n this.fetchImpl = opts.fetchImpl ?? fetch;\n }\n\n /**\n * The client this environment describes, or undefined when it describes none. `origin` outranks\n * `GITHUB_REPOSITORY`: every PR operation names a branch that only exists on `origin`, so if the two\n * disagree the origin-parsed repository wins and the mismatch is warned about once.\n */\n static fromEnv(gitRemoteUrl?: string, warn: WarnFn = noopWarn): GitHubClient | undefined {\n // BIT_GITHUB_TOKEN is the override; GITHUB_TOKEN is the ambient default Actions injects everywhere.\n const token = process.env.BIT_GITHUB_TOKEN || process.env.GITHUB_TOKEN;\n // A remote that is demonstrably not GitHub's is a hard no, whatever the environment says —\n // GITHUB_REPOSITORY is set on every Actions job, including ones whose origin is another host.\n if (gitRemoteUrl && !isGitHubRemote(gitRemoteUrl)) return undefined;\n const repoFromRemote = gitRemoteUrl ? parseGitHubRepo(gitRemoteUrl) : undefined;\n const repoFromEnv = process.env.GITHUB_REPOSITORY;\n const repo = repoFromRemote || repoFromEnv;\n if (!token || !repo) return undefined;\n // GitHub owner/repo names are case-insensitive.\n if (repoFromRemote && repoFromEnv && repoFromRemote.toLowerCase() !== repoFromEnv.toLowerCase()) {\n warn(\n `bit ci sync: GITHUB_REPOSITORY is \"${repoFromEnv}\" but the \"origin\" remote points at ` +\n `\"${repoFromRemote}\" — using \"${repoFromRemote}\", the repository this run pushes its branches to.`\n );\n }\n return new GitHubClient({ token, repo });\n }\n\n matchesRemote(remoteUrl: string): boolean {\n return isGitHubRemote(remoteUrl);\n }\n\n /** A constructed client always has both halves — it cannot be built without them. */\n isConfigured(): boolean {\n return Boolean(this.token && this.repo);\n }\n\n private async request(method: string, path: string, body?: unknown): Promise<any> {\n const res = await this.fetchImpl(`${API}/repos/${this.repo}${path}`, {\n method,\n headers: {\n authorization: `Bearer ${this.token}`,\n accept: 'application/vnd.github+json',\n 'content-type': 'application/json',\n 'x-github-api-version': '2022-11-28',\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n });\n if (!res.ok) {\n const text = await res.text().catch(() => '');\n throw new Error(`GitHub API ${method} ${path} failed: ${res.status} ${text}`);\n }\n return res.status === 204 ? undefined : res.json();\n }\n\n private toPrInfo(raw: any): PrInfo {\n return {\n number: raw.number,\n state: raw.state,\n labels: (raw.labels ?? []).map((l: any) => (typeof l === 'string' ? l : l.name)),\n headRef: raw.head?.ref,\n htmlUrl: raw.html_url,\n };\n }\n\n async findPrByBranch(branch: string): Promise<PrInfo | undefined> {\n const owner = this.repo.split('/')[0];\n const list = await this.request('GET', `/pulls?state=open&head=${encodeURIComponent(`${owner}:${branch}`)}`);\n return list.length ? this.toPrInfo(list[0]) : undefined;\n }\n\n async createPr(opts: { head: string; base: string; title: string; body: string }): Promise<PrInfo> {\n return this.toPrInfo(await this.request('POST', '/pulls', opts));\n }\n\n async closePr(prNumber: number, comment?: string): Promise<void> {\n if (comment) await this.comment(prNumber, comment);\n await this.request('PATCH', `/pulls/${prNumber}`, { state: 'closed' });\n }\n\n async comment(prNumber: number, body: string): Promise<void> {\n await this.request('POST', `/issues/${prNumber}/comments`, { body });\n }\n\n async addLabel(prNumber: number, label: string): Promise<void> {\n await this.request('POST', `/issues/${prNumber}/labels`, { labels: [label] });\n }\n}\n\n/**\n * The registrable GitHub provider: resolves its `GitHubClient` from the environment lazily, since\n * registration happens at aspect load, before credentials are known. A resolved client is memoized;\n * an unresolved one is retried so a later call that does carry the remote URL can still succeed.\n */\nexport class GitHubHostProvider implements GitHostProvider {\n readonly name = 'github';\n\n private client: GitHubClient | undefined;\n\n /** The last `origin` URL seen, so a PR method can still recover `owner/repo` from it. */\n private remoteHint: string | undefined;\n\n constructor(private onWarning: WarnFn = noopWarn) {}\n\n matchesRemote(remoteUrl: string): boolean {\n const matches = isGitHubRemote(remoteUrl);\n // Only remember a remote that is ours — a foreign hint could later pair with GITHUB_REPOSITORY\n // into a client for the wrong repository.\n if (matches) this.remoteHint = remoteUrl;\n return matches;\n }\n\n /**\n * A known non-GitHub remote can never count as configured, however complete the environment looks:\n * on Actions the env is always set, so answering \"configured\" for a remote this provider declined to\n * claim would make it the sole-configured fallback and aim PRs at the wrong host's repository.\n */\n isConfigured(remoteUrl?: string): boolean {\n if (remoteUrl && !isGitHubRemote(remoteUrl)) return false;\n if (remoteUrl) this.remoteHint = remoteUrl;\n return Boolean(this.resolveClient(remoteUrl));\n }\n\n async findPrByBranch(branch: string): Promise<PrInfo | undefined> {\n return this.requireClient().findPrByBranch(branch);\n }\n\n async createPr(opts: { head: string; base: string; title: string; body: string }): Promise<PrInfo> {\n return this.requireClient().createPr(opts);\n }\n\n async closePr(prNumber: number, comment?: string): Promise<void> {\n return this.requireClient().closePr(prNumber, comment);\n }\n\n async comment(prNumber: number, body: string): Promise<void> {\n return this.requireClient().comment(prNumber, body);\n }\n\n async addLabel(prNumber: number, label: string): Promise<void> {\n return this.requireClient().addLabel(prNumber, label);\n }\n\n private resolveClient(remoteUrl?: string): GitHubClient | undefined {\n if (!this.client) this.client = GitHubClient.fromEnv(remoteUrl ?? this.remoteHint, this.onWarning);\n return this.client;\n }\n\n private requireClient(): GitHubClient {\n const client = this.resolveClient();\n if (!client) {\n throw new Error(\n `the github host provider is not configured: set BIT_GITHUB_TOKEN (or GITHUB_TOKEN), and either ` +\n `GITHUB_REPOSITORY or a github.com \"origin\" remote`\n );\n }\n return client;\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function _chai() {
|
|
4
|
+
const data = require("chai");
|
|
5
|
+
_chai = function () {
|
|
6
|
+
return data;
|
|
7
|
+
};
|
|
8
|
+
return data;
|
|
9
|
+
}
|
|
10
|
+
function _githubClient() {
|
|
11
|
+
const data = require("./github-client");
|
|
12
|
+
_githubClient = function () {
|
|
13
|
+
return data;
|
|
14
|
+
};
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two
|
|
19
|
+
* predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.
|
|
20
|
+
*/
|
|
21
|
+
const REMOTES = [
|
|
22
|
+
// url, parseGitHubRepo, isGitHubRemote
|
|
23
|
+
['git@github.com:acme/shop.git', 'acme/shop', true], ['https://github.com/acme/shop.git', 'acme/shop', true], ['https://github.com/acme/shop', 'acme/shop', true], ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],
|
|
24
|
+
// mixed-case host, an explicit port, and credentials in the url are all still github.com
|
|
25
|
+
['https://GitHub.com/acme/shop.git', 'acme/shop', true], ['https://github.com:443/acme/shop.git', 'acme/shop', true], ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true], ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],
|
|
26
|
+
// an all-digits scp owner is an owner, never a port
|
|
27
|
+
['git@github.com:12345/shop.git', '12345/shop', true],
|
|
28
|
+
// another host that merely has "github.com" in its path, in both url forms
|
|
29
|
+
['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false], ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],
|
|
30
|
+
// look-alike and unrelated hosts
|
|
31
|
+
['https://mygithub.com/acme/shop', undefined, false], ['https://github.company.com/acme/shop', undefined, false], ['git@gitlab.com:acme/shop.git', undefined, false], ['https://gitlab.com/acme/shop.git', undefined, false], ['/srv/git/github.com/acme/shop.git', undefined, false]];
|
|
32
|
+
describe('parseGitHubRepo / isGitHubRemote', () => {
|
|
33
|
+
it('agree on every remote form git accepts, and on every host that is not github.com', () => {
|
|
34
|
+
REMOTES.forEach(([url, repo, isGitHub]) => {
|
|
35
|
+
(0, _chai().expect)((0, _githubClient().parseGitHubRepo)(url), url).to.equal(repo);
|
|
36
|
+
(0, _chai().expect)((0, _githubClient().isGitHubRemote)(url), url).to.equal(isGitHub);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('GitHubClient.fromEnv', () => {
|
|
41
|
+
const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'];
|
|
42
|
+
const saved = {};
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
envKeys.forEach(key => {
|
|
45
|
+
saved[key] = process.env[key];
|
|
46
|
+
delete process.env[key];
|
|
47
|
+
});
|
|
48
|
+
process.env.GITHUB_TOKEN = 'tok';
|
|
49
|
+
});
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
envKeys.forEach(key => {
|
|
52
|
+
if (saved[key] === undefined) delete process.env[key];else process.env[key] = saved[key];
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
const OURS = 'git@github.com:acme/shop.git';
|
|
56
|
+
/** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */
|
|
57
|
+
const RESOLUTION = [['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],
|
|
58
|
+
// the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported
|
|
59
|
+
['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],
|
|
60
|
+
// GitHub repository names are case-insensitive, so a case-only difference is not a disagreement
|
|
61
|
+
['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'], ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'], ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],
|
|
62
|
+
// a remote that is demonstrably not github stays unconfigured however complete the environment is
|
|
63
|
+
['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined], ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined]];
|
|
64
|
+
RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {
|
|
65
|
+
it(`resolves the repository when ${name}`, () => {
|
|
66
|
+
process.env.GITHUB_REPOSITORY = repository;
|
|
67
|
+
const warnings = [];
|
|
68
|
+
const client = _githubClient().GitHubClient.fromEnv(remote, message => warnings.push(message));
|
|
69
|
+
(0, _chai().expect)(client?.repo).to.equal(repo);
|
|
70
|
+
(warns ?? []).forEach(fragment => (0, _chai().expect)(warnings.join('\n')).to.contain(fragment));
|
|
71
|
+
if (!warns) (0, _chai().expect)(warnings).to.deep.equal([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('needs a token: a repository on its own configures nothing', () => {
|
|
75
|
+
delete process.env.GITHUB_TOKEN;
|
|
76
|
+
process.env.GITHUB_REPOSITORY = 'acme/shop';
|
|
77
|
+
(0, _chai().expect)(_githubClient().GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Asserted through the `authorization` header, so what is pinned is the token the client actually
|
|
81
|
+
// sends. The fetch stub must be installed before `fromEnv` runs.
|
|
82
|
+
describe('token precedence', () => {
|
|
83
|
+
let realFetch;
|
|
84
|
+
let calls;
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
realFetch = globalThis.fetch;
|
|
87
|
+
calls = [];
|
|
88
|
+
globalThis.fetch = async (url, init) => {
|
|
89
|
+
calls.push({
|
|
90
|
+
url: String(url),
|
|
91
|
+
init
|
|
92
|
+
});
|
|
93
|
+
return new Response(JSON.stringify([]), {
|
|
94
|
+
status: 200,
|
|
95
|
+
headers: {
|
|
96
|
+
'content-type': 'application/json'
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
afterEach(() => {
|
|
102
|
+
globalThis.fetch = realFetch;
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/** The token a client built from this environment puts on the wire. */
|
|
106
|
+
async function sentToken() {
|
|
107
|
+
const client = _githubClient().GitHubClient.fromEnv('git@github.com:acme/shop.git');
|
|
108
|
+
await client?.findPrByBranch('lane-x');
|
|
109
|
+
return calls[0]?.init?.headers?.authorization;
|
|
110
|
+
}
|
|
111
|
+
const PRECEDENCE = [
|
|
112
|
+
// the override beats the ambient default
|
|
113
|
+
['both set', {
|
|
114
|
+
GITHUB_TOKEN: 'workflow-token',
|
|
115
|
+
BIT_GITHUB_TOKEN: 'user-pat'
|
|
116
|
+
}, 'Bearer user-pat'],
|
|
117
|
+
// the ordinary Actions job
|
|
118
|
+
['only GITHUB_TOKEN', {
|
|
119
|
+
GITHUB_TOKEN: 'workflow-token'
|
|
120
|
+
}, 'Bearer workflow-token'],
|
|
121
|
+
// any CI that is not Actions
|
|
122
|
+
['only BIT_GITHUB_TOKEN', {
|
|
123
|
+
BIT_GITHUB_TOKEN: 'user-pat'
|
|
124
|
+
}, 'Bearer user-pat'], ['an EMPTY override', {
|
|
125
|
+
GITHUB_TOKEN: 'workflow-token',
|
|
126
|
+
BIT_GITHUB_TOKEN: ''
|
|
127
|
+
}, 'Bearer workflow-token']];
|
|
128
|
+
PRECEDENCE.forEach(([name, env, expected]) => {
|
|
129
|
+
it(`sends the right token with ${name}`, async () => {
|
|
130
|
+
delete process.env.GITHUB_TOKEN;
|
|
131
|
+
Object.entries(env).forEach(([key, value]) => {
|
|
132
|
+
process.env[key] = value;
|
|
133
|
+
});
|
|
134
|
+
(0, _chai().expect)(await sentToken()).to.equal(expected);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
it('is not configured when neither token is set, and puts nothing on the wire', async () => {
|
|
138
|
+
delete process.env.GITHUB_TOKEN;
|
|
139
|
+
process.env.GITHUB_REPOSITORY = 'acme/shop';
|
|
140
|
+
(0, _chai().expect)(_githubClient().GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);
|
|
141
|
+
(0, _chai().expect)(calls).to.have.lengthOf(0);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
describe('GitHubClient', () => {
|
|
146
|
+
it('createPr posts the pull-request payload to the pulls endpoint', async () => {
|
|
147
|
+
const calls = [];
|
|
148
|
+
const fakeFetch = async (url, init) => {
|
|
149
|
+
calls.push({
|
|
150
|
+
url: String(url),
|
|
151
|
+
init
|
|
152
|
+
});
|
|
153
|
+
return new Response(JSON.stringify({
|
|
154
|
+
number: 7,
|
|
155
|
+
state: 'open',
|
|
156
|
+
labels: [],
|
|
157
|
+
head: {
|
|
158
|
+
ref: 'lane-x'
|
|
159
|
+
},
|
|
160
|
+
html_url: 'http://pr/7'
|
|
161
|
+
}), {
|
|
162
|
+
status: 201,
|
|
163
|
+
headers: {
|
|
164
|
+
'content-type': 'application/json'
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
const client = new (_githubClient().GitHubClient)({
|
|
169
|
+
token: 'tok',
|
|
170
|
+
repo: 'acme/shop',
|
|
171
|
+
fetchImpl: fakeFetch
|
|
172
|
+
});
|
|
173
|
+
const pr = await client.createPr({
|
|
174
|
+
head: 'lane-x',
|
|
175
|
+
base: 'main',
|
|
176
|
+
title: 't',
|
|
177
|
+
body: 'b'
|
|
178
|
+
});
|
|
179
|
+
(0, _chai().expect)(pr.number).to.equal(7);
|
|
180
|
+
(0, _chai().expect)(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');
|
|
181
|
+
(0, _chai().expect)(calls[0].init.method).to.equal('POST');
|
|
182
|
+
(0, _chai().expect)(JSON.parse(calls[0].init.body)).to.deep.equal({
|
|
183
|
+
head: 'lane-x',
|
|
184
|
+
base: 'main',
|
|
185
|
+
title: 't',
|
|
186
|
+
body: 'b'
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
it('findPrByBranch returns undefined on empty list', async () => {
|
|
190
|
+
const fakeFetch = async () => new Response(JSON.stringify([]), {
|
|
191
|
+
status: 200,
|
|
192
|
+
headers: {
|
|
193
|
+
'content-type': 'application/json'
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const client = new (_githubClient().GitHubClient)({
|
|
197
|
+
token: 'tok',
|
|
198
|
+
repo: 'acme/shop',
|
|
199
|
+
fetchImpl: fakeFetch
|
|
200
|
+
});
|
|
201
|
+
(0, _chai().expect)(await client.findPrByBranch('lane-x')).to.equal(undefined);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
//# sourceMappingURL=github-client.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["_chai","data","require","_githubClient","REMOTES","undefined","describe","it","forEach","url","repo","isGitHub","expect","parseGitHubRepo","to","equal","isGitHubRemote","envKeys","saved","beforeEach","key","process","env","GITHUB_TOKEN","afterEach","OURS","RESOLUTION","name","repository","remote","warns","GITHUB_REPOSITORY","warnings","client","GitHubClient","fromEnv","message","push","fragment","join","contain","deep","realFetch","calls","globalThis","fetch","init","String","Response","JSON","stringify","status","headers","sentToken","findPrByBranch","authorization","PRECEDENCE","BIT_GITHUB_TOKEN","expected","Object","entries","value","have","lengthOf","fakeFetch","number","state","labels","head","ref","html_url","token","fetchImpl","pr","createPr","base","title","body","method","parse"],"sources":["github-client.spec.ts"],"sourcesContent":["import { expect } from 'chai';\nimport { GitHubClient, isGitHubRemote, parseGitHubRepo } from './github-client';\n\n/**\n * A claimed remote the parse then fails on silently degrades the whole run to PR-less mode, so the two\n * predicates are pinned over the same remote shapes: whatever `isGitHubRemote` claims must parse.\n */\nconst REMOTES: Array<[string, string | undefined, boolean]> = [\n // url, parseGitHubRepo, isGitHubRemote\n ['git@github.com:acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com/acme/shop', 'acme/shop', true],\n ['ssh://git@github.com/acme/shop.git', 'acme/shop', true],\n // mixed-case host, an explicit port, and credentials in the url are all still github.com\n ['https://GitHub.com/acme/shop.git', 'acme/shop', true],\n ['https://github.com:443/acme/shop.git', 'acme/shop', true],\n ['ssh://git@github.com:22/acme/shop.git', 'acme/shop', true],\n ['https://x-access-token:tok@github.com/acme/shop.git', 'acme/shop', true],\n // an all-digits scp owner is an owner, never a port\n ['git@github.com:12345/shop.git', '12345/shop', true],\n // another host that merely has \"github.com\" in its path, in both url forms\n ['https://gitlab.example.com/mirrors/github.com/acme/repo.git', undefined, false],\n ['git@gitlab.example.com:mirrors/github.com/acme/repo.git', undefined, false],\n // look-alike and unrelated hosts\n ['https://mygithub.com/acme/shop', undefined, false],\n ['https://github.company.com/acme/shop', undefined, false],\n ['git@gitlab.com:acme/shop.git', undefined, false],\n ['https://gitlab.com/acme/shop.git', undefined, false],\n ['/srv/git/github.com/acme/shop.git', undefined, false],\n];\n\ndescribe('parseGitHubRepo / isGitHubRemote', () => {\n it('agree on every remote form git accepts, and on every host that is not github.com', () => {\n REMOTES.forEach(([url, repo, isGitHub]) => {\n expect(parseGitHubRepo(url), url).to.equal(repo);\n expect(isGitHubRemote(url), url).to.equal(isGitHub);\n });\n });\n});\n\ndescribe('GitHubClient.fromEnv', () => {\n const envKeys = ['GITHUB_TOKEN', 'BIT_GITHUB_TOKEN', 'GITHUB_REPOSITORY'] as const;\n const saved: Record<string, string | undefined> = {};\n\n beforeEach(() => {\n envKeys.forEach((key) => {\n saved[key] = process.env[key];\n delete process.env[key];\n });\n process.env.GITHUB_TOKEN = 'tok';\n });\n\n afterEach(() => {\n envKeys.forEach((key) => {\n if (saved[key] === undefined) delete process.env[key];\n else process.env[key] = saved[key];\n });\n });\n\n const OURS = 'git@github.com:acme/shop.git';\n /** [case, GITHUB_REPOSITORY, remote, resolved repo, warning fragments]. */\n const RESOLUTION: Array<[string, string, string | undefined, string | undefined, string[]?]> = [\n ['the remote and the environment agree', 'acme/shop', OURS, 'acme/shop'],\n // the ORIGIN-parsed repository outranks GITHUB_REPOSITORY, and the disagreement must be reported\n ['they disagree', 'other-org/other-repo', OURS, 'acme/shop', ['other-org/other-repo', 'acme/shop']],\n // GitHub repository names are case-insensitive, so a case-only difference is not a disagreement\n ['they differ only in case', 'Acme/Shop', OURS, 'acme/shop'],\n ['there is no remote to parse', 'acme/shop', undefined, 'acme/shop'],\n ['the remote is a github url nothing parses out of', 'acme/shop', 'https://github.com/', 'acme/shop'],\n // a remote that is demonstrably not github stays unconfigured however complete the environment is\n ['the remote is another host', 'acme/shop', 'https://gitlab.com/acme/shop.git', undefined],\n ['the remote is a look-alike host', 'acme/shop', 'https://mygithub.com/acme/shop', undefined],\n ];\n\n RESOLUTION.forEach(([name, repository, remote, repo, warns]) => {\n it(`resolves the repository when ${name}`, () => {\n process.env.GITHUB_REPOSITORY = repository;\n const warnings: string[] = [];\n const client = GitHubClient.fromEnv(remote, (message) => warnings.push(message));\n expect(client?.repo).to.equal(repo);\n (warns ?? []).forEach((fragment) => expect(warnings.join('\\n')).to.contain(fragment));\n if (!warns) expect(warnings).to.deep.equal([]);\n });\n });\n\n it('needs a token: a repository on its own configures nothing', () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n });\n\n // Asserted through the `authorization` header, so what is pinned is the token the client actually\n // sends. The fetch stub must be installed before `fromEnv` runs.\n describe('token precedence', () => {\n let realFetch: typeof fetch;\n let calls: Array<{ url: string; init: any }>;\n\n beforeEach(() => {\n realFetch = globalThis.fetch;\n calls = [];\n globalThis.fetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(JSON.stringify([]), { status: 200, headers: { 'content-type': 'application/json' } });\n }) as typeof fetch;\n });\n\n afterEach(() => {\n globalThis.fetch = realFetch;\n });\n\n /** The token a client built from this environment puts on the wire. */\n async function sentToken(): Promise<string | undefined> {\n const client = GitHubClient.fromEnv('git@github.com:acme/shop.git');\n await client?.findPrByBranch('lane-x');\n return calls[0]?.init?.headers?.authorization;\n }\n\n const PRECEDENCE: Array<[string, Record<string, string | undefined>, string]> = [\n // the override beats the ambient default\n ['both set', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n // the ordinary Actions job\n ['only GITHUB_TOKEN', { GITHUB_TOKEN: 'workflow-token' }, 'Bearer workflow-token'],\n // any CI that is not Actions\n ['only BIT_GITHUB_TOKEN', { BIT_GITHUB_TOKEN: 'user-pat' }, 'Bearer user-pat'],\n ['an EMPTY override', { GITHUB_TOKEN: 'workflow-token', BIT_GITHUB_TOKEN: '' }, 'Bearer workflow-token'],\n ];\n\n PRECEDENCE.forEach(([name, env, expected]) => {\n it(`sends the right token with ${name}`, async () => {\n delete process.env.GITHUB_TOKEN;\n Object.entries(env).forEach(([key, value]) => {\n process.env[key] = value;\n });\n expect(await sentToken()).to.equal(expected);\n });\n });\n\n it('is not configured when neither token is set, and puts nothing on the wire', async () => {\n delete process.env.GITHUB_TOKEN;\n process.env.GITHUB_REPOSITORY = 'acme/shop';\n expect(GitHubClient.fromEnv('git@github.com:acme/shop.git')).to.equal(undefined);\n expect(calls).to.have.lengthOf(0);\n });\n });\n});\n\ndescribe('GitHubClient', () => {\n it('createPr posts the pull-request payload to the pulls endpoint', async () => {\n const calls: Array<{ url: string; init: any }> = [];\n const fakeFetch = (async (url: any, init: any) => {\n calls.push({ url: String(url), init });\n return new Response(\n JSON.stringify({ number: 7, state: 'open', labels: [], head: { ref: 'lane-x' }, html_url: 'http://pr/7' }),\n { status: 201, headers: { 'content-type': 'application/json' } }\n );\n }) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n const pr = await client.createPr({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n expect(pr.number).to.equal(7);\n expect(calls[0].url).to.equal('https://api.github.com/repos/acme/shop/pulls');\n expect(calls[0].init.method).to.equal('POST');\n expect(JSON.parse(calls[0].init.body)).to.deep.equal({ head: 'lane-x', base: 'main', title: 't', body: 'b' });\n });\n\n it('findPrByBranch returns undefined on empty list', async () => {\n const fakeFetch = (async () =>\n new Response(JSON.stringify([]), {\n status: 200,\n headers: { 'content-type': 'application/json' },\n })) as typeof fetch;\n const client = new GitHubClient({ token: 'tok', repo: 'acme/shop', fetchImpl: fakeFetch });\n expect(await client.findPrByBranch('lane-x')).to.equal(undefined);\n });\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bit ci sync --init` — pure scaffolding logic, unit-testable without a live workspace/git checkout.
|
|
3
|
+
*
|
|
4
|
+
* TEMPLATE SOURCE OF TRUTH: `BIT_SYNC_WORKFLOW_RAW` / `BIT_RELEASE_WORKFLOW_RAW` are byte-for-byte
|
|
5
|
+
* copies of the canonical templates in the `bit-git-sync` action workspace
|
|
6
|
+
* (git-sync/git-sync/workflows/templates/) and MUST be kept in sync with them. The ONLY departure is
|
|
7
|
+
* the two `DEFAULT_BRANCH_MARKER` substitution points; everything else — CHANGE-ME comments, the
|
|
8
|
+
* placeholder action ref, `bit-sync/main` config defaults — stays untouched.
|
|
9
|
+
*/
|
|
10
|
+
/** Where `scaffoldWorkflowFiles` writes each file, relative to the workspace root. */
|
|
11
|
+
export declare const WORKFLOW_RELATIVE_PATHS: {
|
|
12
|
+
readonly sync: string;
|
|
13
|
+
readonly release: string;
|
|
14
|
+
};
|
|
15
|
+
/** Render `bit-sync.yml` with the repository's actual default branch substituted. */
|
|
16
|
+
export declare function renderBitSyncWorkflow(defaultBranch: string): string;
|
|
17
|
+
/** Render `bit-release.yml` with the repository's actual default branch substituted. */
|
|
18
|
+
export declare function renderBitReleaseWorkflow(defaultBranch: string): string;
|
|
19
|
+
export interface ScaffoldFileOutcome {
|
|
20
|
+
/** relative to the workspace root, e.g. `.github/workflows/bit-sync.yml` */
|
|
21
|
+
relativePath: string;
|
|
22
|
+
status: 'written' | 'skipped';
|
|
23
|
+
}
|
|
24
|
+
/** Scaffold both workflow files under `workspaceDir`. Never overwrites; creates `.github/workflows/` as needed. */
|
|
25
|
+
export declare function scaffoldWorkflowFiles(workspaceDir: string, defaultBranch: string): ScaffoldFileOutcome[];
|
|
26
|
+
export interface OwnerRepo {
|
|
27
|
+
owner: string;
|
|
28
|
+
repo: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Derive `{owner, repo}` from the `origin` remote when it is a parseable GitHub remote, else
|
|
32
|
+
* undefined (the checklist falls back to a placeholder). Reuses `github-client.ts`'s parsing so the
|
|
33
|
+
* two never disagree.
|
|
34
|
+
*/
|
|
35
|
+
export declare function deriveOwnerRepo(remoteUrl: string | undefined): OwnerRepo | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* The manual-steps checklist `bit ci sync --init` prints: everything the command itself cannot do
|
|
38
|
+
* (secrets live in GitHub settings; the webhook lives on bit.cloud).
|
|
39
|
+
*/
|
|
40
|
+
export declare function renderInitChecklist(ownerRepo: OwnerRepo | undefined): string;
|