c8ctl-plugin-nano 1.35.5 → 1.35.6

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.
Files changed (2) hide show
  1. package/c8ctl-plugin.js +65 -8
  2. package/package.json +8 -8
package/c8ctl-plugin.js CHANGED
@@ -2799,6 +2799,52 @@ function runGit(args, { cwd, env, timeoutMs = 120_000 } = {}) {
2799
2799
  }
2800
2800
  }
2801
2801
 
2802
+ // Bound a git output string without decapitating it: a hard `.slice(0, max)`
2803
+ // drops the tail, but the real reason can live at either end of a long
2804
+ // multiline git error (the `fatal:` up top, wrapping/hint detail below). Keep a
2805
+ // head+tail window joined by an elision marker so both survive.
2806
+ function boundGitOutput(text, max = 500) {
2807
+ const s = String(text ?? '').trim();
2808
+ if (s.length <= max) return s;
2809
+ const marker = ' […] ';
2810
+ // When max is too small to fit even the elision marker the head+tail budget
2811
+ // goes negative, making the slices behave unexpectedly and return MORE than
2812
+ // max — so hard-cap to max (never below zero) in that degenerate case.
2813
+ if (max <= marker.length) return s.slice(0, Math.max(0, max));
2814
+ const budget = max - marker.length;
2815
+ const head = Math.ceil(budget * 0.6);
2816
+ const tail = budget - head;
2817
+ return `${s.slice(0, head)}${marker}${s.slice(s.length - tail)}`;
2818
+ }
2819
+
2820
+ // Build an informative, token-redacted failure reason from a runGit result.
2821
+ // Two things the old `stderr || stdout`.slice(0,500) message threw away:
2822
+ // 1. On a timeout Node SIGTERM-kills git (status→128, signal='SIGTERM') — say
2823
+ // so, and after how long, instead of surfacing only git's flushed
2824
+ // "Cloning into '…'..." progress line as if it were the failure.
2825
+ // 2. `stderr || stdout` let a non-empty-but-useless stderr mask a real reason
2826
+ // on stdout — capture BOTH streams (stderr then stdout) so either survives.
2827
+ function describeGitFailure(action, result, { token, timeoutMs } = {}) {
2828
+ const combined = boundGitOutput(
2829
+ redactToken([result && result.stderr, result && result.stdout].filter(Boolean).join('\n'), token),
2830
+ );
2831
+ // spawnSync's timeout kill lands as SIGTERM (its default killSignal); runGit
2832
+ // maps the null status to 128. Only SIGTERM means "timed out" — any OTHER
2833
+ // signal (e.g. SIGKILL from an OOM kill) is a distinct termination that we
2834
+ // must not misreport as a timeout.
2835
+ const signal = result && result.signal;
2836
+ if (signal === 'SIGTERM') {
2837
+ const secs = timeoutMs ? Math.round(timeoutMs / 1000) : null;
2838
+ const dur = secs ? ` after ${secs}s` : '';
2839
+ return `${action} timed out${dur} (SIGTERM)${combined ? `; last output: ${combined}` : ''}`;
2840
+ }
2841
+ if (signal) {
2842
+ return `${action} terminated by signal ${signal}${combined ? `: ${combined}` : ''}`;
2843
+ }
2844
+ const exit = result && result.status != null ? result.status : '?';
2845
+ return `${action} failed (exit ${exit})${combined ? `: ${combined}` : ''}`;
2846
+ }
2847
+
2802
2848
  // Write a GIT_ASKPASS helper that echoes $GIT_TOKEN, so the token reaches git
2803
2849
  // via the child's ENV — never on argv or in the remote URL. Uses a Node helper
2804
2850
  // (askpass.js reads GIT_TOKEN and writes it verbatim), launched by a per-OS
