@gh-symphony/cli 0.0.20 → 0.0.21
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 +30 -2
- package/dist/chunk-A67CMOYE.js +684 -0
- package/dist/{chunk-TILHWBP6.js → chunk-C67H3OUL.js} +239 -36
- package/dist/{chunk-RN2PACNV.js → chunk-JN3TQVFV.js} +721 -74
- package/dist/{chunk-EKKT5USP.js → chunk-KY6WKH66.js} +437 -101
- package/dist/{chunk-HZVDTAPS.js → chunk-MYVJ6HK4.js} +943 -1182
- package/dist/{chunk-M3IFVLQS.js → chunk-QEONJ5DZ.js} +978 -72
- package/dist/{chunk-3AWF54PI.js → chunk-S6VIK4FF.js} +59 -31
- package/dist/chunk-SXGT7LOF.js +1060 -0
- package/dist/{doctor-IYHCFXOZ.js → doctor-4HBRICHP.js} +102 -37
- package/dist/index.js +38 -17
- package/dist/{init-KZT6YNOH.js → init-HZ3JEDGQ.js} +7 -2
- package/dist/{project-UUVHS3ZR.js → project-25NQ4J4Y.js} +8 -6
- package/dist/{recover-5KQI7WH5.js → recover-L3MJHHDA.js} +4 -2
- package/dist/{repo-HDDE7OUI.js → repo-TDCWQR6P.js} +72 -14
- package/dist/{run-ETC5UTRA.js → run-XJQ6BF7U.js} +4 -2
- package/dist/{setup-VWB7RZUQ.js → setup-B2SVLW2R.js} +46 -8
- package/dist/{start-ENFLZUI6.js → start-I2CC7BLW.js} +6 -4
- package/dist/{upgrade-3YNF3VKY.js → upgrade-OJXPZRYE.js} +2 -2
- package/dist/{version-NUBTTOG7.js → version-TBDCTKDO.js} +1 -1
- package/dist/worker-entry.js +489 -690
- package/dist/{workflow-TBIFY5MO.js → workflow-BLJH2HC3.js} +176 -10
- package/package.json +3 -1
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
// src/github/client.ts
|
|
4
4
|
var DEFAULT_API_URL = "https://api.github.com/graphql";
|
|
5
5
|
var REST_API_URL = "https://api.github.com";
|
|
6
|
+
function findLinkedRepository(project, owner, name) {
|
|
7
|
+
const normalizedOwner = owner.trim().toLowerCase();
|
|
8
|
+
const normalizedName = name.trim().toLowerCase();
|
|
9
|
+
return project.linkedRepositories.find(
|
|
10
|
+
(repository) => repository.owner.trim().toLowerCase() === normalizedOwner && repository.name.trim().toLowerCase() === normalizedName
|
|
11
|
+
) ?? null;
|
|
12
|
+
}
|
|
6
13
|
var GitHubApiError = class extends Error {
|
|
7
14
|
constructor(message, status) {
|
|
8
15
|
super(message);
|
|
@@ -18,6 +25,14 @@ var GitHubScopeError = class extends GitHubApiError {
|
|
|
18
25
|
this.name = "GitHubScopeError";
|
|
19
26
|
}
|
|
20
27
|
};
|
|
28
|
+
var GitHubRepositoryLookupError = class extends GitHubApiError {
|
|
29
|
+
constructor(reason, message, remediation, status) {
|
|
30
|
+
super(message, status);
|
|
31
|
+
this.reason = reason;
|
|
32
|
+
this.remediation = remediation;
|
|
33
|
+
this.name = "GitHubRepositoryLookupError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
21
36
|
function createClient(token, options) {
|
|
22
37
|
return {
|
|
23
38
|
token,
|
|
@@ -25,6 +40,77 @@ function createClient(token, options) {
|
|
|
25
40
|
fetchImpl: options?.fetchImpl ?? fetch
|
|
26
41
|
};
|
|
27
42
|
}
|
|
43
|
+
async function getRepositoryMetadata(client, owner, name) {
|
|
44
|
+
const restUrl = client.apiUrl.replace("/graphql", "");
|
|
45
|
+
const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
|
|
46
|
+
const repoPath = `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
|
|
47
|
+
let response;
|
|
48
|
+
try {
|
|
49
|
+
response = await client.fetchImpl(`${baseUrl}${repoPath}`, {
|
|
50
|
+
headers: {
|
|
51
|
+
authorization: `Bearer ${client.token}`,
|
|
52
|
+
accept: "application/vnd.github+json"
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
} catch (error) {
|
|
56
|
+
const detail = error instanceof Error && error.message.length > 0 ? ` ${error.message}` : "";
|
|
57
|
+
throw new GitHubRepositoryLookupError(
|
|
58
|
+
"offline",
|
|
59
|
+
`GitHub repository validation could not reach the API.${detail}`.trim(),
|
|
60
|
+
"Check your network connection and re-run the command to validate before saving."
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const payload = await response.json().catch(() => null);
|
|
65
|
+
const message = payload?.message?.trim() || response.statusText;
|
|
66
|
+
if (response.status === 403 && (response.headers.get("x-ratelimit-remaining") === "0" || /rate limit/i.test(message))) {
|
|
67
|
+
throw new GitHubRepositoryLookupError(
|
|
68
|
+
"rate_limited",
|
|
69
|
+
"GitHub API rate limit blocked repository validation.",
|
|
70
|
+
"Wait for the rate limit window to reset, then re-run 'gh-symphony repo add owner/name'.",
|
|
71
|
+
response.status
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
if (response.status === 401) {
|
|
75
|
+
throw new GitHubRepositoryLookupError(
|
|
76
|
+
"invalid_token",
|
|
77
|
+
"GitHub token is invalid or expired.",
|
|
78
|
+
"Run 'gh auth login --scopes repo,read:org,project' or refresh GITHUB_GRAPHQL_TOKEN, then retry.",
|
|
79
|
+
response.status
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (response.status === 403 || /resource not accessible|saml|single sign-on|access denied/i.test(message)) {
|
|
83
|
+
throw new GitHubRepositoryLookupError(
|
|
84
|
+
"no_access",
|
|
85
|
+
`GitHub denied access to ${owner}/${name}.`,
|
|
86
|
+
"Confirm that the authenticated user can read this repository and that the token has the required access.",
|
|
87
|
+
response.status
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (response.status === 404) {
|
|
91
|
+
throw new GitHubRepositoryLookupError(
|
|
92
|
+
"not_found",
|
|
93
|
+
`Repository ${owner}/${name} was not found.`,
|
|
94
|
+
"Check the owner/name spelling. If the repository is private, confirm the current token can access it.",
|
|
95
|
+
response.status
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
throw new GitHubRepositoryLookupError(
|
|
99
|
+
"unknown",
|
|
100
|
+
`GitHub repository validation failed: ${response.status} ${message}`.trim(),
|
|
101
|
+
"Retry the command. If the problem continues, verify GitHub API access separately.",
|
|
102
|
+
response.status
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
const repo = await response.json();
|
|
106
|
+
return {
|
|
107
|
+
owner: repo.owner.login,
|
|
108
|
+
name: repo.name,
|
|
109
|
+
url: repo.html_url,
|
|
110
|
+
cloneUrl: repo.clone_url,
|
|
111
|
+
visibility: repo.visibility ?? (repo.private === true ? "private" : "public")
|
|
112
|
+
};
|
|
113
|
+
}
|
|
28
114
|
async function validateToken(client) {
|
|
29
115
|
const restUrl = client.apiUrl.replace("/graphql", "");
|
|
30
116
|
const baseUrl = restUrl === client.apiUrl ? REST_API_URL : restUrl;
|
|
@@ -57,34 +143,123 @@ function checkRequiredScopes(scopes) {
|
|
|
57
143
|
const missing = required.filter((r) => !normalizedScopes.includes(r));
|
|
58
144
|
return { valid: missing.length === 0, missing };
|
|
59
145
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
146
|
+
var PROJECT_PAGE_SIZE = 50;
|
|
147
|
+
var ORGANIZATION_PAGE_SIZE = 20;
|
|
148
|
+
var MAX_PROJECT_DISCOVERY_REQUESTS = 40;
|
|
149
|
+
var MAX_DISCOVERED_PROJECTS = 1e3;
|
|
150
|
+
async function discoverUserProjects(client) {
|
|
65
151
|
const projects = [];
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
152
|
+
const seenProjectIds = /* @__PURE__ */ new Set();
|
|
153
|
+
const orgLogins = [];
|
|
154
|
+
let requestCount = 0;
|
|
155
|
+
let partial = false;
|
|
156
|
+
let reason = null;
|
|
157
|
+
const tryStartRequest = () => {
|
|
158
|
+
if (requestCount >= MAX_PROJECT_DISCOVERY_REQUESTS) {
|
|
159
|
+
partial = true;
|
|
160
|
+
reason ??= "request_limit";
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
requestCount += 1;
|
|
164
|
+
return true;
|
|
165
|
+
};
|
|
166
|
+
const collectProject = (node, owner) => {
|
|
167
|
+
if (seenProjectIds.has(node.id)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (projects.length >= MAX_DISCOVERED_PROJECTS) {
|
|
171
|
+
partial = true;
|
|
172
|
+
reason ??= "result_limit";
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
seenProjectIds.add(node.id);
|
|
176
|
+
projects.push(normalizeProjectSummary(node, owner));
|
|
177
|
+
return true;
|
|
178
|
+
};
|
|
179
|
+
let viewerProjectsCursor = null;
|
|
180
|
+
let hasMoreViewerProjects = true;
|
|
181
|
+
let viewerLogin = "";
|
|
182
|
+
while (hasMoreViewerProjects) {
|
|
183
|
+
if (!tryStartRequest()) {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
const data = await graphql(
|
|
187
|
+
client,
|
|
188
|
+
VIEWER_PROJECTS_PAGE_QUERY,
|
|
189
|
+
{ cursor: viewerProjectsCursor }
|
|
73
190
|
);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
191
|
+
viewerLogin = data.viewer.login;
|
|
192
|
+
const projectPage = data.viewer.projectsV2;
|
|
193
|
+
for (const node of projectPage?.nodes ?? []) {
|
|
194
|
+
if (!node) continue;
|
|
195
|
+
if (!collectProject(node, { login: viewerLogin, type: "User" })) {
|
|
196
|
+
hasMoreViewerProjects = false;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (partial) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
hasMoreViewerProjects = projectPage?.pageInfo?.hasNextPage ?? false;
|
|
204
|
+
viewerProjectsCursor = projectPage?.pageInfo?.endCursor ?? null;
|
|
205
|
+
}
|
|
206
|
+
let organizationsCursor = null;
|
|
207
|
+
let hasMoreOrganizations = true;
|
|
208
|
+
while (!partial && hasMoreOrganizations) {
|
|
209
|
+
if (!tryStartRequest()) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
const data = await graphql(
|
|
213
|
+
client,
|
|
214
|
+
VIEWER_ORGANIZATIONS_PAGE_QUERY,
|
|
215
|
+
{ cursor: organizationsCursor }
|
|
216
|
+
);
|
|
217
|
+
for (const orgNode of data.viewer.organizations?.nodes ?? []) {
|
|
218
|
+
if (!orgNode) continue;
|
|
219
|
+
orgLogins.push(orgNode.login);
|
|
220
|
+
}
|
|
221
|
+
hasMoreOrganizations = data.viewer.organizations?.pageInfo?.hasNextPage ?? false;
|
|
222
|
+
organizationsCursor = data.viewer.organizations?.pageInfo?.endCursor ?? null;
|
|
223
|
+
}
|
|
224
|
+
for (const orgLogin of orgLogins) {
|
|
225
|
+
let orgProjectsCursor = null;
|
|
226
|
+
let hasMoreOrgProjects = true;
|
|
227
|
+
while (!partial && hasMoreOrgProjects) {
|
|
228
|
+
if (!tryStartRequest()) {
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
const data = await graphql(
|
|
232
|
+
client,
|
|
233
|
+
ORGANIZATION_PROJECTS_PAGE_QUERY,
|
|
234
|
+
{ login: orgLogin, cursor: orgProjectsCursor }
|
|
84
235
|
);
|
|
236
|
+
const projectPage = data.organization?.projectsV2 ?? null;
|
|
237
|
+
for (const node of projectPage?.nodes ?? []) {
|
|
238
|
+
if (!node) continue;
|
|
239
|
+
if (!collectProject(node, {
|
|
240
|
+
login: orgLogin,
|
|
241
|
+
type: "Organization"
|
|
242
|
+
})) {
|
|
243
|
+
hasMoreOrgProjects = false;
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (partial) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
hasMoreOrgProjects = projectPage?.pageInfo?.hasNextPage ?? false;
|
|
251
|
+
orgProjectsCursor = projectPage?.pageInfo?.endCursor ?? null;
|
|
85
252
|
}
|
|
86
253
|
}
|
|
87
|
-
return
|
|
254
|
+
return {
|
|
255
|
+
projects,
|
|
256
|
+
partial,
|
|
257
|
+
reason,
|
|
258
|
+
requests: requestCount
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function listUserProjects(client) {
|
|
262
|
+
return (await discoverUserProjects(client)).projects;
|
|
88
263
|
}
|
|
89
264
|
function normalizeProjectSummary(node, owner) {
|
|
90
265
|
return {
|
|
@@ -230,11 +405,11 @@ async function graphql(client, query, variables) {
|
|
|
230
405
|
}
|
|
231
406
|
return payload.data;
|
|
232
407
|
}
|
|
233
|
-
var
|
|
234
|
-
query
|
|
408
|
+
var VIEWER_PROJECTS_PAGE_QUERY = `
|
|
409
|
+
query ViewerProjectsPage($cursor: String) {
|
|
235
410
|
viewer {
|
|
236
411
|
login
|
|
237
|
-
projectsV2(first:
|
|
412
|
+
projectsV2(first: ${PROJECT_PAGE_SIZE}, after: $cursor) {
|
|
238
413
|
nodes {
|
|
239
414
|
id
|
|
240
415
|
title
|
|
@@ -242,19 +417,43 @@ var VIEWER_PROJECTS_QUERY = `
|
|
|
242
417
|
url
|
|
243
418
|
items { totalCount }
|
|
244
419
|
}
|
|
420
|
+
pageInfo {
|
|
421
|
+
endCursor
|
|
422
|
+
hasNextPage
|
|
423
|
+
}
|
|
245
424
|
}
|
|
246
|
-
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
`;
|
|
428
|
+
var VIEWER_ORGANIZATIONS_PAGE_QUERY = `
|
|
429
|
+
query ViewerOrganizationsPage($cursor: String) {
|
|
430
|
+
viewer {
|
|
431
|
+
organizations(first: ${ORGANIZATION_PAGE_SIZE}, after: $cursor) {
|
|
247
432
|
nodes {
|
|
248
433
|
login
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
434
|
+
}
|
|
435
|
+
pageInfo {
|
|
436
|
+
endCursor
|
|
437
|
+
hasNextPage
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
`;
|
|
443
|
+
var ORGANIZATION_PROJECTS_PAGE_QUERY = `
|
|
444
|
+
query OrganizationProjectsPage($login: String!, $cursor: String) {
|
|
445
|
+
organization(login: $login) {
|
|
446
|
+
projectsV2(first: ${PROJECT_PAGE_SIZE}, after: $cursor) {
|
|
447
|
+
nodes {
|
|
448
|
+
id
|
|
449
|
+
title
|
|
450
|
+
shortDescription
|
|
451
|
+
url
|
|
452
|
+
items { totalCount }
|
|
453
|
+
}
|
|
454
|
+
pageInfo {
|
|
455
|
+
endCursor
|
|
456
|
+
hasNextPage
|
|
258
457
|
}
|
|
259
458
|
}
|
|
260
459
|
}
|
|
@@ -615,11 +814,15 @@ function parseScopes(output) {
|
|
|
615
814
|
}
|
|
616
815
|
|
|
617
816
|
export {
|
|
817
|
+
findLinkedRepository,
|
|
618
818
|
GitHubApiError,
|
|
619
819
|
GitHubScopeError,
|
|
820
|
+
GitHubRepositoryLookupError,
|
|
620
821
|
createClient,
|
|
822
|
+
getRepositoryMetadata,
|
|
621
823
|
validateToken,
|
|
622
824
|
checkRequiredScopes,
|
|
825
|
+
discoverUserProjects,
|
|
623
826
|
listUserProjects,
|
|
624
827
|
getProjectDetail,
|
|
625
828
|
REQUIRED_GH_SCOPES,
|