@theholocron/holocron-plugin-github 2.0.0-alpha.5 → 2.0.0-alpha.50
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 +11 -7
- package/dist/index.d.mts +41 -86
- package/dist/index.mjs +181 -289
- package/package.json +16 -11
package/README.md
CHANGED
|
@@ -13,8 +13,10 @@ against the GitHub REST API:
|
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
16
|
+
<!-- prettier-ignore -->
|
|
16
17
|
```bash
|
|
17
18
|
pnpm add -D @theholocron/holocron-plugin-github@alpha
|
|
19
|
+
|
|
18
20
|
```
|
|
19
21
|
|
|
20
22
|
## Auth
|
|
@@ -33,17 +35,19 @@ toggles, etc.) — silent fallback would surface as mysterious 403s.
|
|
|
33
35
|
|
|
34
36
|
## Config
|
|
35
37
|
|
|
38
|
+
<!-- prettier-ignore -->
|
|
36
39
|
```jsonc
|
|
37
40
|
// holocron.config.json
|
|
38
41
|
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
"providers": {
|
|
43
|
+
"source": "github",
|
|
44
|
+
"ci": "github",
|
|
45
|
+
"secrets": "github",
|
|
46
|
+
"environments": "github",
|
|
47
|
+
"issues": ["github", { "labels": { "inProgress": "status:in-progress", "inReview": "status:in-review" } }],
|
|
48
|
+
},
|
|
46
49
|
}
|
|
50
|
+
|
|
47
51
|
```
|
|
48
52
|
|
|
49
53
|
## Status
|
package/dist/index.d.mts
CHANGED
|
@@ -1,62 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GitHubClient, createGitHubClient } from "@theholocron/github-client";
|
|
2
|
+
import { Auth, AuthError, Ci, CiRun, CiRunFilter, Environment, Environments, Issue, IssueSearchFilter, Issues, LabelDef, LifecycleResult, LifecycleSlot, RepoRef, RepoSettings, ResolveTokenInput, Ruleset, SecretScope, Secrets, Source, TrackerDoctorReport, TrackerUser } from "@theholocron/cli";
|
|
2
3
|
|
|
3
4
|
//#region src/auth.d.ts
|
|
4
|
-
|
|
5
|
-
* Token resolution for the GitHub plugin. See README §Auth.
|
|
6
|
-
*
|
|
7
|
-
* Resolution order (matches the standard 4-step precedence set by
|
|
8
|
-
* `.notes/tech-auth-bootstrap.spec.md`):
|
|
9
|
-
* 1. explicit `token` argument (from `--token` flag)
|
|
10
|
-
* 2. HOLOCRON_GH_TOKEN env var (preferred over GITHUB_TOKEN — clearer intent)
|
|
11
|
-
* 3. GITHUB_TOKEN env var (auto-injected in GH Actions)
|
|
12
|
-
* 4. keyring (com.theholocron.cli / "github")
|
|
13
|
-
* 5. AuthError naming all four options + the bootstrap hint
|
|
14
|
-
*
|
|
15
|
-
* No `gh auth token` fallback by design — it usually has narrower
|
|
16
|
-
* scopes than admin commands need.
|
|
17
|
-
*/
|
|
18
|
-
declare class AuthError extends Error {
|
|
19
|
-
name: string;
|
|
20
|
-
}
|
|
21
|
-
interface ResolveTokenInput {
|
|
22
|
-
/** From `--token` CLI flag. */
|
|
23
|
-
cliToken?: string;
|
|
24
|
-
/** Env vars; passed in for testability. Defaults to `process.env`. */
|
|
25
|
-
env?: NodeJS.ProcessEnv;
|
|
26
|
-
/** Keyring lookup fn; passed in for testability. Defaults to `getToken(provider)`. */
|
|
27
|
-
keyring?: (provider: string) => string | null;
|
|
28
|
-
}
|
|
29
|
-
declare function resolveToken(input?: ResolveTokenInput): string;
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/rest.d.ts
|
|
32
|
-
/**
|
|
33
|
-
* Thin REST wrapper around api.github.com.
|
|
34
|
-
*
|
|
35
|
-
* Adapted from rando-id/rando.id `packages/cli/src/adapters/gh-rest.ts`
|
|
36
|
-
* `request<T>` method into a standalone class so each capability can
|
|
37
|
-
* hold a reference and share the same auth/error handling.
|
|
38
|
-
*
|
|
39
|
-
* Errors throw `ProviderApiError` from `@theholocron/cli`, which the
|
|
40
|
-
* orchestrator catches to soft-skip rather than abort the whole pipeline.
|
|
41
|
-
*/
|
|
42
|
-
interface RestClientOptions {
|
|
43
|
-
token: string;
|
|
44
|
-
fetch?: typeof fetch;
|
|
45
|
-
baseUrl?: string;
|
|
46
|
-
}
|
|
47
|
-
interface RequestOptions {
|
|
48
|
-
method?: "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
49
|
-
body?: unknown;
|
|
50
|
-
/** Skip JSON parse when true (204 No Content endpoints). */
|
|
51
|
-
expectNoContent?: boolean;
|
|
52
|
-
}
|
|
53
|
-
declare class GitHubRestClient {
|
|
54
|
-
private readonly token;
|
|
55
|
-
private readonly fetchImpl;
|
|
56
|
-
readonly baseUrl: string;
|
|
57
|
-
constructor(opts: RestClientOptions);
|
|
58
|
-
request<T>(path: string, opts?: RequestOptions): Promise<T>;
|
|
59
|
-
}
|
|
5
|
+
declare const resolveToken: (input?: import("@theholocron/http-client").ResolveTokenInput) => string;
|
|
60
6
|
//#endregion
|
|
61
7
|
//#region src/sodium.d.ts
|
|
62
8
|
/**
|
|
@@ -68,19 +14,18 @@ declare function encryptSecret(publicKeyBase64: string, value: string): Promise<
|
|
|
68
14
|
//#region src/capabilities/source.d.ts
|
|
69
15
|
interface SourceOptions {
|
|
70
16
|
repo: string;
|
|
71
|
-
/** Absolute path to the repo root. Workflow file ops are scoped here. */
|
|
72
17
|
repoRoot: string;
|
|
73
18
|
}
|
|
74
19
|
declare class GitHubSource implements Source {
|
|
75
|
-
private readonly
|
|
20
|
+
private readonly client;
|
|
76
21
|
readonly key: "source";
|
|
77
22
|
readonly providerName = "github";
|
|
78
23
|
private readonly owner;
|
|
79
24
|
private readonly name;
|
|
80
|
-
private readonly
|
|
25
|
+
private readonly repo;
|
|
81
26
|
private readonly repoRoot;
|
|
82
27
|
private readonly workflowDir;
|
|
83
|
-
constructor(
|
|
28
|
+
constructor(client: GitHubClient, opts: SourceOptions);
|
|
84
29
|
whoami(): Promise<{
|
|
85
30
|
login: string;
|
|
86
31
|
}>;
|
|
@@ -94,6 +39,7 @@ declare class GitHubSource implements Source {
|
|
|
94
39
|
enableAutomatedSecurityFixes(): Promise<void>;
|
|
95
40
|
enableSecretScanning(): Promise<void>;
|
|
96
41
|
enableCodeScanning(): Promise<string>;
|
|
42
|
+
disableDefaultCodeScanning(): Promise<void>;
|
|
97
43
|
enableDependencyGraph(): Promise<void>;
|
|
98
44
|
enablePrivateVulnerabilityReporting(): Promise<void>;
|
|
99
45
|
listWorkflowFiles(): Promise<string[]>;
|
|
@@ -101,6 +47,9 @@ declare class GitHubSource implements Source {
|
|
|
101
47
|
writeWorkflowFile(name: string, contents: string): Promise<void>;
|
|
102
48
|
removeWorkflowFile(name: string): Promise<void>;
|
|
103
49
|
writeRepoFile(path: string, contents: string): Promise<void>;
|
|
50
|
+
syncLabels(canonical: ReadonlyArray<LabelDef>, stale: ReadonlyArray<string>): Promise<string>;
|
|
51
|
+
syncProperties(values: Record<string, string>): Promise<string>;
|
|
52
|
+
syncTopics(topics: string[]): Promise<string>;
|
|
104
53
|
}
|
|
105
54
|
//#endregion
|
|
106
55
|
//#region src/capabilities/secrets.d.ts
|
|
@@ -108,16 +57,14 @@ interface SecretsOptions {
|
|
|
108
57
|
repo: string;
|
|
109
58
|
}
|
|
110
59
|
declare class GitHubSecrets implements Secrets {
|
|
111
|
-
private readonly
|
|
60
|
+
private readonly client;
|
|
61
|
+
private readonly opts;
|
|
112
62
|
readonly key: "secrets";
|
|
113
63
|
readonly providerName = "github";
|
|
114
|
-
|
|
115
|
-
constructor(rest: GitHubRestClient, opts: SecretsOptions);
|
|
64
|
+
constructor(client: GitHubClient, opts: SecretsOptions);
|
|
116
65
|
listSecrets(scope: SecretScope): Promise<string[]>;
|
|
117
66
|
setSecret(scope: SecretScope, name: string, value: string): Promise<void>;
|
|
118
67
|
deleteSecret(scope: SecretScope, name: string): Promise<void>;
|
|
119
|
-
/** Endpoint root for the given scope. */
|
|
120
|
-
private scopeBase;
|
|
121
68
|
}
|
|
122
69
|
//#endregion
|
|
123
70
|
//#region src/capabilities/environments.d.ts
|
|
@@ -125,11 +72,11 @@ interface EnvironmentsOptions {
|
|
|
125
72
|
repo: string;
|
|
126
73
|
}
|
|
127
74
|
declare class GitHubEnvironments implements Environments {
|
|
128
|
-
private readonly
|
|
75
|
+
private readonly client;
|
|
76
|
+
private readonly opts;
|
|
129
77
|
readonly key: "environments";
|
|
130
78
|
readonly providerName = "github";
|
|
131
|
-
|
|
132
|
-
constructor(rest: GitHubRestClient, opts: EnvironmentsOptions);
|
|
79
|
+
constructor(client: GitHubClient, opts: EnvironmentsOptions);
|
|
133
80
|
listEnvironments(): Promise<Environment[]>;
|
|
134
81
|
upsertEnvironment(env: Environment): Promise<void>;
|
|
135
82
|
deleteEnvironment(name: string): Promise<void>;
|
|
@@ -141,19 +88,13 @@ interface CiOptions {
|
|
|
141
88
|
repo: string;
|
|
142
89
|
}
|
|
143
90
|
declare class GitHubCi implements Ci {
|
|
144
|
-
private readonly
|
|
91
|
+
private readonly client;
|
|
92
|
+
private readonly opts;
|
|
145
93
|
readonly key: "ci";
|
|
146
94
|
readonly providerName = "github";
|
|
147
|
-
|
|
148
|
-
constructor(rest: GitHubRestClient, opts: CiOptions);
|
|
95
|
+
constructor(client: GitHubClient, opts: CiOptions);
|
|
149
96
|
listRuns(filter?: CiRunFilter): Promise<CiRun[]>;
|
|
150
97
|
getRun(id: string | number): Promise<CiRun>;
|
|
151
|
-
/**
|
|
152
|
-
* GitHub reports run state as `status` plus `conclusion` (the latter
|
|
153
|
-
* is set once `status === 'completed'`). Holocron's `CiRunStatus`
|
|
154
|
-
* collapses these onto one axis — when complete, the conclusion is
|
|
155
|
-
* the meaningful value.
|
|
156
|
-
*/
|
|
157
98
|
private mapRun;
|
|
158
99
|
private mapConclusion;
|
|
159
100
|
}
|
|
@@ -165,9 +106,7 @@ interface IssuesOptions {
|
|
|
165
106
|
* Lifecycle slot → status label name. Optional at construction; if
|
|
166
107
|
* omitted, capability methods that need labels (`transition`,
|
|
167
108
|
* `doctor`) degrade gracefully — `transition` throws a clear error,
|
|
168
|
-
* `doctor` reports the two label-backed slots as unresolved.
|
|
169
|
-
* lets the plugin load in configs where the operator hasn't picked
|
|
170
|
-
* labels yet without failing the whole runtime.
|
|
109
|
+
* `doctor` reports the two label-backed slots as unresolved.
|
|
171
110
|
*/
|
|
172
111
|
labels?: {
|
|
173
112
|
inProgress: string;
|
|
@@ -175,14 +114,14 @@ interface IssuesOptions {
|
|
|
175
114
|
};
|
|
176
115
|
}
|
|
177
116
|
declare class GitHubIssues implements Issues {
|
|
178
|
-
private readonly
|
|
117
|
+
private readonly client;
|
|
179
118
|
readonly key: "issues";
|
|
180
119
|
readonly providerName = "github";
|
|
181
120
|
private readonly owner;
|
|
182
121
|
private readonly repoName;
|
|
183
|
-
private readonly
|
|
122
|
+
private readonly repo;
|
|
184
123
|
private readonly labels;
|
|
185
|
-
constructor(
|
|
124
|
+
constructor(client: GitHubClient, opts: IssuesOptions);
|
|
186
125
|
getMyself(): Promise<TrackerUser>;
|
|
187
126
|
search(filter: IssueSearchFilter): Promise<Issue[]>;
|
|
188
127
|
get(key: string): Promise<Issue>;
|
|
@@ -202,6 +141,22 @@ declare class GitHubIssues implements Issues {
|
|
|
202
141
|
private mapIssue;
|
|
203
142
|
}
|
|
204
143
|
//#endregion
|
|
144
|
+
//#region src/capabilities/labels.d.ts
|
|
145
|
+
interface CanonicalLabel {
|
|
146
|
+
readonly name: string;
|
|
147
|
+
readonly color: string;
|
|
148
|
+
readonly description: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Idempotently synchronise a repo's labels to the canonical set.
|
|
152
|
+
*
|
|
153
|
+
* - Creates labels that are missing.
|
|
154
|
+
* - PATCHes labels whose color or description has drifted.
|
|
155
|
+
* - DELETEs labels listed in `stale`.
|
|
156
|
+
* - Leaves unrecognised labels that aren't in `stale` untouched.
|
|
157
|
+
*/
|
|
158
|
+
declare function syncLabels(client: GitHubClient, repo: string, canonical: ReadonlyArray<CanonicalLabel>, stale: ReadonlyArray<string>): Promise<string>;
|
|
159
|
+
//#endregion
|
|
205
160
|
//#region src/verify-token.d.ts
|
|
206
161
|
/**
|
|
207
162
|
* `verifyToken` — plugin-level export used by `holocron auth set` +
|
|
@@ -247,7 +202,7 @@ interface GitHubPluginOptions extends ResolveTokenInput {
|
|
|
247
202
|
}
|
|
248
203
|
interface PluginContext {
|
|
249
204
|
options: GitHubPluginOptions;
|
|
250
|
-
|
|
205
|
+
client: GitHubClient;
|
|
251
206
|
repo: string;
|
|
252
207
|
repoRoot: string;
|
|
253
208
|
}
|
|
@@ -275,4 +230,4 @@ declare function createPlugin(options: GitHubPluginOptions): {
|
|
|
275
230
|
*/
|
|
276
231
|
declare const AUTH_HINT: string;
|
|
277
232
|
//#endregion
|
|
278
|
-
export { AUTH_HINT, type Auth, AuthError, GitHubCi, GitHubEnvironments, GitHubIssues, GitHubPluginOptions,
|
|
233
|
+
export { AUTH_HINT, type Auth, AuthError, type CanonicalLabel, GitHubCi, GitHubEnvironments, GitHubIssues, GitHubPluginOptions, GitHubSecrets, GitHubSource, PluginContext, type ResolveTokenInput, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, ci, createContext, createGitHubClient, createPlugin, encryptSecret, environments, issues, resolveToken, secrets, source, syncLabels, verifyToken };
|
package/dist/index.mjs
CHANGED
|
@@ -1,77 +1,37 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
2
|
+
import { createGitHubClient, createGitHubClient as createGitHubClient$1 } from "@theholocron/github-client";
|
|
3
|
+
import { AuthError, createResolveToken } from "@theholocron/cli";
|
|
3
4
|
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
//#region src/auth.ts
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* 2. HOLOCRON_GH_TOKEN env var (preferred over GITHUB_TOKEN — clearer intent)
|
|
13
|
-
* 3. GITHUB_TOKEN env var (auto-injected in GH Actions)
|
|
14
|
-
* 4. keyring (com.theholocron.cli / "github")
|
|
15
|
-
* 5. AuthError naming all four options + the bootstrap hint
|
|
16
|
-
*
|
|
17
|
-
* No `gh auth token` fallback by design — it usually has narrower
|
|
18
|
-
* scopes than admin commands need.
|
|
19
|
-
*/
|
|
20
|
-
var AuthError = class extends Error {
|
|
21
|
-
name = "AuthError";
|
|
22
|
-
};
|
|
23
|
-
function resolveToken(input = {}) {
|
|
24
|
-
const env = input.env ?? process.env;
|
|
25
|
-
const keyring = input.keyring ?? getToken;
|
|
26
|
-
const token = input.cliToken || env.HOLOCRON_GH_TOKEN || env.GITHUB_TOKEN || keyring("github");
|
|
27
|
-
if (!token) throw new AuthError("no GitHub token found. Pass --token <PAT>, set HOLOCRON_GH_TOKEN / GITHUB_TOKEN, or run: holocron auth set github <PAT>");
|
|
28
|
-
return token;
|
|
29
|
-
}
|
|
30
|
-
//#endregion
|
|
31
|
-
//#region src/repo.ts
|
|
32
|
-
var RepoError = class extends Error {
|
|
33
|
-
name = "RepoError";
|
|
34
|
-
};
|
|
35
|
-
function parseRepo(repo) {
|
|
36
|
-
if (!repo) throw new RepoError("repo is required; pass { repo: \"owner/name\" } in plugin options");
|
|
37
|
-
const [owner, name] = repo.split("/");
|
|
38
|
-
if (!owner || !name) throw new RepoError(`invalid repo "${repo}" — expected "owner/name"`);
|
|
39
|
-
return {
|
|
40
|
-
owner,
|
|
41
|
-
name
|
|
42
|
-
};
|
|
43
|
-
}
|
|
7
|
+
const resolveToken = createResolveToken({
|
|
8
|
+
envName: "HOLOCRON_GH_TOKEN",
|
|
9
|
+
vendorEnvName: "GITHUB_TOKEN",
|
|
10
|
+
keyringService: "github",
|
|
11
|
+
errorMessage: "no GitHub token found. Pass --token <PAT>, set HOLOCRON_GH_TOKEN / GITHUB_TOKEN, or run: holocron auth set github <PAT>"
|
|
12
|
+
});
|
|
44
13
|
//#endregion
|
|
45
14
|
//#region src/capabilities/ci.ts
|
|
46
15
|
var GitHubCi = class {
|
|
47
|
-
|
|
16
|
+
client;
|
|
17
|
+
opts;
|
|
48
18
|
key = "ci";
|
|
49
19
|
providerName = "github";
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.
|
|
53
|
-
const { owner, name } = parseRepo(opts.repo);
|
|
54
|
-
this.base = `/repos/${owner}/${name}/actions/runs`;
|
|
20
|
+
constructor(client, opts) {
|
|
21
|
+
this.client = client;
|
|
22
|
+
this.opts = opts;
|
|
55
23
|
}
|
|
56
24
|
async listRuns(filter) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const path = qs ? `${this.base}?${qs}` : this.base;
|
|
63
|
-
return (await this.rest.request(path)).workflow_runs.map((r) => this.mapRun(r));
|
|
25
|
+
return (await this.client.workflows.listRuns(this.opts.repo, {
|
|
26
|
+
branch: filter?.branch,
|
|
27
|
+
limit: filter?.limit,
|
|
28
|
+
status: filter?.status
|
|
29
|
+
})).map((r) => this.mapRun(r));
|
|
64
30
|
}
|
|
65
31
|
async getRun(id) {
|
|
66
|
-
const raw = await this.
|
|
32
|
+
const raw = await this.client.workflows.getRun(this.opts.repo, id);
|
|
67
33
|
return this.mapRun(raw);
|
|
68
34
|
}
|
|
69
|
-
/**
|
|
70
|
-
* GitHub reports run state as `status` plus `conclusion` (the latter
|
|
71
|
-
* is set once `status === 'completed'`). Holocron's `CiRunStatus`
|
|
72
|
-
* collapses these onto one axis — when complete, the conclusion is
|
|
73
|
-
* the meaningful value.
|
|
74
|
-
*/
|
|
75
35
|
mapRun(raw) {
|
|
76
36
|
const status = raw.status === "completed" && raw.conclusion ? this.mapConclusion(raw.conclusion) : raw.status;
|
|
77
37
|
return {
|
|
@@ -98,17 +58,16 @@ var GitHubCi = class {
|
|
|
98
58
|
//#endregion
|
|
99
59
|
//#region src/capabilities/environments.ts
|
|
100
60
|
var GitHubEnvironments = class {
|
|
101
|
-
|
|
61
|
+
client;
|
|
62
|
+
opts;
|
|
102
63
|
key = "environments";
|
|
103
64
|
providerName = "github";
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
this.
|
|
107
|
-
const { owner, name } = parseRepo(opts.repo);
|
|
108
|
-
this.base = `/repos/${owner}/${name}/environments`;
|
|
65
|
+
constructor(client, opts) {
|
|
66
|
+
this.client = client;
|
|
67
|
+
this.opts = opts;
|
|
109
68
|
}
|
|
110
69
|
async listEnvironments() {
|
|
111
|
-
return (await this.
|
|
70
|
+
return (await this.client.environments.listEnvironments(this.opts.repo)).map((e) => ({
|
|
112
71
|
name: e.name,
|
|
113
72
|
waitTimer: e.wait_timer,
|
|
114
73
|
preventSelfReview: e.prevent_self_review,
|
|
@@ -123,16 +82,10 @@ var GitHubEnvironments = class {
|
|
|
123
82
|
type: r.type,
|
|
124
83
|
id: r.id
|
|
125
84
|
}));
|
|
126
|
-
await this.
|
|
127
|
-
method: "PUT",
|
|
128
|
-
body
|
|
129
|
-
});
|
|
85
|
+
await this.client.environments.upsertEnvironment(this.opts.repo, env.name, body);
|
|
130
86
|
}
|
|
131
87
|
async deleteEnvironment(name) {
|
|
132
|
-
await this.
|
|
133
|
-
method: "DELETE",
|
|
134
|
-
expectNoContent: true
|
|
135
|
-
});
|
|
88
|
+
await this.client.environments.deleteEnvironment(this.opts.repo, name);
|
|
136
89
|
}
|
|
137
90
|
flattenReviewers(rules) {
|
|
138
91
|
if (!rules) return void 0;
|
|
@@ -147,41 +100,51 @@ var GitHubEnvironments = class {
|
|
|
147
100
|
//#endregion
|
|
148
101
|
//#region src/capabilities/issues.ts
|
|
149
102
|
var GitHubIssues = class {
|
|
150
|
-
|
|
103
|
+
client;
|
|
151
104
|
key = "issues";
|
|
152
105
|
providerName = "github";
|
|
153
106
|
owner;
|
|
154
107
|
repoName;
|
|
155
|
-
|
|
108
|
+
repo;
|
|
156
109
|
labels;
|
|
157
|
-
constructor(
|
|
158
|
-
this.
|
|
159
|
-
const
|
|
110
|
+
constructor(client, opts) {
|
|
111
|
+
this.client = client;
|
|
112
|
+
const [owner = "", repoName = ""] = opts.repo.split("/", 2);
|
|
160
113
|
this.owner = owner;
|
|
161
|
-
this.repoName =
|
|
162
|
-
this.
|
|
114
|
+
this.repoName = repoName;
|
|
115
|
+
this.repo = opts.repo;
|
|
163
116
|
this.labels = opts.labels;
|
|
164
117
|
}
|
|
165
118
|
async getMyself() {
|
|
166
|
-
|
|
119
|
+
const raw = await this.client.user.getCurrentUser();
|
|
120
|
+
return {
|
|
121
|
+
id: raw.login,
|
|
122
|
+
displayName: raw.name ?? raw.login,
|
|
123
|
+
...raw.email ? { emailAddress: raw.email } : {}
|
|
124
|
+
};
|
|
167
125
|
}
|
|
168
126
|
async search(filter) {
|
|
169
|
-
const params =
|
|
127
|
+
const params = {
|
|
170
128
|
state: filter.openOnly ? "open" : "all",
|
|
171
129
|
sort: "updated",
|
|
172
130
|
direction: "desc",
|
|
173
|
-
per_page:
|
|
131
|
+
per_page: filter.limit ?? 50,
|
|
174
132
|
filter: "all"
|
|
175
|
-
}
|
|
176
|
-
if (filter.assignee === "currentUser")
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
133
|
+
};
|
|
134
|
+
if (filter.assignee === "currentUser") params.assignee = (await this.getMyself()).id;
|
|
135
|
+
else if (filter.assignee) params.assignee = filter.assignee;
|
|
136
|
+
return (await this.client.issues.listIssues(this.repo, {
|
|
137
|
+
state: params.state,
|
|
138
|
+
sort: params.sort,
|
|
139
|
+
direction: params.direction,
|
|
140
|
+
per_page: Number(params.per_page),
|
|
141
|
+
filter: params.filter,
|
|
142
|
+
assignee: params.assignee
|
|
143
|
+
})).filter((i) => !i.pull_request).map((i) => this.mapIssue(i));
|
|
181
144
|
}
|
|
182
145
|
async get(key) {
|
|
183
146
|
const number = parseIssueNumber(key, this.owner, this.repoName);
|
|
184
|
-
const raw = await this.
|
|
147
|
+
const raw = await this.client.issues.getIssue(this.repo, number);
|
|
185
148
|
return this.mapIssue(raw);
|
|
186
149
|
}
|
|
187
150
|
async create(input) {
|
|
@@ -189,14 +152,11 @@ var GitHubIssues = class {
|
|
|
189
152
|
if (input.body) body.body = input.body;
|
|
190
153
|
if (input.labels?.length) body.labels = input.labels;
|
|
191
154
|
if (input.milestone) body.milestone = await this.resolveMilestone(input.milestone);
|
|
192
|
-
return { key: `#${(await this.
|
|
193
|
-
method: "POST",
|
|
194
|
-
body
|
|
195
|
-
})).number}` };
|
|
155
|
+
return { key: `#${(await this.client.issues.createIssue(this.repo, body)).number}` };
|
|
196
156
|
}
|
|
197
157
|
async transition(key, slot) {
|
|
198
158
|
const number = parseIssueNumber(key, this.owner, this.repoName);
|
|
199
|
-
const issue = await this.
|
|
159
|
+
const issue = await this.client.issues.getIssue(this.repo, number);
|
|
200
160
|
const currentStatusLabels = issue.labels.map((l) => l.name).filter((n) => n.startsWith("status:"));
|
|
201
161
|
if (slot === "done") {
|
|
202
162
|
if (issue.state === "closed") return {
|
|
@@ -205,12 +165,9 @@ var GitHubIssues = class {
|
|
|
205
165
|
via: "already closed"
|
|
206
166
|
};
|
|
207
167
|
await this.removeStatusLabels(number, currentStatusLabels);
|
|
208
|
-
await this.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
state: "closed",
|
|
212
|
-
state_reason: "completed"
|
|
213
|
-
}
|
|
168
|
+
await this.client.issues.updateIssue(this.repo, number, {
|
|
169
|
+
state: "closed",
|
|
170
|
+
state_reason: "completed"
|
|
214
171
|
});
|
|
215
172
|
return {
|
|
216
173
|
transitioned: true,
|
|
@@ -227,16 +184,10 @@ var GitHubIssues = class {
|
|
|
227
184
|
status: `open + ${targetLabel}`,
|
|
228
185
|
via: "already there"
|
|
229
186
|
};
|
|
230
|
-
if (!alreadyOpen) await this.
|
|
231
|
-
method: "PATCH",
|
|
232
|
-
body: { state: "open" }
|
|
233
|
-
});
|
|
187
|
+
if (!alreadyOpen) await this.client.issues.updateIssue(this.repo, number, { state: "open" });
|
|
234
188
|
const toRemove = currentStatusLabels.filter((n) => n !== targetLabel);
|
|
235
189
|
if (toRemove.length) await this.removeStatusLabels(number, toRemove);
|
|
236
|
-
if (!alreadyLabeled) await this.
|
|
237
|
-
method: "POST",
|
|
238
|
-
body: { labels: [targetLabel] }
|
|
239
|
-
});
|
|
190
|
+
if (!alreadyLabeled) await this.client.issues.addLabels(this.repo, number, [targetLabel]);
|
|
240
191
|
return {
|
|
241
192
|
transitioned: true,
|
|
242
193
|
status: `open + ${targetLabel}`,
|
|
@@ -245,15 +196,12 @@ var GitHubIssues = class {
|
|
|
245
196
|
}
|
|
246
197
|
async comment(key, body) {
|
|
247
198
|
const number = parseIssueNumber(key, this.owner, this.repoName);
|
|
248
|
-
await this.
|
|
249
|
-
method: "POST",
|
|
250
|
-
body: { body }
|
|
251
|
-
});
|
|
199
|
+
await this.client.issues.createComment(this.repo, number, body);
|
|
252
200
|
}
|
|
253
201
|
async doctor() {
|
|
254
202
|
const me = await this.getMyself();
|
|
255
|
-
const repo = await this.
|
|
256
|
-
const allLabels = await this.
|
|
203
|
+
const repo = await this.client.repos.getRepo(this.repo);
|
|
204
|
+
const allLabels = await this.client.labels.listLabels(this.repo);
|
|
257
205
|
const labelNames = new Set(allLabels.map((l) => l.name));
|
|
258
206
|
const statuses = [
|
|
259
207
|
{
|
|
@@ -320,12 +268,12 @@ var GitHubIssues = class {
|
|
|
320
268
|
}
|
|
321
269
|
async resolveMilestone(ref) {
|
|
322
270
|
if (/^\d+$/.test(ref)) return parseInt(ref, 10);
|
|
323
|
-
const match = (await this.
|
|
271
|
+
const match = (await this.client.issues.listMilestones(this.repo, { state: "all" })).find((m) => m.title.toLowerCase() === ref.toLowerCase());
|
|
324
272
|
if (!match) throw new Error(`Milestone "${ref}" not found in ${this.owner}/${this.repoName}. Use the numeric id or an exact title.`);
|
|
325
273
|
return match.number;
|
|
326
274
|
}
|
|
327
275
|
async removeStatusLabels(number, labels) {
|
|
328
|
-
for (const label of labels) await this.
|
|
276
|
+
for (const label of labels) await this.client.issues.removeLabel(this.repo, number, label);
|
|
329
277
|
}
|
|
330
278
|
mapIssue(raw) {
|
|
331
279
|
const statusLabels = raw.labels.map((l) => l.name).filter((n) => n.startsWith("status:"));
|
|
@@ -346,17 +294,16 @@ var GitHubIssues = class {
|
|
|
346
294
|
...raw.body ? { body: raw.body } : {},
|
|
347
295
|
status: statusName,
|
|
348
296
|
statusCategory: category,
|
|
349
|
-
assignee: raw.assignee ?
|
|
297
|
+
assignee: raw.assignee ? {
|
|
298
|
+
id: raw.assignee.login,
|
|
299
|
+
displayName: raw.assignee.name ?? raw.assignee.login,
|
|
300
|
+
...raw.assignee.email ? { emailAddress: raw.assignee.email } : {}
|
|
301
|
+
} : null,
|
|
350
302
|
updated: raw.updated_at,
|
|
351
303
|
url: raw.html_url
|
|
352
304
|
};
|
|
353
305
|
}
|
|
354
306
|
};
|
|
355
|
-
/**
|
|
356
|
-
* Accept "#42", "42", or "owner/repo#42" and return the numeric id.
|
|
357
|
-
* Cross-repo refs are allowed in input but the adapter is bound to one
|
|
358
|
-
* repo — we error if the owner/repo doesn't match.
|
|
359
|
-
*/
|
|
360
307
|
function parseIssueNumber(key, owner, repoName) {
|
|
361
308
|
const trimmed = key.trim();
|
|
362
309
|
const crossMatch = trimmed.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
@@ -368,13 +315,6 @@ function parseIssueNumber(key, owner, repoName) {
|
|
|
368
315
|
if (!/^\d+$/.test(num)) throw new Error(`Invalid GitHub issue key "${key}" — expected #N or N.`);
|
|
369
316
|
return parseInt(num, 10);
|
|
370
317
|
}
|
|
371
|
-
function mapUser(raw) {
|
|
372
|
-
return {
|
|
373
|
-
id: raw.login,
|
|
374
|
-
displayName: raw.name ?? raw.login,
|
|
375
|
-
...raw.email ? { emailAddress: raw.email } : {}
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
318
|
//#endregion
|
|
379
319
|
//#region src/sodium.ts
|
|
380
320
|
/**
|
|
@@ -410,84 +350,114 @@ async function encryptSecret(publicKeyBase64, value) {
|
|
|
410
350
|
//#endregion
|
|
411
351
|
//#region src/capabilities/secrets.ts
|
|
412
352
|
var GitHubSecrets = class {
|
|
413
|
-
|
|
353
|
+
client;
|
|
354
|
+
opts;
|
|
414
355
|
key = "secrets";
|
|
415
356
|
providerName = "github";
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
this.
|
|
419
|
-
const { owner, name } = parseRepo(opts.repo);
|
|
420
|
-
this.repoBase = `/repos/${owner}/${name}`;
|
|
357
|
+
constructor(client, opts) {
|
|
358
|
+
this.client = client;
|
|
359
|
+
this.opts = opts;
|
|
421
360
|
}
|
|
422
361
|
async listSecrets(scope) {
|
|
423
|
-
|
|
424
|
-
return (await this.rest.request(`${base}/secrets`)).secrets.map((s) => s.name);
|
|
362
|
+
return this.client.secrets.listSecrets(this.opts.repo, toGHScope(scope));
|
|
425
363
|
}
|
|
426
364
|
async setSecret(scope, name, value) {
|
|
427
|
-
const
|
|
428
|
-
const pk = await this.
|
|
365
|
+
const ghScope = toGHScope(scope);
|
|
366
|
+
const pk = await this.client.secrets.getPublicKey(this.opts.repo, ghScope);
|
|
429
367
|
const body = {
|
|
430
368
|
encrypted_value: await encryptSecret(pk.key, value),
|
|
431
369
|
key_id: pk.key_id
|
|
432
370
|
};
|
|
433
371
|
if (scope.kind === "organization") body.visibility = "all";
|
|
434
|
-
await this.
|
|
435
|
-
method: "PUT",
|
|
436
|
-
body,
|
|
437
|
-
expectNoContent: true
|
|
438
|
-
});
|
|
372
|
+
await this.client.secrets.putSecret(this.opts.repo, ghScope, name, body);
|
|
439
373
|
}
|
|
440
374
|
async deleteSecret(scope, name) {
|
|
441
|
-
|
|
442
|
-
await this.rest.request(`${base}/secrets/${name}`, {
|
|
443
|
-
method: "DELETE",
|
|
444
|
-
expectNoContent: true
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
/** Endpoint root for the given scope. */
|
|
448
|
-
scopeBase(scope) {
|
|
449
|
-
switch (scope.kind) {
|
|
450
|
-
case "repo": return `${this.repoBase}/actions`;
|
|
451
|
-
case "environment": return `${this.repoBase}/environments/${scope.name}`;
|
|
452
|
-
case "organization": return `/orgs/${scope.name}/actions`;
|
|
453
|
-
}
|
|
375
|
+
await this.client.secrets.deleteSecret(this.opts.repo, toGHScope(scope), name);
|
|
454
376
|
}
|
|
455
377
|
};
|
|
378
|
+
function toGHScope(scope) {
|
|
379
|
+
if (scope.kind === "organization") return {
|
|
380
|
+
kind: "organization",
|
|
381
|
+
org: scope.name
|
|
382
|
+
};
|
|
383
|
+
return scope;
|
|
384
|
+
}
|
|
456
385
|
//#endregion
|
|
457
|
-
//#region src/capabilities/
|
|
386
|
+
//#region src/capabilities/labels.ts
|
|
458
387
|
/**
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
* Ported from rando-id/rando.id `packages/cli/src/adapters/gh-rest.ts`
|
|
462
|
-
* with two changes for v2:
|
|
388
|
+
* Idempotently synchronise a repo's labels to the canonical set.
|
|
463
389
|
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
*
|
|
390
|
+
* - Creates labels that are missing.
|
|
391
|
+
* - PATCHes labels whose color or description has drifted.
|
|
392
|
+
* - DELETEs labels listed in `stale`.
|
|
393
|
+
* - Leaves unrecognised labels that aren't in `stale` untouched.
|
|
467
394
|
*/
|
|
395
|
+
async function syncLabels(client, repo, canonical, stale) {
|
|
396
|
+
const existing = await client.labels.listLabels(repo);
|
|
397
|
+
const existingMap = new Map(existing.map((l) => [l.name.toLowerCase(), l]));
|
|
398
|
+
let created = 0;
|
|
399
|
+
let updated = 0;
|
|
400
|
+
let deleted = 0;
|
|
401
|
+
for (const label of canonical) {
|
|
402
|
+
const current = existingMap.get(label.name);
|
|
403
|
+
if (!current) {
|
|
404
|
+
await client.labels.createLabel(repo, {
|
|
405
|
+
name: label.name,
|
|
406
|
+
color: label.color,
|
|
407
|
+
description: label.description
|
|
408
|
+
});
|
|
409
|
+
created++;
|
|
410
|
+
} else if (current.color !== label.color || (current.description ?? "") !== label.description) {
|
|
411
|
+
await client.labels.updateLabel(repo, label.name, {
|
|
412
|
+
color: label.color,
|
|
413
|
+
description: label.description
|
|
414
|
+
});
|
|
415
|
+
updated++;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const staleName of stale) if (existingMap.has(staleName)) {
|
|
419
|
+
await client.labels.deleteLabel(repo, staleName);
|
|
420
|
+
deleted++;
|
|
421
|
+
}
|
|
422
|
+
return `${created} created, ${updated} updated, ${deleted} deleted`;
|
|
423
|
+
}
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/capabilities/properties.ts
|
|
426
|
+
async function syncProperties(client, repo, values) {
|
|
427
|
+
await client.properties.setProperties(repo, values);
|
|
428
|
+
return `${Object.keys(values).length} properties set`;
|
|
429
|
+
}
|
|
430
|
+
//#endregion
|
|
431
|
+
//#region src/capabilities/topics.ts
|
|
432
|
+
async function syncTopics(client, repo, topics) {
|
|
433
|
+
await client.topics.setTopics(repo, topics);
|
|
434
|
+
return `${topics.length} topics set`;
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/capabilities/source.ts
|
|
468
438
|
var GitHubSource = class {
|
|
469
|
-
|
|
439
|
+
client;
|
|
470
440
|
key = "source";
|
|
471
441
|
providerName = "github";
|
|
472
442
|
owner;
|
|
473
443
|
name;
|
|
474
|
-
|
|
444
|
+
repo;
|
|
475
445
|
repoRoot;
|
|
476
446
|
workflowDir;
|
|
477
|
-
constructor(
|
|
478
|
-
this.
|
|
479
|
-
const
|
|
447
|
+
constructor(client, opts) {
|
|
448
|
+
this.client = client;
|
|
449
|
+
const [owner = "", name = ""] = opts.repo.split("/", 2);
|
|
480
450
|
this.owner = owner;
|
|
481
451
|
this.name = name;
|
|
482
|
-
this.
|
|
452
|
+
this.repo = opts.repo;
|
|
483
453
|
this.repoRoot = opts.repoRoot;
|
|
484
454
|
this.workflowDir = join(opts.repoRoot, ".github", "workflows");
|
|
485
455
|
}
|
|
486
456
|
async whoami() {
|
|
487
|
-
return { login: (await this.
|
|
457
|
+
return { login: (await this.client.user.getCurrentUser()).login };
|
|
488
458
|
}
|
|
489
459
|
async getRepo() {
|
|
490
|
-
const data = await this.
|
|
460
|
+
const data = await this.client.repos.getRepo(this.repo);
|
|
491
461
|
return {
|
|
492
462
|
owner: this.owner,
|
|
493
463
|
name: this.name,
|
|
@@ -495,79 +465,40 @@ var GitHubSource = class {
|
|
|
495
465
|
};
|
|
496
466
|
}
|
|
497
467
|
async listRulesets() {
|
|
498
|
-
return this.
|
|
468
|
+
return this.client.rulesets.listRulesets(this.repo);
|
|
499
469
|
}
|
|
500
470
|
async createRuleset(payload) {
|
|
501
|
-
return this.
|
|
502
|
-
method: "POST",
|
|
503
|
-
body: payload
|
|
504
|
-
});
|
|
471
|
+
return this.client.rulesets.createRuleset(this.repo, payload);
|
|
505
472
|
}
|
|
506
473
|
async updateRuleset(id, payload) {
|
|
507
|
-
return this.
|
|
508
|
-
method: "PUT",
|
|
509
|
-
body: payload
|
|
510
|
-
});
|
|
474
|
+
return this.client.rulesets.updateRuleset(this.repo, id, payload);
|
|
511
475
|
}
|
|
512
476
|
async updateRepoSettings(settings) {
|
|
513
|
-
await this.
|
|
514
|
-
method: "PATCH",
|
|
515
|
-
body: settings
|
|
516
|
-
});
|
|
477
|
+
await this.client.repos.updateRepo(this.repo, settings);
|
|
517
478
|
}
|
|
518
479
|
async protectBranch(branch, payload) {
|
|
519
|
-
await this.
|
|
520
|
-
method: "PUT",
|
|
521
|
-
body: payload
|
|
522
|
-
});
|
|
480
|
+
await this.client.branches.protectBranch(this.repo, branch, payload);
|
|
523
481
|
}
|
|
524
482
|
async enableVulnerabilityAlerts() {
|
|
525
|
-
await this.
|
|
526
|
-
method: "PUT",
|
|
527
|
-
expectNoContent: true
|
|
528
|
-
});
|
|
483
|
+
await this.client.security.enableVulnerabilityAlerts(this.repo);
|
|
529
484
|
}
|
|
530
485
|
async enableAutomatedSecurityFixes() {
|
|
531
|
-
await this.
|
|
532
|
-
method: "PUT",
|
|
533
|
-
expectNoContent: true
|
|
534
|
-
});
|
|
486
|
+
await this.client.security.enableAutomatedSecurityFixes(this.repo);
|
|
535
487
|
}
|
|
536
488
|
async enableSecretScanning() {
|
|
537
|
-
await this.
|
|
538
|
-
method: "PATCH",
|
|
539
|
-
body: { security_and_analysis: {
|
|
540
|
-
secret_scanning: { status: "enabled" },
|
|
541
|
-
secret_scanning_push_protection: { status: "enabled" },
|
|
542
|
-
secret_scanning_validity_checks: { status: "enabled" },
|
|
543
|
-
secret_scanning_non_provider_patterns: { status: "enabled" }
|
|
544
|
-
} }
|
|
545
|
-
});
|
|
489
|
+
await this.client.security.enableSecretScanning(this.repo);
|
|
546
490
|
}
|
|
547
491
|
async enableCodeScanning() {
|
|
548
|
-
return `run ${(await this.
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
query_suite: "extended",
|
|
553
|
-
threat_model: "remote_and_local"
|
|
554
|
-
}
|
|
555
|
-
})).run_id}`;
|
|
492
|
+
return `run ${(await this.client.security.enableCodeScanning(this.repo)).run_id}`;
|
|
493
|
+
}
|
|
494
|
+
async disableDefaultCodeScanning() {
|
|
495
|
+
await this.client.security.disableDefaultCodeScanning(this.repo);
|
|
556
496
|
}
|
|
557
497
|
async enableDependencyGraph() {
|
|
558
|
-
await this.
|
|
559
|
-
method: "PATCH",
|
|
560
|
-
body: { security_and_analysis: {
|
|
561
|
-
dependency_graph: { status: "enabled" },
|
|
562
|
-
dependency_graph_autosubmit_action: { status: "enabled" }
|
|
563
|
-
} }
|
|
564
|
-
});
|
|
498
|
+
await this.client.security.enableDependencyGraph(this.repo);
|
|
565
499
|
}
|
|
566
500
|
async enablePrivateVulnerabilityReporting() {
|
|
567
|
-
await this.
|
|
568
|
-
method: "PUT",
|
|
569
|
-
expectNoContent: true
|
|
570
|
-
});
|
|
501
|
+
await this.client.security.enablePrivateVulnerabilityReporting(this.repo);
|
|
571
502
|
}
|
|
572
503
|
async listWorkflowFiles() {
|
|
573
504
|
try {
|
|
@@ -602,6 +533,15 @@ var GitHubSource = class {
|
|
|
602
533
|
await ensureDir(dirname(full));
|
|
603
534
|
await writeFile(full, contents, "utf8");
|
|
604
535
|
}
|
|
536
|
+
async syncLabels(canonical, stale) {
|
|
537
|
+
return syncLabels(this.client, this.repo, canonical, stale);
|
|
538
|
+
}
|
|
539
|
+
async syncProperties(values) {
|
|
540
|
+
return syncProperties(this.client, this.repo, values);
|
|
541
|
+
}
|
|
542
|
+
async syncTopics(topics) {
|
|
543
|
+
return syncTopics(this.client, this.repo, topics);
|
|
544
|
+
}
|
|
605
545
|
};
|
|
606
546
|
function isENOENT(err) {
|
|
607
547
|
return typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT";
|
|
@@ -618,55 +558,6 @@ async function ensureDir(path) {
|
|
|
618
558
|
}
|
|
619
559
|
}
|
|
620
560
|
//#endregion
|
|
621
|
-
//#region src/rest.ts
|
|
622
|
-
/**
|
|
623
|
-
* Thin REST wrapper around api.github.com.
|
|
624
|
-
*
|
|
625
|
-
* Adapted from rando-id/rando.id `packages/cli/src/adapters/gh-rest.ts`
|
|
626
|
-
* `request<T>` method into a standalone class so each capability can
|
|
627
|
-
* hold a reference and share the same auth/error handling.
|
|
628
|
-
*
|
|
629
|
-
* Errors throw `ProviderApiError` from `@theholocron/cli`, which the
|
|
630
|
-
* orchestrator catches to soft-skip rather than abort the whole pipeline.
|
|
631
|
-
*/
|
|
632
|
-
var GitHubRestClient = class {
|
|
633
|
-
token;
|
|
634
|
-
fetchImpl;
|
|
635
|
-
baseUrl;
|
|
636
|
-
constructor(opts) {
|
|
637
|
-
this.token = opts.token;
|
|
638
|
-
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
639
|
-
let url = opts.baseUrl ?? "https://api.github.com";
|
|
640
|
-
while (url.endsWith("/")) url = url.slice(0, -1);
|
|
641
|
-
this.baseUrl = url;
|
|
642
|
-
}
|
|
643
|
-
async request(path, opts = {}) {
|
|
644
|
-
const url = `${this.baseUrl}${path}`;
|
|
645
|
-
const headers = {
|
|
646
|
-
accept: "application/vnd.github+json",
|
|
647
|
-
authorization: `Bearer ${this.token}`,
|
|
648
|
-
"x-github-api-version": "2022-11-28"
|
|
649
|
-
};
|
|
650
|
-
const init = {
|
|
651
|
-
method: opts.method ?? "GET",
|
|
652
|
-
headers
|
|
653
|
-
};
|
|
654
|
-
if (opts.body !== void 0) {
|
|
655
|
-
headers["content-type"] = "application/json";
|
|
656
|
-
init.body = JSON.stringify(opts.body);
|
|
657
|
-
}
|
|
658
|
-
const res = await this.fetchImpl(url, init);
|
|
659
|
-
if (!res.ok) {
|
|
660
|
-
const body = await res.text().catch(() => "");
|
|
661
|
-
throw new ProviderApiError(`GitHub ${init.method} ${path} → ${res.status}`, res.status, body);
|
|
662
|
-
}
|
|
663
|
-
if (opts.expectNoContent || res.status === 204) return;
|
|
664
|
-
const text = await res.text();
|
|
665
|
-
if (!text) return void 0;
|
|
666
|
-
return JSON.parse(text);
|
|
667
|
-
}
|
|
668
|
-
};
|
|
669
|
-
//#endregion
|
|
670
561
|
//#region src/verify-token.ts
|
|
671
562
|
/**
|
|
672
563
|
* `verifyToken` — plugin-level export used by `holocron auth set` +
|
|
@@ -679,12 +570,13 @@ var GitHubRestClient = class {
|
|
|
679
570
|
* what we don't have yet at bootstrap time.
|
|
680
571
|
*/
|
|
681
572
|
async function verifyToken(token, opts = {}) {
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
573
|
+
const client = createGitHubClient({
|
|
574
|
+
token,
|
|
575
|
+
baseUrl: opts.baseUrl,
|
|
576
|
+
fetch: opts.fetch
|
|
577
|
+
});
|
|
686
578
|
try {
|
|
687
|
-
const me = await
|
|
579
|
+
const me = await client.user.getCurrentUser();
|
|
688
580
|
return {
|
|
689
581
|
ok: true,
|
|
690
582
|
subject: `user @ ${me.login ?? me.email ?? "unknown"}`
|
|
@@ -701,7 +593,7 @@ async function verifyToken(token, opts = {}) {
|
|
|
701
593
|
function createContext(options) {
|
|
702
594
|
return {
|
|
703
595
|
options,
|
|
704
|
-
|
|
596
|
+
client: createGitHubClient$1({
|
|
705
597
|
token: resolveToken(options),
|
|
706
598
|
baseUrl: options.baseUrl,
|
|
707
599
|
fetch: options.fetch
|
|
@@ -711,24 +603,24 @@ function createContext(options) {
|
|
|
711
603
|
};
|
|
712
604
|
}
|
|
713
605
|
function source(ctx) {
|
|
714
|
-
return new GitHubSource(ctx.
|
|
606
|
+
return new GitHubSource(ctx.client, {
|
|
715
607
|
repo: ctx.repo,
|
|
716
608
|
repoRoot: ctx.repoRoot
|
|
717
609
|
});
|
|
718
610
|
}
|
|
719
611
|
function secrets(ctx) {
|
|
720
|
-
return new GitHubSecrets(ctx.
|
|
612
|
+
return new GitHubSecrets(ctx.client, { repo: ctx.repo });
|
|
721
613
|
}
|
|
722
614
|
function environments(ctx) {
|
|
723
|
-
return new GitHubEnvironments(ctx.
|
|
615
|
+
return new GitHubEnvironments(ctx.client, { repo: ctx.repo });
|
|
724
616
|
}
|
|
725
617
|
function ci(ctx) {
|
|
726
|
-
return new GitHubCi(ctx.
|
|
618
|
+
return new GitHubCi(ctx.client, { repo: ctx.repo });
|
|
727
619
|
}
|
|
728
620
|
function issues(ctx) {
|
|
729
621
|
const opts = { repo: ctx.repo };
|
|
730
622
|
if (ctx.options.labels !== void 0) opts.labels = ctx.options.labels;
|
|
731
|
-
return new GitHubIssues(ctx.
|
|
623
|
+
return new GitHubIssues(ctx.client, opts);
|
|
732
624
|
}
|
|
733
625
|
function createPlugin(options) {
|
|
734
626
|
const ctx = createContext(options);
|
|
@@ -751,4 +643,4 @@ function createPlugin(options) {
|
|
|
751
643
|
*/
|
|
752
644
|
const AUTH_HINT = "generate a Personal Access Token at https://github.com/settings/tokens (scopes: repo, admin:repo_hook — plus admin:org for org-level ops), then run: holocron auth set github <PAT>";
|
|
753
645
|
//#endregion
|
|
754
|
-
export { AUTH_HINT, AuthError, GitHubCi, GitHubEnvironments, GitHubIssues,
|
|
646
|
+
export { AUTH_HINT, AuthError, GitHubCi, GitHubEnvironments, GitHubIssues, GitHubSecrets, GitHubSource, ci, createContext, createGitHubClient, createPlugin, encryptSecret, environments, issues, resolveToken, secrets, source, syncLabels, verifyToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/holocron-plugin-github",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.50",
|
|
4
4
|
"description": "Holocron plugin for GitHub. Implements source, ci, secrets, environments, and issues capabilities against the GitHub REST API.",
|
|
5
5
|
"homepage": "https://github.com/theholocron/holocron/tree/main/packages/holocron-plugin-github#readme",
|
|
6
6
|
"bugs": "https://github.com/theholocron/holocron/issues",
|
|
@@ -21,24 +21,29 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
|
-
"@theholocron/
|
|
24
|
+
"@theholocron/github-client": "^0.7.0",
|
|
25
|
+
"@theholocron/cli": "2.0.0-alpha.50"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"libsodium-wrappers": "^0.7.15"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
|
-
"@theholocron/
|
|
31
|
-
"@
|
|
32
|
-
"@
|
|
31
|
+
"@theholocron/github-client": "^0.7.0",
|
|
32
|
+
"@types/node": "^26",
|
|
33
|
+
"@theholocron/eslint-config": "^5.2.0",
|
|
34
|
+
"@theholocron/tsconfig": "^6.0.0",
|
|
35
|
+
"@theholocron/tsdown-config": "^5.1.2",
|
|
36
|
+
"@theholocron/vitest-config": "^5.2.0",
|
|
33
37
|
"@types/libsodium-wrappers": "^0.7.14",
|
|
34
|
-
"@vitest/
|
|
35
|
-
"eslint": "^
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"vitest": "^3.2.6",
|
|
38
|
+
"@vitest/eslint-plugin": "^1.6.23",
|
|
39
|
+
"eslint": "^10.7.0",
|
|
40
|
+
"eslint-plugin-n": "^18.2.2",
|
|
41
|
+
"globals": "^17.7.0",
|
|
39
42
|
"tsdown": "^0.22.3",
|
|
40
43
|
"tsx": "^4.22.4",
|
|
41
|
-
"
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vitest": "^4.1.10",
|
|
46
|
+
"@theholocron/cli": "2.0.0-alpha.50"
|
|
42
47
|
},
|
|
43
48
|
"publishConfig": {
|
|
44
49
|
"access": "public"
|