@redpanda-data/docs-extensions-and-macros 5.42.0 → 5.44.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/bin/doc-tools.js CHANGED
@@ -1476,6 +1476,9 @@ automation
1476
1476
  * parsing `-X list` text for rpk versions that predate it. Hidden -X options
1477
1477
  * appear in neither source, so they are excluded automatically.
1478
1478
  *
1479
+ * The table is sectioned by the API group rpk reports for each option, and is
1480
+ * a single flat table on rpk versions that report no groups.
1481
+ *
1479
1482
  * The main rpk-docs pipeline also writes this partial from the tree it
1480
1483
  * already holds; this standalone command is for targeted refreshes without
1481
1484
  * a full generation run.
@@ -1492,7 +1495,7 @@ automation
1492
1495
  */
1493
1496
  automation
1494
1497
  .command('rpk-env-partial')
1495
- .description('Generate the -X -> RPK_* env var mapping partial from rpk -X list output.')
1498
+ .description('Generate the -X -> RPK_* env var mapping partial from rpk itself.')
1496
1499
  .option('-r, --ref <ref>', 'Git branch or tag to build rpk from (e.g., dev, v26.2.1). Clones from GitHub.')
1497
1500
  .option('--from-source <path>', 'Path to local rpk source (src/go/rpk directory)')
1498
1501
  .option('--rpk-bin <path>', 'Path to an existing rpk binary (skips clone and build)')
