@theholocron/holocron-plugin-github 5.0.0-alpha.434 → 5.0.0-alpha.435
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/dist/index.d.mts +7 -5
- package/dist/index.mjs +391 -28
- package/package.json +7 -7
package/dist/index.d.mts
CHANGED
|
@@ -377,6 +377,12 @@ export declare function syncTeams(client: GitHubClient, repo: string, teams: Tea
|
|
|
377
377
|
* Returns the base64 ciphertext to send as `encrypted_value`.
|
|
378
378
|
*/
|
|
379
379
|
export declare function encryptSecret(publicKeyBase64: string, value: string): Promise<string>;
|
|
380
|
+
/**
|
|
381
|
+
* Every token this plugin takes, for `holocron doctor tokens`, with what each layer's token must and must not be able
|
|
382
|
+
* to do. The release token is `optional`: only CI's release workflow reads it (from the org secret), so a development
|
|
383
|
+
* environment has no reason to hold it.
|
|
384
|
+
*/
|
|
385
|
+
export declare const tokens: PluginToken[];
|
|
380
386
|
/** Options for `verifyToken`. */
|
|
381
387
|
interface VerifyTokenOptions {
|
|
382
388
|
/** Overrides the API base URL, for tests. */
|
|
@@ -432,6 +438,7 @@ export declare function projects(ctx: PluginContext): Projects;
|
|
|
432
438
|
export declare function createPlugin(options: GitHubPluginOptions): {
|
|
433
439
|
name: string;
|
|
434
440
|
probes: {
|
|
441
|
+
token: import("@theholocron/cli").Probe;
|
|
435
442
|
connection: import("@theholocron/cli").Probe;
|
|
436
443
|
};
|
|
437
444
|
capabilities: {
|
|
@@ -448,9 +455,4 @@ export declare function createPlugin(options: GitHubPluginOptions): {
|
|
|
448
455
|
* is supplied or the supplied token is rejected.
|
|
449
456
|
*/
|
|
450
457
|
export declare const AUTH_HINT: string;
|
|
451
|
-
/**
|
|
452
|
-
* Every token this plugin takes, for `holocron doctor secrets --verify`. The release token is `optional`: only CI's release
|
|
453
|
-
* workflow reads it (from the org secret), so a development environment has no reason to hold it.
|
|
454
|
-
*/
|
|
455
|
-
export declare const tokens: PluginToken[];
|
|
456
458
|
export { type Auth, AuthError, type CanonicalLabel, type GitHubClientOptions, type NormalizedTeamEntry, type ProjectsOptions, type ResolveTokenInput, type TeamEntry, type TeamPermission, type VerifyTokenFailure, type VerifyTokenResult, type VerifyTokenSuccess, createFeatureResolver, createGitHubClient };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { AuthError, MissingTokenError, ProviderApiError, connectionProbe, createFeatureResolver, pickByCreation, readKeysLimited, reportProgress } from "@theholocron/cli";
|
|
2
|
+
import { AuthError, MissingTokenError, ProviderApiError, connectionProbe, createFeatureResolver, pickByCreation, readKeysLimited, reportProgress, tokenExpectations } from "@theholocron/cli";
|
|
3
3
|
import { createGitHubClient, createGitHubClient as createGitHubClient$1 } from "@theholocron/github-client";
|
|
4
4
|
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
@@ -2143,6 +2143,378 @@ async function ensureDir(path) {
|
|
|
2143
2143
|
throw err;
|
|
2144
2144
|
}
|
|
2145
2145
|
}
|
|
2146
|
+
// src/probes/token/expectations.ts
|
|
2147
|
+
/** The same rules in every layer that runs the workflow: a laptop (`local` inherits `dev`) and CI. */
|
|
2148
|
+
function everywhere(rules) {
|
|
2149
|
+
return { permissions: {
|
|
2150
|
+
local: rules,
|
|
2151
|
+
dev: rules,
|
|
2152
|
+
ci: rules
|
|
2153
|
+
} };
|
|
2154
|
+
}
|
|
2155
|
+
/**
|
|
2156
|
+
* What each GitHub token is meant to carry, by its `envName`, the same as the GitHub tokens table in
|
|
2157
|
+
* `docs/src/content/docs/tokens.mdx`. This is the plugin's `tokens` declaration, read through `tokenExpectations()`;
|
|
2158
|
+
* change it and that table together.
|
|
2159
|
+
*
|
|
2160
|
+
* Every token CI uses (the `sync-repo`, `sync-secrets`, `reconcile-projects` and `check-tokens` workflows read all of
|
|
2161
|
+
* them) is declared with the same permissions in `local`, `dev` and `ci`, so the probe checks that a `ci` token is no
|
|
2162
|
+
* broader than its `dev` one rather than assuming a split the workflows do not allow. The release token is held by CI
|
|
2163
|
+
* only, so it has no `local` or `dev` entry. A narrow token must not reach administration or secrets; the admin token
|
|
2164
|
+
* must not reach organization secrets (that is `HOLOCRON_SECRETS_TOKEN`'s alone).
|
|
2165
|
+
*/
|
|
2166
|
+
const EXPECTED = {
|
|
2167
|
+
HOLOCRON_READ_TOKEN: everywhere({
|
|
2168
|
+
contents: "required",
|
|
2169
|
+
actions: "required",
|
|
2170
|
+
administration: "absent",
|
|
2171
|
+
secrets: "absent",
|
|
2172
|
+
environments: "absent"
|
|
2173
|
+
}),
|
|
2174
|
+
HOLOCRON_ISSUES_TOKEN: everywhere({
|
|
2175
|
+
issues: "required",
|
|
2176
|
+
administration: "absent",
|
|
2177
|
+
secrets: "absent",
|
|
2178
|
+
environments: "absent"
|
|
2179
|
+
}),
|
|
2180
|
+
HOLOCRON_SYNC_TOKEN: everywhere({
|
|
2181
|
+
actions: "required",
|
|
2182
|
+
contents: "required",
|
|
2183
|
+
"pull-requests": "required",
|
|
2184
|
+
administration: "absent",
|
|
2185
|
+
secrets: "absent"
|
|
2186
|
+
}),
|
|
2187
|
+
HOLOCRON_RELEASE_TOKEN: { permissions: { ci: {
|
|
2188
|
+
contents: "required",
|
|
2189
|
+
issues: "required",
|
|
2190
|
+
"pull-requests": "required",
|
|
2191
|
+
administration: "absent",
|
|
2192
|
+
secrets: "absent"
|
|
2193
|
+
} } },
|
|
2194
|
+
HOLOCRON_ADMIN_TOKEN: everywhere({
|
|
2195
|
+
administration: "required",
|
|
2196
|
+
secrets: "required",
|
|
2197
|
+
environments: "required",
|
|
2198
|
+
issues: "required",
|
|
2199
|
+
"custom-properties": "required",
|
|
2200
|
+
"actions-variables": "required",
|
|
2201
|
+
"org-secrets": "absent"
|
|
2202
|
+
}),
|
|
2203
|
+
HOLOCRON_SECRETS_TOKEN: everywhere({
|
|
2204
|
+
"org-secrets": "required",
|
|
2205
|
+
administration: "absent"
|
|
2206
|
+
}),
|
|
2207
|
+
HOLOCRON_DEPLOY_TOKEN: everywhere({
|
|
2208
|
+
pages: "required",
|
|
2209
|
+
administration: "required",
|
|
2210
|
+
secrets: "absent",
|
|
2211
|
+
"org-secrets": "absent"
|
|
2212
|
+
}),
|
|
2213
|
+
HOLOCRON_USER_SYNC_TOKEN: everywhere({
|
|
2214
|
+
"user-sync-contents": "required",
|
|
2215
|
+
administration: "absent",
|
|
2216
|
+
secrets: "absent"
|
|
2217
|
+
}),
|
|
2218
|
+
HOLOCRON_ORG_TOKEN: everywhere({
|
|
2219
|
+
"org-administration": "required",
|
|
2220
|
+
"org-members": "required",
|
|
2221
|
+
"org-variables": "required",
|
|
2222
|
+
"org-secrets": "absent"
|
|
2223
|
+
})
|
|
2224
|
+
};
|
|
2225
|
+
// src/tokens.ts
|
|
2226
|
+
/** Adds what a layer's token must and must not be able to do (`EXPECTED`, by `envName`) to a token. */
|
|
2227
|
+
function declared(token) {
|
|
2228
|
+
return {
|
|
2229
|
+
...token,
|
|
2230
|
+
expect: EXPECTED[token.envName]
|
|
2231
|
+
};
|
|
2232
|
+
}
|
|
2233
|
+
/**
|
|
2234
|
+
* Every token this plugin takes, for `holocron doctor tokens`, with what each layer's token must and must not be able
|
|
2235
|
+
* to do. The release token is `optional`: only CI's release workflow reads it (from the org secret), so a development
|
|
2236
|
+
* environment has no reason to hold it.
|
|
2237
|
+
*/
|
|
2238
|
+
const tokens = [
|
|
2239
|
+
declared(resolveReadToken.token),
|
|
2240
|
+
declared(resolveIssuesToken.token),
|
|
2241
|
+
declared(resolveSyncToken.token),
|
|
2242
|
+
declared({
|
|
2243
|
+
...resolveReleaseToken.token,
|
|
2244
|
+
optional: true
|
|
2245
|
+
}),
|
|
2246
|
+
declared(resolveAdminToken.token),
|
|
2247
|
+
declared(resolveSecretsToken.token),
|
|
2248
|
+
declared(resolveDeployToken.token),
|
|
2249
|
+
declared(resolveUserSyncToken.token),
|
|
2250
|
+
declared(resolveOrgToken.token)
|
|
2251
|
+
];
|
|
2252
|
+
// src/probes/token/parse-token-args.ts
|
|
2253
|
+
/** Reads the words after the probe name: `--layer <word>` and `--token-name <VAR>`, in either spelling. */
|
|
2254
|
+
function parseTokenArgs(args) {
|
|
2255
|
+
let layer;
|
|
2256
|
+
let tokenName;
|
|
2257
|
+
for (let i = 0; i < args.length; i++) {
|
|
2258
|
+
const arg = args[i];
|
|
2259
|
+
if (arg === "--layer") layer = args[++i];
|
|
2260
|
+
else if (arg.startsWith("--layer=")) layer = arg.slice(8);
|
|
2261
|
+
else if (arg === "--token-name") tokenName = args[++i];
|
|
2262
|
+
else if (arg.startsWith("--token-name=")) tokenName = arg.slice(13);
|
|
2263
|
+
}
|
|
2264
|
+
return {
|
|
2265
|
+
...layer ? { layer } : {},
|
|
2266
|
+
...tokenName ? { tokenName } : {}
|
|
2267
|
+
};
|
|
2268
|
+
}
|
|
2269
|
+
// src/probes/token/permissions.ts
|
|
2270
|
+
/**
|
|
2271
|
+
* Every permission the GitHub plugin's tokens are declared against, by the name the declaration uses, with one read
|
|
2272
|
+
* that needs it. A read proves the token can see the area, not that it can write to it: GitHub exposes no list of a
|
|
2273
|
+
* fine-grained token's permissions, so the write half of `read/write` is taken from the permission table in
|
|
2274
|
+
* `docs/src/content/docs/tokens.mdx`. On a public repository the `contents`, `actions`, `issues` and `pull-requests`
|
|
2275
|
+
* reads succeed for any token, so only the other permissions can prove a permission absent. Names with more than one
|
|
2276
|
+
* word are hyphenated, not underscored: `holocron probe docs tokens` strips `_` (Markdown emphasis) from a table cell
|
|
2277
|
+
* before it compares the id with the declaration.
|
|
2278
|
+
*/
|
|
2279
|
+
const PERMISSIONS = {
|
|
2280
|
+
contents: {
|
|
2281
|
+
label: "Contents",
|
|
2282
|
+
path: "/repos/{r}/contents"
|
|
2283
|
+
},
|
|
2284
|
+
actions: {
|
|
2285
|
+
label: "Actions",
|
|
2286
|
+
path: "/repos/{r}/actions/runs?per_page=1"
|
|
2287
|
+
},
|
|
2288
|
+
issues: {
|
|
2289
|
+
label: "Issues",
|
|
2290
|
+
path: "/repos/{r}/issues?per_page=1"
|
|
2291
|
+
},
|
|
2292
|
+
"pull-requests": {
|
|
2293
|
+
label: "Pull requests",
|
|
2294
|
+
path: "/repos/{r}/pulls?per_page=1"
|
|
2295
|
+
},
|
|
2296
|
+
administration: {
|
|
2297
|
+
label: "Administration",
|
|
2298
|
+
path: "/repos/{r}/actions/permissions"
|
|
2299
|
+
},
|
|
2300
|
+
secrets: {
|
|
2301
|
+
label: "Secrets",
|
|
2302
|
+
path: "/repos/{r}/actions/secrets?per_page=1"
|
|
2303
|
+
},
|
|
2304
|
+
environments: {
|
|
2305
|
+
label: "Environments",
|
|
2306
|
+
path: "/repos/{r}/environments?per_page=1"
|
|
2307
|
+
},
|
|
2308
|
+
"actions-variables": {
|
|
2309
|
+
label: "Variables",
|
|
2310
|
+
path: "/repos/{r}/actions/variables?per_page=1"
|
|
2311
|
+
},
|
|
2312
|
+
"custom-properties": {
|
|
2313
|
+
label: "Custom properties",
|
|
2314
|
+
path: "/repos/{r}/properties/values"
|
|
2315
|
+
},
|
|
2316
|
+
pages: {
|
|
2317
|
+
label: "Pages",
|
|
2318
|
+
path: "/repos/{r}/pages"
|
|
2319
|
+
},
|
|
2320
|
+
"org-administration": {
|
|
2321
|
+
label: "Organization: Administration",
|
|
2322
|
+
path: "/orgs/{o}/actions/permissions"
|
|
2323
|
+
},
|
|
2324
|
+
"org-members": {
|
|
2325
|
+
label: "Organization: Members",
|
|
2326
|
+
path: "/orgs/{o}/members?per_page=1"
|
|
2327
|
+
},
|
|
2328
|
+
"org-variables": {
|
|
2329
|
+
label: "Organization: Variables",
|
|
2330
|
+
path: "/orgs/{o}/actions/variables?per_page=1"
|
|
2331
|
+
},
|
|
2332
|
+
"org-secrets": {
|
|
2333
|
+
label: "Organization: Secrets",
|
|
2334
|
+
path: "/orgs/{o}/actions/secrets?per_page=1"
|
|
2335
|
+
},
|
|
2336
|
+
"user-sync-contents": {
|
|
2337
|
+
label: "Contents (.github-private)",
|
|
2338
|
+
path: "/repos/{o}/.github-private/contents"
|
|
2339
|
+
}
|
|
2340
|
+
};
|
|
2341
|
+
// src/probes/token/token.ts
|
|
2342
|
+
/** The token checked when none is named: the one the plugin reads with by default. */
|
|
2343
|
+
const DEFAULT_TOKEN = "HOLOCRON_READ_TOKEN";
|
|
2344
|
+
/** Does one GET with the token; the response headers are returned for the caller that wants them. */
|
|
2345
|
+
async function get(ctx, token, path) {
|
|
2346
|
+
return ctx.fetch(`${ctx.baseUrl}${path}`, { headers: {
|
|
2347
|
+
authorization: `Bearer ${token}`,
|
|
2348
|
+
accept: "application/vnd.github+json",
|
|
2349
|
+
"x-github-api-version": "2022-11-28",
|
|
2350
|
+
"user-agent": "holocron-probe"
|
|
2351
|
+
} });
|
|
2352
|
+
}
|
|
2353
|
+
/**
|
|
2354
|
+
* One read for one permission. GitHub answers a token without the permission with a 403, or a 404 for something it will
|
|
2355
|
+
* not admit exists, so both are `none`; a 2xx is `has`. Any other status, or a request that did not go through, is an
|
|
2356
|
+
* `error`, which is a failure whatever the layer expects.
|
|
2357
|
+
*/
|
|
2358
|
+
async function reach(ctx, token, permission) {
|
|
2359
|
+
const [owner = ""] = ctx.repo.split("/");
|
|
2360
|
+
const path = PERMISSIONS[permission].path.replaceAll("{r}", ctx.repo).replaceAll("{o}", owner);
|
|
2361
|
+
try {
|
|
2362
|
+
const res = await get(ctx, token, path);
|
|
2363
|
+
if (res.ok) return {
|
|
2364
|
+
kind: "has",
|
|
2365
|
+
detail: `read ${path.split("?")[0]}`
|
|
2366
|
+
};
|
|
2367
|
+
if (res.status === 403 || res.status === 404) return {
|
|
2368
|
+
kind: "none",
|
|
2369
|
+
detail: `no access (${res.status})`
|
|
2370
|
+
};
|
|
2371
|
+
return {
|
|
2372
|
+
kind: "error",
|
|
2373
|
+
detail: `${res.status} ${res.statusText}`.trim()
|
|
2374
|
+
};
|
|
2375
|
+
} catch (err) {
|
|
2376
|
+
return {
|
|
2377
|
+
kind: "error",
|
|
2378
|
+
detail: errorMessage(err)
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* One row for what was tried. With no rule it only describes (`ok` for access, `info` for none). Under a layer's rule it
|
|
2384
|
+
* judges: a required permission the token lacks fails, and one the layer must not have fails when the token has it; a
|
|
2385
|
+
* permission that is rightly absent is a bare `ok`.
|
|
2386
|
+
*/
|
|
2387
|
+
function rowFor({ label, outcome }, rule) {
|
|
2388
|
+
if (outcome.kind === "error") return {
|
|
2389
|
+
status: "fail",
|
|
2390
|
+
label,
|
|
2391
|
+
detail: outcome.detail
|
|
2392
|
+
};
|
|
2393
|
+
const has = outcome.kind === "has";
|
|
2394
|
+
if (rule === "required") return {
|
|
2395
|
+
status: has ? "ok" : "fail",
|
|
2396
|
+
label,
|
|
2397
|
+
detail: outcome.detail
|
|
2398
|
+
};
|
|
2399
|
+
if (rule === "absent") return has ? {
|
|
2400
|
+
status: "fail",
|
|
2401
|
+
label,
|
|
2402
|
+
detail: outcome.detail
|
|
2403
|
+
} : {
|
|
2404
|
+
status: "ok",
|
|
2405
|
+
label
|
|
2406
|
+
};
|
|
2407
|
+
return {
|
|
2408
|
+
status: has ? "ok" : "info",
|
|
2409
|
+
label,
|
|
2410
|
+
detail: outcome.detail
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
/**
|
|
2414
|
+
* The `token` probe: `holocron probe github token` tries each permission Holocron uses with one of the plugin's nine
|
|
2415
|
+
* tokens and reports which ones the token reaches. It only reads (one GET per permission), so it changes nothing, and a
|
|
2416
|
+
* read proves the token can see an area, not that it can write there.
|
|
2417
|
+
*
|
|
2418
|
+
* GitHub lists scopes only for a classic token (the `X-OAuth-Scopes` header on any response), so a classic token's scopes
|
|
2419
|
+
* are shown. A fine-grained token has no list, so the permissions are found by trying them; the first row says which kind
|
|
2420
|
+
* was read. With `--environment <layer>` the CLI runs the probe once for each token the Environment holds and hands it
|
|
2421
|
+
* `--token-name <envName>` (and the token itself as `cliToken`); by hand, `--token-name` picks which token the plugin
|
|
2422
|
+
* resolves (default `HOLOCRON_READ_TOKEN`).
|
|
2423
|
+
*
|
|
2424
|
+
* With no layer the token is only described. When the CLI passes `--layer` (because `--environment` named `ci`, `dev` or
|
|
2425
|
+
* `local`), the results are judged against what that token is meant to carry in that layer (the plugin's `tokens`
|
|
2426
|
+
* declaration, read with `tokenExpectations()`) and shown in two groups, what it should have and what it should not,
|
|
2427
|
+
* where a missing or extra permission fails.
|
|
2428
|
+
*/
|
|
2429
|
+
function tokenProbe(ctx) {
|
|
2430
|
+
return {
|
|
2431
|
+
summary: "Try each GitHub permission Holocron uses (contents, actions, issues, administration, secrets, ...) with one of the plugin's tokens and report which it reaches; for a ci or dev Environment, check it against what that layer should carry. Read-only.",
|
|
2432
|
+
writes: false,
|
|
2433
|
+
acceptsToken: true,
|
|
2434
|
+
async run(probe) {
|
|
2435
|
+
const { layer, tokenName } = parseTokenArgs(probe.args ?? []);
|
|
2436
|
+
const envName = tokenName ?? DEFAULT_TOKEN;
|
|
2437
|
+
const token = ctx.resolve(envName);
|
|
2438
|
+
if (token === void 0) {
|
|
2439
|
+
probe.say({
|
|
2440
|
+
status: "fail",
|
|
2441
|
+
label: envName,
|
|
2442
|
+
detail: `not a GitHub token; use one of: ${ctx.tokenNames.join(", ")}`
|
|
2443
|
+
});
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2446
|
+
probe.secret(token);
|
|
2447
|
+
let identity;
|
|
2448
|
+
try {
|
|
2449
|
+
identity = await get(ctx, token, "/user");
|
|
2450
|
+
} catch (err) {
|
|
2451
|
+
probe.say({
|
|
2452
|
+
status: "fail",
|
|
2453
|
+
label: "token",
|
|
2454
|
+
detail: errorMessage(err)
|
|
2455
|
+
});
|
|
2456
|
+
return;
|
|
2457
|
+
}
|
|
2458
|
+
if (identity.status === 401) {
|
|
2459
|
+
probe.say({
|
|
2460
|
+
status: "fail",
|
|
2461
|
+
label: "token",
|
|
2462
|
+
detail: "rejected by GitHub (401): expired, revoked or not a token"
|
|
2463
|
+
});
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const scopes = identity.headers.get("x-oauth-scopes");
|
|
2467
|
+
probe.say({
|
|
2468
|
+
status: "ok",
|
|
2469
|
+
label: "token",
|
|
2470
|
+
detail: scopes === null ? "fine-grained (GitHub lists no permissions for one, so each is tried with a read)" : `classic (scopes: ${scopes === "" ? "none" : scopes}); each permission is also tried with a read`
|
|
2471
|
+
});
|
|
2472
|
+
const declared = layer ? tokenExpectations(tokens, {
|
|
2473
|
+
layer,
|
|
2474
|
+
envName
|
|
2475
|
+
})[0] : void 0;
|
|
2476
|
+
if (layer && !declared) probe.say({
|
|
2477
|
+
status: "info",
|
|
2478
|
+
label: layer,
|
|
2479
|
+
detail: "no expectations are declared for this token there, so it is only described"
|
|
2480
|
+
});
|
|
2481
|
+
const rules = declared?.rules ?? {};
|
|
2482
|
+
const names = declared ? Object.keys(rules) : Object.keys(PERMISSIONS);
|
|
2483
|
+
const tried = [];
|
|
2484
|
+
for (const permission of names) tried.push({
|
|
2485
|
+
label: PERMISSIONS[permission].label,
|
|
2486
|
+
permission,
|
|
2487
|
+
outcome: await reach(ctx, token, permission)
|
|
2488
|
+
});
|
|
2489
|
+
if (!declared) {
|
|
2490
|
+
for (const t of tried) probe.say(rowFor(t, void 0));
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
for (const [heading, rule] of [["Should have", "required"], ["Should not have", "absent"]]) {
|
|
2494
|
+
const inGroup = tried.filter((t) => rules[t.permission] === rule);
|
|
2495
|
+
if (inGroup.length === 0) continue;
|
|
2496
|
+
probe.say({
|
|
2497
|
+
status: "heading",
|
|
2498
|
+
label: heading
|
|
2499
|
+
});
|
|
2500
|
+
for (const t of inGroup) probe.say(rowFor(t, rule));
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
// src/token-resolvers.ts
|
|
2506
|
+
/** Each token's resolver by its environment variable (`HOLOCRON_ADMIN_TOKEN`), which is what `--token-name` carries. */
|
|
2507
|
+
const TOKEN_RESOLVERS = {
|
|
2508
|
+
HOLOCRON_READ_TOKEN: resolveReadToken,
|
|
2509
|
+
HOLOCRON_ISSUES_TOKEN: resolveIssuesToken,
|
|
2510
|
+
HOLOCRON_SYNC_TOKEN: resolveSyncToken,
|
|
2511
|
+
HOLOCRON_RELEASE_TOKEN: resolveReleaseToken,
|
|
2512
|
+
HOLOCRON_ADMIN_TOKEN: resolveAdminToken,
|
|
2513
|
+
HOLOCRON_SECRETS_TOKEN: resolveSecretsToken,
|
|
2514
|
+
HOLOCRON_DEPLOY_TOKEN: resolveDeployToken,
|
|
2515
|
+
HOLOCRON_USER_SYNC_TOKEN: resolveUserSyncToken,
|
|
2516
|
+
HOLOCRON_ORG_TOKEN: resolveOrgToken
|
|
2517
|
+
};
|
|
2146
2518
|
// src/verify-token.ts
|
|
2147
2519
|
/** Checks a token against `GET /user` and returns who it belongs to, or why it failed. */
|
|
2148
2520
|
async function verifyToken(token, opts = {}) {
|
|
@@ -2271,16 +2643,25 @@ function createPlugin(options) {
|
|
|
2271
2643
|
}
|
|
2272
2644
|
return {
|
|
2273
2645
|
name: "@theholocron/holocron-plugin-github",
|
|
2274
|
-
probes: {
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2646
|
+
probes: {
|
|
2647
|
+
token: tokenProbe({
|
|
2648
|
+
repo: options.repo,
|
|
2649
|
+
baseUrl: (options.baseUrl ?? "https://api.github.com").replace(/\/$/, ""),
|
|
2650
|
+
fetch: options.fetch ?? fetch,
|
|
2651
|
+
resolve: (envName) => TOKEN_RESOLVERS[envName]?.(options),
|
|
2652
|
+
tokenNames: Object.keys(TOKEN_RESOLVERS)
|
|
2653
|
+
}),
|
|
2654
|
+
connection: connectionProbe({
|
|
2655
|
+
provider: "GitHub",
|
|
2656
|
+
url: options.baseUrl ?? "https://api.github.com",
|
|
2657
|
+
fetch: options.fetch,
|
|
2658
|
+
token: () => resolveReadToken(options),
|
|
2659
|
+
verify: (token) => verifyToken(token, {
|
|
2660
|
+
baseUrl: options.baseUrl,
|
|
2661
|
+
fetch: options.fetch
|
|
2662
|
+
})
|
|
2282
2663
|
})
|
|
2283
|
-
}
|
|
2664
|
+
},
|
|
2284
2665
|
capabilities: {
|
|
2285
2666
|
source: () => source(makeCtx(resolveAdminToken), makeOrgClient(), makeUserSyncClient()),
|
|
2286
2667
|
ci: () => ci(makeCtx(resolveReadToken)),
|
|
@@ -2296,22 +2677,4 @@ function createPlugin(options) {
|
|
|
2296
2677
|
* is supplied or the supplied token is rejected.
|
|
2297
2678
|
*/
|
|
2298
2679
|
const AUTH_HINT = "generate fine-grained Personal Access Tokens at https://github.com/settings/tokens (one per feature — see docs/tokens.md for required scopes per operation), then run: holocron new token github.<feature> <PAT>";
|
|
2299
|
-
/**
|
|
2300
|
-
* Every token this plugin takes, for `holocron doctor secrets --verify`. The release token is `optional`: only CI's release
|
|
2301
|
-
* workflow reads it (from the org secret), so a development environment has no reason to hold it.
|
|
2302
|
-
*/
|
|
2303
|
-
const tokens = [
|
|
2304
|
-
resolveReadToken.token,
|
|
2305
|
-
resolveIssuesToken.token,
|
|
2306
|
-
resolveSyncToken.token,
|
|
2307
|
-
{
|
|
2308
|
-
...resolveReleaseToken.token,
|
|
2309
|
-
optional: true
|
|
2310
|
-
},
|
|
2311
|
-
resolveAdminToken.token,
|
|
2312
|
-
resolveSecretsToken.token,
|
|
2313
|
-
resolveDeployToken.token,
|
|
2314
|
-
resolveUserSyncToken.token,
|
|
2315
|
-
resolveOrgToken.token
|
|
2316
|
-
];
|
|
2317
2680
|
export { AUTH_HINT, AuthError, GitHubCi, GitHubEnvironments, GitHubIssues, GitHubProjects, GitHubSecrets, GitHubSource, ci, createFeatureResolver, createGitHubClient, createPlugin, encryptSecret, environments, issues, normalizeTeamEntry, projects, resolveAdminToken, resolveDeployToken, resolveIssuesToken, resolveOrgToken, resolveReadToken, resolveReleaseToken, resolveSecretsToken, resolveSyncToken, resolveUserSyncToken, secrets, source, syncLabels, syncTeams, tokens, verifyToken };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theholocron/holocron-plugin-github",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.435",
|
|
4
4
|
"description": "Holocron plugin for GitHub. Implements source, ci, secrets, environments, and issues capabilities against the GitHub REST API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ci",
|
|
@@ -44,21 +44,21 @@
|
|
|
44
44
|
"tsx": "4.23.15",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"vitest": "^5.1.0",
|
|
47
|
-
"@theholocron/cli": "5.0.0-alpha.434",
|
|
48
|
-
"@theholocron/eslint-config": "^8.9.0-alpha.17",
|
|
49
47
|
"@theholocron/fs-utils": "^1.17.0-alpha.4",
|
|
50
|
-
"@theholocron/github-client": "^1.58.0-alpha.
|
|
51
|
-
"@theholocron/
|
|
48
|
+
"@theholocron/github-client": "^1.58.0-alpha.31",
|
|
49
|
+
"@theholocron/cli": "5.0.0-alpha.435",
|
|
52
50
|
"@theholocron/misc-utils": "^1.17.0-alpha.4",
|
|
51
|
+
"@theholocron/http-client": "^1.58.0-alpha.31",
|
|
52
|
+
"@theholocron/tsconfig": "^8.9.0-alpha.17",
|
|
53
53
|
"@theholocron/tsdown-config": "^8.9.0-alpha.17",
|
|
54
54
|
"@theholocron/vitest-config": "^8.9.0-alpha.17",
|
|
55
|
-
"@theholocron/
|
|
55
|
+
"@theholocron/eslint-config": "^8.9.0-alpha.17"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@theholocron/fs-utils": "^1.16.0",
|
|
59
59
|
"@theholocron/github-client": "^1.58.0",
|
|
60
60
|
"@theholocron/misc-utils": "^1.16.0",
|
|
61
|
-
"@theholocron/cli": "5.0.0-alpha.
|
|
61
|
+
"@theholocron/cli": "5.0.0-alpha.435"
|
|
62
62
|
},
|
|
63
63
|
"engines": {
|
|
64
64
|
"node": ">=22"
|