c8ctl-plugin-nano 1.26.0 → 1.26.1
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/c8ctl-plugin.js +116 -8
- package/package.json +8 -8
package/c8ctl-plugin.js
CHANGED
|
@@ -2295,14 +2295,9 @@ function makeSecretResolver(kind) {
|
|
|
2295
2295
|
// Resolve the names a job needs (setup.secretRefs, plus the repo/PR credential
|
|
2296
2296
|
// when allowPr). Returns resolved values + a list of names that were missing so
|
|
2297
2297
|
// the caller can fail the job with a clear provisioning error.
|
|
2298
|
-
function resolveJobSecrets(resolver, envelope) {
|
|
2298
|
+
function resolveJobSecrets(resolver, envelope, { ghAuthToken = ghAuthTokenFromCli } = {}) {
|
|
2299
2299
|
const names = new Set();
|
|
2300
2300
|
for (const n of envelope.setup?.secretRefs || []) if (n) names.add(n);
|
|
2301
|
-
if (envelope.task?.allowPr) {
|
|
2302
|
-
const provider = envelope.repository?.provider || 'github';
|
|
2303
|
-
const authRef = envelope.repository?.authRef || (provider === 'github' ? 'GITHUB_TOKEN' : undefined);
|
|
2304
|
-
if (authRef) names.add(authRef);
|
|
2305
|
-
}
|
|
2306
2301
|
const resolved = {};
|
|
2307
2302
|
const missing = [];
|
|
2308
2303
|
for (const name of names) {
|
|
@@ -2310,6 +2305,34 @@ function resolveJobSecrets(resolver, envelope) {
|
|
|
2310
2305
|
if (v === undefined) missing.push(name);
|
|
2311
2306
|
else resolved[name] = v;
|
|
2312
2307
|
}
|
|
2308
|
+
// The github clone/push credential is resolved with a gh-CLI fallback so a
|
|
2309
|
+
// default GITHUB_TOKEN isn't reported "missing" merely because it isn't in the
|
|
2310
|
+
// env when `gh auth login` provides it. A custom authRef stays strict.
|
|
2311
|
+
if (envelope.task?.allowPr) {
|
|
2312
|
+
const provider = envelope.repository?.provider || 'github';
|
|
2313
|
+
const authRef = envelope.repository?.authRef;
|
|
2314
|
+
const ref = normalizeAuthRef(authRef);
|
|
2315
|
+
if (ref.kind === 'invalid') {
|
|
2316
|
+
// A present-but-blank authRef is a misconfiguration: surface it as missing
|
|
2317
|
+
// so provisioning sheds rather than silently borrowing the default/gh token.
|
|
2318
|
+
if (!missing.includes('repository.authRef')) missing.push('repository.authRef');
|
|
2319
|
+
} else {
|
|
2320
|
+
const ghAuthRef = ref.kind === 'custom'
|
|
2321
|
+
? ref.name
|
|
2322
|
+
: (provider === 'github' ? 'GITHUB_TOKEN' : undefined);
|
|
2323
|
+
if (ghAuthRef) {
|
|
2324
|
+
names.add(ghAuthRef);
|
|
2325
|
+
const token = githubCloneToken({ provider, authRef, secretResolver: resolver, ghAuthToken });
|
|
2326
|
+
const missingIdx = missing.indexOf(ghAuthRef);
|
|
2327
|
+
if (token) {
|
|
2328
|
+
resolved[ghAuthRef] = token;
|
|
2329
|
+
if (missingIdx !== -1) missing.splice(missingIdx, 1);
|
|
2330
|
+
} else if (missingIdx === -1) {
|
|
2331
|
+
missing.push(ghAuthRef);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2313
2336
|
return { resolved, missing, names: [...names] };
|
|
2314
2337
|
}
|
|
2315
2338
|
|
|
@@ -2460,6 +2483,82 @@ function credArgs() {
|
|
|
2460
2483
|
return ['-c', 'credential.helper='];
|
|
2461
2484
|
}
|
|
2462
2485
|
|
|
2486
|
+
// Fall back to the `gh` CLI's stored credential when GITHUB_TOKEN is not exported
|
|
2487
|
+
// to the env. Most interactive setups authenticate with `gh auth login` (keychain)
|
|
2488
|
+
// rather than an env var, so an env-only secret resolver yields no token and a
|
|
2489
|
+
// private/internal clone fails with "could not read Username". Best effort: returns
|
|
2490
|
+
// a trimmed token, or null when gh is missing / not logged in. The token is fed to
|
|
2491
|
+
// git via GIT_ASKPASS only (never argv/URL/helper), preserving the ephemeral-token
|
|
2492
|
+
// guarantee.
|
|
2493
|
+
// Memoized for the process lifetime: this is a synchronous spawnSync (up to a
|
|
2494
|
+
// 10s timeout) that can be reached per job, and jobs may run concurrently
|
|
2495
|
+
// (maxParallelJobs > 1), so consult the CLI at most once per worker run rather
|
|
2496
|
+
// than blocking every handler. A sentinel distinguishes "not yet computed" from
|
|
2497
|
+
// a cached null (gh missing / not logged in).
|
|
2498
|
+
//
|
|
2499
|
+
// Memoization alone still lets the *first* job pay the synchronous spawn on the
|
|
2500
|
+
// event loop, stalling any sibling handlers (and lock-extension heartbeats) for
|
|
2501
|
+
// up to the timeout. So `nano work` primes this cache once at startup via
|
|
2502
|
+
// `primeGhAuthToken()` — before the poll loop — moving the one unavoidable
|
|
2503
|
+
// blocking spawn off the job-handling path entirely. Any later call is a warm
|
|
2504
|
+
// cache hit; the memoization here is the safety net for paths that never primed.
|
|
2505
|
+
const GH_AUTH_TOKEN_UNSET = Symbol('gh-auth-token-unset');
|
|
2506
|
+
let ghAuthTokenCache = GH_AUTH_TOKEN_UNSET;
|
|
2507
|
+
function ghAuthTokenFromCli() {
|
|
2508
|
+
if (ghAuthTokenCache !== GH_AUTH_TOKEN_UNSET) return ghAuthTokenCache;
|
|
2509
|
+
let token = null;
|
|
2510
|
+
try {
|
|
2511
|
+
const r = spawnSync('gh', ['auth', 'token'], { encoding: 'utf8', timeout: 10_000 });
|
|
2512
|
+
const tok = r.status === 0 ? (r.stdout || '').trim() : '';
|
|
2513
|
+
token = tok || null;
|
|
2514
|
+
} catch {
|
|
2515
|
+
token = null;
|
|
2516
|
+
}
|
|
2517
|
+
ghAuthTokenCache = token;
|
|
2518
|
+
return token;
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Warm the gh-token cache once, off the job-handling path. Safe to call any
|
|
2522
|
+
// number of times: the first call performs the (possibly blocking) lookup, the
|
|
2523
|
+
// rest are cache hits. Returns true once the cache is populated.
|
|
2524
|
+
function primeGhAuthToken() {
|
|
2525
|
+
ghAuthTokenFromCli();
|
|
2526
|
+
return ghAuthTokenCache !== GH_AUTH_TOKEN_UNSET;
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
// Normalize a repository authRef into one of three intents. Trimming matters so
|
|
2530
|
+
// a present-but-blank authRef ('' or whitespace) is treated as a misconfiguration
|
|
2531
|
+
// rather than "absent": absence enables the default/gh fallback, but a blank
|
|
2532
|
+
// custom ref must NOT silently borrow the operator's gh login.
|
|
2533
|
+
// { kind: 'default' } no custom authRef configured (undefined/null)
|
|
2534
|
+
// { kind: 'custom', name } a non-empty custom authRef (strict)
|
|
2535
|
+
// { kind: 'invalid' } authRef present but blank (config error)
|
|
2536
|
+
function normalizeAuthRef(authRef) {
|
|
2537
|
+
if (authRef === undefined || authRef === null) return { kind: 'default' };
|
|
2538
|
+
const trimmed = String(authRef).trim();
|
|
2539
|
+
if (trimmed === '') return { kind: 'invalid' };
|
|
2540
|
+
return { kind: 'custom', name: trimmed };
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// Resolve the github clone/push credential. The default credential (env
|
|
2544
|
+
// GITHUB_TOKEN) falls back to the gh CLI's stored token so `gh auth login`
|
|
2545
|
+
// setups work without exporting GITHUB_TOKEN. A custom `authRef` is honored
|
|
2546
|
+
// strictly (env/secret resolver only, no gh fallback) so a misconfigured named
|
|
2547
|
+
// secret surfaces as missing rather than silently borrowing the operator's gh
|
|
2548
|
+
// login. An authRef that is present but blank is a misconfiguration and yields no
|
|
2549
|
+
// token (never the gh fallback). `ghAuthToken` is injectable for testing.
|
|
2550
|
+
// Returns a token or null.
|
|
2551
|
+
function githubCloneToken({ provider, authRef, secretResolver, ghAuthToken = ghAuthTokenFromCli }) {
|
|
2552
|
+
const prov = provider || 'github';
|
|
2553
|
+
const ref = normalizeAuthRef(authRef);
|
|
2554
|
+
if (ref.kind === 'invalid') return null;
|
|
2555
|
+
const usesDefault = prov === 'github' && ref.kind === 'default';
|
|
2556
|
+
const name = ref.kind === 'custom' ? ref.name : (prov === 'github' ? 'GITHUB_TOKEN' : null);
|
|
2557
|
+
let token = name ? (secretResolver.resolve(name) || null) : null;
|
|
2558
|
+
if (!token && usesDefault) token = ghAuthToken() || null;
|
|
2559
|
+
return token;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2463
2562
|
// Clone repo into <runDir>/workspace and check out / create the working branch.
|
|
2464
2563
|
// Returns { workspaceDir, gitEnv, startSha, workingBranch, remote }. Throws a
|
|
2465
2564
|
// ProvisionError (token-redacted) on any git failure so the caller can shed.
|
|
@@ -3194,6 +3293,12 @@ async function workAgent(req, flags) {
|
|
|
3194
3293
|
const extraNote = extraJobTypes.length > 0 ? ` (${extraJobTypes.length} via --job-type)` : '';
|
|
3195
3294
|
logger.info(` listening on ${jobTypes.length} job type(s)${extraNote}: ${jobTypes.join(' ')}`);
|
|
3196
3295
|
logger.info(` max parallel: ${maxParallelJobs}; recovery window: ${recoveryWindowMs}ms; idle timeout: ${idleTimeoutMs}ms; hard cap: ${hardCapMs > 0 ? `${hardCapMs}ms` : 'off'}; poll timeout: ${pollTimeoutMs}ms`);
|
|
3296
|
+
// Warm the gh-token cache now, off the job-handling path: githubCloneToken()
|
|
3297
|
+
// may consult `gh auth token` (a synchronous spawn, up to 10s) as its default
|
|
3298
|
+
// credential fallback, and doing that inside a job handler would stall sibling
|
|
3299
|
+
// handlers + lock heartbeats when maxParallelJobs > 1. Priming here pays that
|
|
3300
|
+
// cost once at startup so every later lookup is a warm cache hit.
|
|
3301
|
+
primeGhAuthToken();
|
|
3197
3302
|
logger.info('Polling for work — press Ctrl-C to stop.');
|
|
3198
3303
|
|
|
3199
3304
|
// When launched under the supervisor, report per-job activity — which job(s)
|
|
@@ -3295,8 +3400,8 @@ async function workAgent(req, flags) {
|
|
|
3295
3400
|
let repoToken = null;
|
|
3296
3401
|
if (hasRepo) {
|
|
3297
3402
|
const provider = envelope.repository.provider || 'github';
|
|
3298
|
-
const authRef = envelope.repository.authRef
|
|
3299
|
-
|
|
3403
|
+
const authRef = envelope.repository.authRef;
|
|
3404
|
+
repoToken = githubCloneToken({ provider, authRef, secretResolver }); // absent → anonymous clone
|
|
3300
3405
|
try {
|
|
3301
3406
|
mkdirSync(agentRunsRoot(), { recursive: true });
|
|
3302
3407
|
runDir = mkdtempSync(join(agentRunsRoot(), 'run-'));
|
|
@@ -6217,6 +6322,9 @@ export {
|
|
|
6217
6322
|
reconcileAgentPr,
|
|
6218
6323
|
reapAgentRunDirs,
|
|
6219
6324
|
authUrl,
|
|
6325
|
+
githubCloneToken,
|
|
6326
|
+
ghAuthTokenFromCli,
|
|
6327
|
+
primeGhAuthToken,
|
|
6220
6328
|
redactToken,
|
|
6221
6329
|
agentRunsRoot,
|
|
6222
6330
|
ProvisionError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -47,12 +47,12 @@
|
|
|
47
47
|
"semantic-release": "^25.0.3"
|
|
48
48
|
},
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.26.
|
|
51
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.26.
|
|
52
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.26.
|
|
53
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.26.
|
|
54
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.26.
|
|
55
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.26.
|
|
56
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.26.
|
|
50
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.26.1",
|
|
51
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.26.1",
|
|
52
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.26.1",
|
|
53
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.26.1",
|
|
54
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.26.1",
|
|
55
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.26.1",
|
|
56
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.26.1"
|
|
57
57
|
}
|
|
58
58
|
}
|