@@ -0,0 +1,84 @@
1
+ 'use strict'
2
+
3
+ /**
4
+ * Git credential helper environment
5
+ *
6
+ * Every doc-tools code path that clones or fetches a private Redpanda repo
7
+ * with the git CLI needs the same thing: hand git a GitHub token without the
8
+ * token ever becoming visible outside the process. This module is the single
9
+ * implementation of that.
10
+ *
11
+ * The token travels through a per-invocation credential helper registered
12
+ * with GIT_CONFIG_* environment variables, so:
13
+ *
14
+ * - It is never a `git -c` argument or part of the remote URL, so it cannot
15
+ * be read out of argv (`ps`, /proc/<pid>/cmdline) for the length of a
16
+ * multi-minute clone, and git cannot echo it back in an error message.
17
+ * - It is never written into the clone's .git/config, where it would sit
18
+ * readable on disk for every later step that reads the checkout.
19
+ * - Nothing goes through a shell the caller has to quote for.
20
+ *
21
+ * Use getGitHubToken() from ./github-token to resolve the token, then pass
22
+ * the result here. Pair it with redactCredentials() on any git stderr that
23
+ * reaches an Error message or a CI log.
24
+ */
25
+
26
+ // The variable the helper reads the token from at callback time. Only the
27
+ // name appears in the (static, secret-free) helper command string.
28
+ const TOKEN_ENV_VAR = 'DOC_TOOLS_GIT_CREDENTIAL_TOKEN'
29
+
30
+ /**
31
+ * Environment for a git subprocess that must authenticate to github.com.
32
+ *
33
+ * With no token, `baseEnv` is returned unchanged rather than half-configured:
34
+ * git then falls back to the host's own credential helpers, which is what a
35
+ * local run relies on and all a public repo needs. Callers that require a
36
+ * token should check for one and fail with their own message first.
37
+ *
38
+ * The helper is scoped to `credential.https://github.com.helper`, so no other
39
+ * host is ever offered the token, and entry 0 clears inherited helpers so a
40
+ * system credential manager cannot intercept (or prompt) before ours answers.
41
+ * Any GIT_CONFIG_* entries already in `baseEnv` are replaced.
42
+ *
43
+ * @param {string|null|undefined} token - GitHub token, or falsy for no auth
44
+ * @param {NodeJS.ProcessEnv} [baseEnv=process.env] - Environment to extend
45
+ * @returns {NodeJS.ProcessEnv} Environment to pass as a spawn/exec `env`
46
+ */
47
+ function gitAuthEnv(token, baseEnv = process.env) {
48
+ if (!token) return baseEnv
49
+ return {
50
+ ...baseEnv,
51
+ [TOKEN_ENV_VAR]: token,
52
+ GIT_CONFIG_COUNT: '2',
53
+ GIT_CONFIG_KEY_0: 'credential.helper',
54
+ GIT_CONFIG_VALUE_0: '',
55
+ GIT_CONFIG_KEY_1: 'credential.https://github.com.helper',
56
+ GIT_CONFIG_VALUE_1: `!f() { echo "username=x-access-token"; echo "password=$${TOKEN_ENV_VAR}"; }; f`
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Strip anything credential-shaped out of text before it is surfaced
62
+ * anywhere that gets logged -- an Error message ends up in plain CI logs.
63
+ *
64
+ * Covers a credential in a remote URL's userinfo (https://<token>@github.com/...),
65
+ * which git echoes back verbatim on common failures (a private repo it cannot
66
+ * see, a bad ref), and a Basic-auth header in case git ever echoes failing
67
+ * config back. Defense in depth: gitAuthEnv() already keeps the token out of
68
+ * argv and URLs, so this covers credentials that came from somewhere else,
69
+ * such as a URL the caller was handed.
70
+ *
71
+ * @param {string} text - Text to scrub (nullish becomes '')
72
+ * @returns {string} Text with credentials replaced by ***
73
+ */
74
+ function redactCredentials(text) {
75
+ return String(text || '')
76
+ .replace(/\/\/[^/@\s]+@/g, '//***@')
77
+ .replace(/(authorization:\s*basic\s+)\S+/gi, '$1***')
78
+ }
79
+
80
+ module.exports = {
81
+ gitAuthEnv,
82
+ redactCredentials,
83
+ TOKEN_ENV_VAR
84
+ }
@@ -6,6 +6,7 @@ const { execSync } = require('child_process')
6
6
  const fs = require('fs')
7
7
  const path = require('path')
8
8
  const { getGitHubToken } = require('../cli-utils/github-token')
9
+ const { gitAuthEnv, redactCredentials } = require('../cli-utils/git-credential-env')
9
10
 
10
11
  /**
11
12
  * Configure Antora to use full git clones instead of shallow clones.
@@ -72,27 +73,21 @@ module.exports.register = function ({ config, playbook }) {
72
73
 
73
74
  // Private repos need credentials: the git CLI does not share Antora's
74
75
  // built-in credential manager, so resolve a token (GIT_CREDENTIALS first,
75
- // then the common token env vars) and hand it to git through a credential
76
- // helper. Both the config and the token travel via environment variables
77
- // (GIT_CONFIG_* and GIT_FULL_CLONE_TOKEN), so nothing touches the command
78
- // line, the remote URL, or the cached .git/config, and there is no shell
79
- // quoting to break on other platforms. Config entry 0 clears any inherited
80
- // helpers; entry 1 registers a helper scoped to github.com so no other
81
- // host is ever offered the token.
76
+ // then the common token env vars) and hand it to git through gitAuthEnv's
77
+ // credential helper. Both the config and the token travel via environment
78
+ // variables, so nothing touches the command line, the remote URL, or the
79
+ // cached .git/config, and there is no shell quoting to break on other
80
+ // platforms.
82
81
  const token = getGitHubToken()
83
82
  const gitCommand = 'git fetch --unshallow'
84
- const gitEnv = { ...process.env, GIT_TERMINAL_PROMPT: '0' }
85
83
  if (token) {
86
84
  logger.info(' → Using GitHub token for unshallow fetches')
87
- gitEnv.GIT_FULL_CLONE_TOKEN = token
88
- gitEnv.GIT_CONFIG_COUNT = '2'
89
- gitEnv.GIT_CONFIG_KEY_0 = 'credential.helper'
90
- gitEnv.GIT_CONFIG_VALUE_0 = ''
91
- gitEnv.GIT_CONFIG_KEY_1 = 'credential.https://github.com.helper'
92
- gitEnv.GIT_CONFIG_VALUE_1 = '!f() { echo "username=x-access-token"; echo "password=$GIT_FULL_CLONE_TOKEN"; }; f'
93
85
  } else {
94
86
  logger.info(' → No GitHub token found; unshallow may fail for private repos')
95
87
  }
88
+ // GIT_TERMINAL_PROMPT=0 in both cases: a prompt in a build has no one to
89
+ // answer it, so fail fast instead of hanging.
90
+ const gitEnv = gitAuthEnv(token, { ...process.env, GIT_TERMINAL_PROMPT: '0' })
96
91
 
97
92
  for (const aggregate of contentAggregate) {
98
93
  for (const origin of aggregate.origins || []) {
@@ -123,7 +118,7 @@ module.exports.register = function ({ config, playbook }) {
123
118
  if (err.killed) {
124
119
  logger.warn(` ✗ Unshallow timeout after ${duration}ms for ${path.basename(gitdir)} (increase unshallowTimeout if needed)`)
125
120
  } else {
126
- logger.warn(` ✗ Failed to unshallow ${path.basename(gitdir)}: ${err.message}`)
121
+ logger.warn(` ✗ Failed to unshallow ${path.basename(gitdir)}: ${redactCredentials(err.message)}`)
127
122
  }
128
123
  logger.warn(` ⚠️ Git dates may be inaccurate for this repo - consider using a pre-cloned cache`)
129
124
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.42.0",
3
+ "version": "5.44.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const { execSync, execFileSync, spawnSync } = require('child_process');
6
6
  const yaml = require('yaml');
7
7
  const { getMajorMinor } = require('../cli-utils/version');
8
+ const { gitAuthEnv, redactCredentials } = require('../cli-utils/git-credential-env');
8
9
 
9
10
  /**
10
11
  * Normalize a Git tag into a semantic version string.
@@ -662,24 +663,16 @@ async function bundleOpenAPI(options) {
662
663
  throw new Error('redpanda-data/streaming-enterprise is a private repository: set GH_TOKEN, GITHUB_TOKEN, or REDPANDA_GITHUB_TOKEN so the clone can authenticate.');
663
664
  }
664
665
 
665
- // Auth travels via a credential helper registered through GIT_CONFIG_*
666
- // environment variables, never as a git -c argument, so the token
667
- // cannot appear in this process's argv (visible to anything that can
668
- // list processes) or get embedded in an exec error's command-line
669
- // message. It's also never embedded in the remote URL or persisted in
670
- // the clone's .git/config.
671
- const gitEnv = { ...process.env };
672
- if (token && repositoryUrl.includes('github.com')) {
673
- if (!quiet) {
674
- console.log('🔑 Using authenticated clone (token provided)');
675
- }
676
- gitEnv.BUNDLE_OPENAPI_CLONE_TOKEN = token;
677
- gitEnv.GIT_CONFIG_COUNT = '2';
678
- gitEnv.GIT_CONFIG_KEY_0 = 'credential.helper';
679
- gitEnv.GIT_CONFIG_VALUE_0 = '';
680
- gitEnv.GIT_CONFIG_KEY_1 = 'credential.https://github.com.helper';
681
- gitEnv.GIT_CONFIG_VALUE_1 = '!f() { echo "username=x-access-token"; echo "password=$BUNDLE_OPENAPI_CLONE_TOKEN"; }; f';
666
+ // gitAuthEnv hands the token to git through a credential helper carried
667
+ // in the subprocess environment, so it cannot appear in this process's
668
+ // argv (visible to anything that can list processes) or in an exec
669
+ // error's command line, and is never embedded in the remote URL or
670
+ // persisted in the clone's .git/config.
671
+ const useToken = Boolean(token) && repositoryUrl.includes('github.com');
672
+ if (useToken && !quiet) {
673
+ console.log('🔑 Using authenticated clone (token provided)');
682
674
  }
675
+ const gitEnv = gitAuthEnv(useToken ? token : null, { ...process.env });
683
676
 
684
677
  try {
685
678
  execFileSync('git', ['clone', '--depth', '1', '--branch', tag, repositoryUrl, 'redpanda'], {
@@ -689,7 +682,7 @@ async function bundleOpenAPI(options) {
689
682
  timeout: 60000 // 1 minute timeout
690
683
  });
691
684
  } catch (cloneError) {
692
- throw new Error(`Failed to clone repository: ${cloneError.message}`);
685
+ throw new Error(`Failed to clone repository: ${redactCredentials(cloneError.message)}`);
693
686
  }
694
687
 
695
688
  const repoDir = path.join(tempDir, 'redpanda');
@@ -5,6 +5,7 @@ const fs = require('fs')
5
5
  const os = require('os')
6
6
  const path = require('path')
7
7
  const { getGitHubToken, getGitHubApiToken } = require('../../cli-utils/github-token')
8
+ const { gitAuthEnv, redactCredentials } = require('../../cli-utils/git-credential-env')
8
9
 
9
10
  /**
10
11
  * rp_util's config schema JSON, for any streaming-enterprise ref (tag,
@@ -58,49 +59,6 @@ const SCHEMA_FLAGS = [
58
59
  * @param {string} ref - Branch, tag, or commit SHA
59
60
  * @param {string} destDir - Destination directory (must not exist yet)
60
61
  */
61
- /**
62
- * Strip a credential out of text before it's surfaced anywhere that gets
63
- * logged -- an Error message ends up in plain CI logs. Covers a credential
64
- * embedded in a Git remote URL's userinfo (e.g. https://<token>@github.com/...),
65
- * which Git echoes back verbatim in common failures (a private repo it can't
66
- * access, a bad ref, ...), and a Basic-auth header in case Git ever echoes
67
- * failing config back -- defense in depth: this module keeps the token out
68
- * of argv and URLs entirely (see gitAuthEnv).
69
- */
70
- function redactCredentials(text) {
71
- return String(text || '')
72
- .replace(/\/\/[^/@\s]+@/g, '//***@')
73
- .replace(/(authorization:\s*basic\s+)\S+/gi, '$1***')
74
- }
75
-
76
- /**
77
- * Build the environment that authenticates git's github.com requests via a
78
- * per-invocation credential helper, injected through GIT_CONFIG_* env vars.
79
- * No token byte ever appears in argv (readable by any local process via
80
- * `ps`/`/proc/<pid>/cmdline` for the full multi-minute clone) or in a URL
81
- * (echoed verbatim by git on common failures), and nothing is written into
82
- * the resulting clone's .git/config, where it would otherwise sit readable
83
- * on disk for the entire downstream Bazel/Docker build. Same env-only
84
- * pattern the property-docs Makefile migration uses.
85
- * @param {string} token
86
- * @returns {object} env for spawnSync
87
- */
88
- function gitAuthEnv(token) {
89
- return {
90
- ...process.env,
91
- RP_UTIL_FETCH_GIT_TOKEN: token,
92
- GIT_CONFIG_COUNT: '2',
93
- // Clear any inherited helpers first so a system credential manager
94
- // can't intercept (or prompt) before ours answers.
95
- GIT_CONFIG_KEY_0: 'credential.helper',
96
- GIT_CONFIG_VALUE_0: '',
97
- GIT_CONFIG_KEY_1: 'credential.https://github.com.helper',
98
- // The helper reads the token from its own environment at callback time;
99
- // argv carries only this static, secret-free string.
100
- GIT_CONFIG_VALUE_1: '!f() { echo "username=x-access-token"; echo "password=$RP_UTIL_FETCH_GIT_TOKEN"; }; f'
101
- }
102
- }
103
-
104
62
  function cloneStreamingEnterprise(ref, destDir) {
105
63
  const token = getGitHubToken()
106
64
  if (!token) {
@@ -17,6 +17,8 @@ const fs = require('fs')
17
17
  const path = require('path')
18
18
  const os = require('os')
19
19
  const { spawnSync } = require('child_process')
20
+ const { getGitHubToken } = require('../../cli-utils/github-token')
21
+ const { gitAuthEnv, redactCredentials } = require('../../cli-utils/git-credential-env')
20
22
 
21
23
  /**
22
24
  * Read command titles from generated partials. The title line is
@@ -78,21 +80,35 @@ function fetchPartialsDir({ docsRepo, docsRef, plugin, sourcePath }) {
78
80
  // both carry the single-source tag, so either family can be stubbed
79
81
  const sparsePath = sourcePath || `modules/reference/partials/rpk-${plugin}`
80
82
 
81
- console.log(`Fetching ${sparsePath} from ${docsRepo}@${docsRef}...`)
83
+ // The docs repo is private, so authenticate when the environment offers a
84
+ // token (GIT_CREDENTIALS in Antora builds; ACTIONS_BOT_TOKEN, GITHUB_TOKEN,
85
+ // and friends in workflows). Clone anonymously otherwise, and say so, so a
86
+ // failed run in CI names the missing piece instead of a bare git prompt.
87
+ const token = getGitHubToken()
88
+ // gitAuthEnv keeps the token in the subprocess environment only: never in
89
+ // argv, never in the remote URL, never in the clone's .git/config.
90
+ const env = gitAuthEnv(token)
91
+ const authNote = token ? '' : ' (no GitHub token found; cloning anonymously)'
92
+ console.log(`Fetching ${sparsePath} from ${docsRepo}@${docsRef}${authNote}...`)
82
93
  const cloneResult = spawnSync('git', [
83
94
  'clone', '--depth', '1', '--filter=blob:none', '--sparse',
84
95
  '--branch', docsRef,
85
96
  `https://github.com/${docsRepo}.git`, repoDir
86
- ], { encoding: 'utf8', timeout: 180000 })
97
+ ], { encoding: 'utf8', timeout: 180000, env })
87
98
  if (cloneResult.status !== 0) {
88
- throw new Error(`Failed to clone ${docsRepo}@${docsRef}: ${cloneResult.stderr}`)
99
+ const hint = token
100
+ ? ''
101
+ : `\nIf ${docsRepo} is private, set ACTIONS_BOT_TOKEN, GITHUB_TOKEN, REDPANDA_GITHUB_TOKEN, or GIT_CREDENTIALS so the clone can authenticate.`
102
+ throw new Error(`Failed to clone ${docsRepo}@${docsRef}: ${redactCredentials(cloneResult.stderr)}${hint}`)
89
103
  }
90
104
 
105
+ // --filter=blob:none deferred the blobs, so the sparse checkout pulls them
106
+ // now and needs the same credential helper as the clone.
91
107
  const sparseResult = spawnSync('git', ['sparse-checkout', 'set', sparsePath], {
92
- cwd: repoDir, encoding: 'utf8', timeout: 60000
108
+ cwd: repoDir, encoding: 'utf8', timeout: 60000, env
93
109
  })
94
110
  if (sparseResult.status !== 0) {
95
- throw new Error(`Failed sparse checkout of ${sparsePath}: ${sparseResult.stderr}`)
111
+ throw new Error(`Failed sparse checkout of ${sparsePath}: ${redactCredentials(sparseResult.stderr)}`)
96
112
  }
97
113
 
98
114
  const partialsDir = path.join(repoDir, sparsePath)
@@ -3227,7 +3227,11 @@ function writeEnvVarsPartial (tree, partialsDir) {
3227
3227
  return { written: false }
3228
3228
  }
3229
3229
  const { renderPartial, keyToEnvVar } = require('./generate-x-env-partial.js')
3230
- const options = tree.x_options.map(o => ({ name: o.name, env: o.env || keyToEnvVar(o.name) }))
3230
+ const options = tree.x_options.map(o => ({
3231
+ name: o.name,
3232
+ env: o.env || keyToEnvVar(o.name),
3233
+ groupTitle: o.group_title || null
3234
+ }))
3231
3235
  const output = path.join(partialsDir, 'rpk-env-vars.adoc')
3232
3236
  fs.mkdirSync(path.dirname(output), { recursive: true })
3233
3237
  fs.writeFileSync(output, renderPartial(options))
@@ -11,6 +11,12 @@
11
11
  * descriptions.
12
12
  * 2. `rpk -X list` text output, for rpk versions that predate x_options.
13
13
  *
14
+ * Each x_option also carries the API it configures, as `group` (a stable
15
+ * slug) and `group_title` (the heading), added in
16
+ * redpanda-data/streaming-enterprise#311. When rpk supplies them the table is
17
+ * sectioned by API, so the grouping comes from rpk rather than being restated
18
+ * here; rpk versions without them get one flat table.
19
+ *
14
20
  * The partial is included by both reference:rpk/rpk-x-options.adoc and
15
21
  * reference:environment-variables.adoc in the docs repo. Hidden -X options
16
22
  * (for example cloud_environment, whose values are deliberately
@@ -38,10 +44,12 @@ const GENERATED_BANNER = `// tag::generated[]
38
44
  /**
39
45
  * Extract -X options from `rpk --print-tree` JSON.
40
46
  * @param {string} output - Raw stdout from `rpk --print-tree`
41
- * @returns {Array<{name: string, env: string}>|null} options in display
42
- * order, or null when the tree has no x_options (rpk predates
43
- * redpanda#31520). The env name comes from rpk itself when present
47
+ * @returns {Array<{name: string, env: string, group: ?string, groupTitle: ?string}>|null}
48
+ * options in display order, or null when the tree has no x_options (rpk
49
+ * predates redpanda#31520). The env name comes from rpk itself when present
44
50
  * (the tree's env field is produced by the same derivation rpk reads).
51
+ * group and groupTitle are null on rpk versions that do not group the
52
+ * options.
45
53
  */
46
54
  function xOptionsFromTree (output) {
47
55
  let tree
@@ -51,7 +59,12 @@ function xOptionsFromTree (output) {
51
59
  return null
52
60
  }
53
61
  if (!Array.isArray(tree.x_options) || tree.x_options.length === 0) return null
54
- return tree.x_options.map(o => ({ name: o.name, env: o.env || keyToEnvVar(o.name) }))
62
+ return tree.x_options.map(o => ({
63
+ name: o.name,
64
+ env: o.env || keyToEnvVar(o.name),
65
+ group: o.group || null,
66
+ groupTitle: o.group_title || null
67
+ }))
55
68
  }
56
69
 
57
70
  /**
@@ -96,16 +109,53 @@ function keyToAnchor (key) {
96
109
  return key.replace(/\./g, '-')
97
110
  }
98
111
 
112
+ /**
113
+ * Group options by the API group rpk reports, in first-seen order.
114
+ *
115
+ * Returns null unless every option carries a group title, so a table is
116
+ * either fully sectioned or not sectioned at all: a half-sectioned table
117
+ * would read as though the options with no heading belonged to the section
118
+ * above them.
119
+ *
120
+ * Grouping by title rather than slicing on the boundaries means a group whose
121
+ * options are not contiguous still gets one heading instead of two.
122
+ * @param {Array<{name: string, env: string, groupTitle: ?string}>} options
123
+ * @returns {Array<{title: string, options: Array<Object>}>|null}
124
+ */
125
+ function groupOptions (options) {
126
+ if (!options.every(o => o.groupTitle)) return null
127
+ const byTitle = new Map()
128
+ for (const option of options) {
129
+ const existing = byTitle.get(option.groupTitle)
130
+ if (existing) existing.push(option)
131
+ else byTitle.set(option.groupTitle, [option])
132
+ }
133
+ return [...byTitle].map(([title, opts]) => ({ title, options: opts }))
134
+ }
135
+
136
+ /**
137
+ * Render one table row for an option.
138
+ * @param {{name: string, env: string}} option
139
+ * @returns {string}
140
+ */
141
+ function renderRow ({ name, env }) {
142
+ return `|xref:reference:rpk/rpk-x-options.adoc#${keyToAnchor(name)}[${name}] |${env}`
143
+ }
144
+
99
145
  /**
100
146
  * Render the partial's AsciiDoc content.
101
- * @param {Array<{name: string, env: string}>} options - -X options
147
+ *
148
+ * The table is sectioned by API group when rpk reports one for every option,
149
+ * and is a single flat table otherwise (rpk versions that predate the groups,
150
+ * and the -X list fallback, which has no group data to parse).
151
+ * @param {Array<{name: string, env: string, groupTitle: ?string}>} options - -X options
102
152
  * @returns {string}
103
153
  */
104
154
  function renderPartial (options) {
105
- const rows = options.map(({ name, env }) => {
106
- const xrefTarget = `xref:reference:rpk/rpk-x-options.adoc#${keyToAnchor(name)}[${name}]`
107
- return `|${xrefTarget} |${env}`
108
- })
155
+ const groups = groupOptions(options)
156
+ const body = groups
157
+ ? groups.map(g => `2+s|${g.title}\n${g.options.map(renderRow).join('\n')}`).join('\n\n')
158
+ : options.map(renderRow).join('\n')
109
159
  return `${GENERATED_BANNER}
110
160
  Every \`-X\` option has a corresponding \`RPK_*\` environment variable. Convert by prefixing with \`RPK_\` and replacing dots with underscores:
111
161
 
@@ -113,7 +163,7 @@ Every \`-X\` option has a corresponding \`RPK_*\` environment variable. Convert
113
163
  |===
114
164
  |\`-X\` Option |Environment Variable
115
165
 
116
- ${rows.join('\n')}
166
+ ${body}
117
167
  |===
118
168
  // end::generated[]
119
169
  `
@@ -276,6 +326,7 @@ function handleXEnvPartialGeneration (options) {
276
326
 
277
327
  module.exports = {
278
328
  xOptionsFromTree,
329
+ groupOptions,
279
330
  parseXList,
280
331
  keyToEnvVar,
281
332
  keyToAnchor,
@@ -12,6 +12,7 @@ const { downloadRpkBinary, RPK_RELEASE_TAG_RE, RPK_CDN_BASE } = require('../../c
12
12
  const { generateRpkDiff, printDiffReport, generateWhatsNewSection, flattenToMap } = require('./report-delta')
13
13
  const { loadAndValidateOverrides, ValidationResult } = require('./validate-overrides')
14
14
  const { validateDirectory, formatResults } = require('./validate-output')
15
+ const { gitAuthEnv, redactCredentials } = require('../../cli-utils/git-credential-env')
15
16
 
16
17
  /**
17
18
  * Known rpk plugins that are managed separately (have install/uninstall commands)
@@ -241,12 +242,10 @@ function prepareSourceFromRef(sourceRef, sourcePath = null) {
241
242
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'rpk-source-'))
242
243
  const repoDir = path.join(tmpDir, 'redpanda')
243
244
 
244
- // streaming-enterprise is private, so a token is required. Auth travels
245
- // via a credential helper registered through GIT_CONFIG_* environment
246
- // variables, never as a git -c argument, so the token cannot appear in
247
- // this process's argv (visible to anything that can list processes) and
248
- // never embedded in the remote URL, so no token is persisted in the
249
- // clone's .git/config either.
245
+ // streaming-enterprise is private, so a token is required. gitAuthEnv
246
+ // hands it to git through a credential helper carried in the subprocess
247
+ // environment, so it never reaches argv, the remote URL, or the clone's
248
+ // .git/config.
250
249
  const { getGitHubToken } = require('../../cli-utils/github-token')
251
250
  const token = getGitHubToken()
252
251
  if (!token) {
@@ -255,15 +254,7 @@ function prepareSourceFromRef(sourceRef, sourcePath = null) {
255
254
  'Set GH_TOKEN, GITHUB_TOKEN, or REDPANDA_GITHUB_TOKEN so the clone can authenticate.'
256
255
  )
257
256
  }
258
- const gitEnv = {
259
- ...process.env,
260
- RPK_SOURCE_CLONE_TOKEN: token,
261
- GIT_CONFIG_COUNT: '2',
262
- GIT_CONFIG_KEY_0: 'credential.helper',
263
- GIT_CONFIG_VALUE_0: '',
264
- GIT_CONFIG_KEY_1: 'credential.https://github.com.helper',
265
- GIT_CONFIG_VALUE_1: '!f() { echo "username=x-access-token"; echo "password=$RPK_SOURCE_CLONE_TOKEN"; }; f'
266
- }
257
+ const gitEnv = gitAuthEnv(token)
267
258
 
268
259
  console.log(`Sparse-cloning streaming-enterprise repo (ref: ${sourceRef}) to ${repoDir}...`)
269
260
 
@@ -287,7 +278,7 @@ function prepareSourceFromRef(sourceRef, sourcePath = null) {
287
278
  throw new Error(
288
279
  `Failed to clone streaming-enterprise repo with ref '${sourceRef}'.\n` +
289
280
  `Make sure the branch or tag exists.\n` +
290
- `Error: ${cloneResult.stderr}`
281
+ `Error: ${redactCredentials(cloneResult.stderr)}`
291
282
  )
292
283
  }
293
284
 
@@ -302,7 +293,7 @@ function prepareSourceFromRef(sourceRef, sourcePath = null) {
302
293
  })
303
294
 
304
295
  if (sparseResult.status !== 0) {
305
- throw new Error(`Failed to set sparse checkout: ${sparseResult.stderr}`)
296
+ throw new Error(`Failed to set sparse checkout: ${redactCredentials(sparseResult.stderr)}`)
306
297
  }
307
298
 
308
299
  console.log(`Sparse checkout complete`)