@@ -3082,17 +3128,25 @@ function provisionRepo({ envelope, token, runDir, timeoutMs = 120_000 }) {
3082
3128
 
3083
3129
  const clone = runGit(cloneArgs, { env: gitEnv, timeoutMs });
3084
3130
  if (clone.status !== 0) {
3085
- throw new ProvisionError(`git clone failed: ${redactToken(clone.stderr || clone.stdout, token).trim().slice(0, 500) || `exit ${clone.status}`}`);
3131
+ throw new ProvisionError(describeGitFailure('git clone', clone, { token, timeoutMs }));
3086
3132
  }
3087
3133
 
3088
3134
  if (isSha) {
3089
3135
  // The SHA may not be present under a shallow clone of the default branch —
3090
3136
  // fetch it explicitly (best effort), then check it out (detached HEAD).
3091
3137
  const fetch = runGit([...credArgs(), 'fetch', '--no-tags', 'origin', target], { cwd: workspaceDir, env: gitEnv, timeoutMs });
3092
- const co = runGit(['checkout', '--detach', target], { cwd: workspaceDir, env: gitEnv });
3138
+ const co = runGit(['checkout', '--detach', target], { cwd: workspaceDir, env: gitEnv, timeoutMs });
3093
3139
  if (co.status !== 0) {
3094
- const why = redactToken(co.stderr || fetch.stderr, token).trim().slice(0, 300);
3095
- throw new ProvisionError(`git checkout ${target} failed: ${why || `exit ${co.status}`}`);
3140
+ // Prefer the failing checkout's own output, but fall back to the fetch's
3141
+ // (a timeout/fatal there is the real cause the checkout can't recover from)
3142
+ // ONLY when fetch actually failed — a succeeded fetch (status 0) is not the
3143
+ // cause, and reporting it would yield a misleading "exit 0" message.
3144
+ const useCheckout = !!(co.stderr || co.stdout || fetch.status === 0);
3145
+ const source = useCheckout ? co : fetch;
3146
+ // Label the failure with the action whose output we actually report, so a
3147
+ // fetch timeout/fatal isn't misreported as a checkout failure.
3148
+ const action = useCheckout ? `git checkout ${target}` : `git fetch origin ${target}`;
3149
+ throw new ProvisionError(describeGitFailure(action, source, { token, timeoutMs }));
3096
3150
  }
3097
3151
  }
3098
3152
 
@@ -3127,8 +3181,8 @@ function provisionRepo({ envelope, token, runDir, timeoutMs = 120_000 }) {
3127
3181
  // finalizeGit skips the push/PR reconcile instead of pushing a bogus ref.
3128
3182
  let workingBranch = null;
3129
3183
  if (envelope.branch?.create) {
3130
- const cb = runGit(['checkout', '-B', envelope.branch.create], { cwd: workspaceDir, env: gitEnv });
3131
- if (cb.status !== 0) throw new ProvisionError(`git checkout -B ${envelope.branch.create} failed: ${redactToken(cb.stderr, token).trim().slice(0, 300)}`);
3184
+ const cb = runGit(['checkout', '-B', envelope.branch.create], { cwd: workspaceDir, env: gitEnv, timeoutMs });
3185
+ if (cb.status !== 0) throw new ProvisionError(describeGitFailure(`git checkout -B ${envelope.branch.create}`, cb, { token, timeoutMs }));
3132
3186
  workingBranch = envelope.branch.create;
3133
3187
  } else {
3134
3188
  const head = runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: workspaceDir, env: gitEnv });
@@ -3278,9 +3332,10 @@ function finalizeGit({ workspaceDir, gitEnv, startSha, workingBranch, envelope,
3278
3332
  if (!workingBranch) {
3279
3333
  out.detached = true; // clone landed on a tag/sha ⇒ no branch to push
3280
3334
  } else if (coerceBool(envelope.branch?.push, true) && out.commits.length > 0) {
3281
- const push = runGit([...credArgs(), 'push', '--set-upstream', 'origin', workingBranch], { cwd: workspaceDir, env: gitEnv });
3335
+ const pushTimeoutMs = 120_000; // matches runGit's default; surfaced in a timeout reason
3336
+ const push = runGit([...credArgs(), 'push', '--set-upstream', 'origin', workingBranch], { cwd: workspaceDir, env: gitEnv, timeoutMs: pushTimeoutMs });
3282
3337
  if (push.status === 0) out.pushed = true;
3283
- else out.pushError = redactToken(push.stderr || push.stdout, token).trim().slice(0, 300) || `push exit ${push.status}`;
3338
+ else out.pushError = describeGitFailure('git push', push, { token, timeoutMs: pushTimeoutMs });
3284
3339
  }
3285
3340
 
3286
3341
  if (workingBranch && envelope.task?.allowPr) {
@@ -8388,6 +8443,8 @@ export {
8388
8443
  startLockExtender,
8389
8444
  provisionRepo,
8390
8445
  finalizeGit,
8446
+ describeGitFailure,
8447
+ boundGitOutput,
8391
8448
  reconcileAgentPr,
8392
8449
  resolveCommitterIdentity,
8393
8450
  isPlaceholderEmail,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.35.5",
3
+ "version": "1.35.6",
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",
@@ -57,12 +57,12 @@
57
57
  },
58
58
  "optionalDependencies": {
59
59
  "node-pty": "^1.0.0",
60
- "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.35.5",
61
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.35.5",
62
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.35.5",
63
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.35.5",
64
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.35.5",
65
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.35.5",
66
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.35.5"
60
+ "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.35.6",
61
+ "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.35.6",
62
+ "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.35.6",
63
+ "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.35.6",
64
+ "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.35.6",
65
+ "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.35.6",
66
+ "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.35.6"
67
67
  }
68
68
  }