Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions __tests__/tools/rpk-docs/validate-overrides.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -532,3 +532,27 @@ describe('validate-overrides', () => {
})
})
})

describe('cloudOnly and selfHostedOnly mutual exclusion', () => {
const { validateOverrides } = require('../../../tools/rpk-docs/validate-overrides.js')

const tree = { name: 'rpk', commands: [{ name: 'cluster', commands: [{ name: 'info', commands: [] }] }] }

test('both set at command level is an error', () => {
const overrides = { commands: { 'rpk cluster info': { cloudOnly: true, selfHostedOnly: true } } }
const result = validateOverrides(overrides, tree)
expect(result.errors.some(e => /unsatisfiable/.test(e.message))).toBe(true)
})

test('both set on a flag is an error', () => {
const overrides = { commands: { 'rpk cluster info': { flags: { detailed: { cloudOnly: true, selfHostedOnly: true } } } } }
const result = validateOverrides(overrides, tree)
expect(result.errors.some(e => /flag "detailed"/.test(e.message))).toBe(true)
})

test('one of the two is fine', () => {
const overrides = { commands: { 'rpk cluster info': { selfHostedOnly: true, flags: { detailed: { cloudOnly: true } } } } }
const result = validateOverrides(overrides, tree)
expect(result.errors.filter(e => /unsatisfiable/.test(e.message))).toHaveLength(0)
})
})
40 changes: 40 additions & 0 deletions __tests__/tools/rpk-docs/whats-new-merge.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,43 @@ describe('linkable predicate coverage', () => {
expect(linkable('rpk topic create')).toBe(true)
})
})

describe('filterDiffForWhatsNew (rpk ai exclusion)', () => {
const { filterDiffForWhatsNew } = require('../../../tools/rpk-docs/rpk-docs-handler.js')

const diff = {
summary: {},
details: {
newCommands: [
{ path: 'rpk ai llm-provider create', name: 'create', description: 'x' },
{ path: 'rpk check install', name: 'install', description: 'y' },
],
removedCommands: [
{ path: 'rpk ai llm', name: 'llm', description: 'x' },
{ path: 'rpk aim', name: 'aim', description: 'not ai: prefix must respect word boundary' },
],
newFlags: [
{ commandPath: 'rpk ai auth login', flagName: 'no-browser' },
{ commandPath: 'rpk cluster info', flagName: 'detailed' },
],
changedDefaults: [
{ commandPath: 'rpk container start', flagName: 'console-image', oldDefault: 'a', newDefault: 'b' },
],
},
}

test('drops rpk ai entries from every category and keeps the rest', () => {
const out = filterDiffForWhatsNew(diff)
expect(out.details.newCommands.map(c => c.path)).toEqual(['rpk check install'])
expect(out.details.removedCommands.map(c => c.path)).toEqual(['rpk aim'])
expect(out.details.newFlags.map(f => f.commandPath)).toEqual(['rpk cluster info'])
expect(out.details.changedDefaults).toHaveLength(1)
// Input untouched
expect(diff.details.newCommands).toHaveLength(2)
})

test('custom exclusion list is honored', () => {
const out = filterDiffForWhatsNew(diff, ['rpk check'])
expect(out.details.newCommands.map(c => c.path)).toEqual(['rpk ai llm-provider create'])
})
})
58 changes: 52 additions & 6 deletions tools/rpk-docs/rpk-docs-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,37 @@ function makeLinkablePredicate(overridesData) {
}
}

// Command subtrees whose changes never belong in the Self-Managed What's
// new. rpk ai's documentation home is adp-docs, and the ADP release notes
// already cover its CLI changes per release. The plugin-release receiver
// workflow excludes ai from --update-whats-new for exactly this reason; the
// full-regeneration path must agree, or a full run floods the Self-Managed
// release notes with rpk ai entries (a rename release alone produces 21 new
// plus 21 removed bullets).
const WHATS_NEW_EXCLUDED_SUBTREES = ['rpk ai']

/**
* Return a copy of diffData without entries under the excluded subtrees.
* Only the published What's-new block filters; diff reports and PR
* summaries keep the full picture.
* @param {Object} diffData - Diff from generateRpkDiff
* @param {string[]} [excluded] - Command-path prefixes to drop
* @returns {Object} Filtered copy
*/
function filterDiffForWhatsNew(diffData, excluded = WHATS_NEW_EXCLUDED_SUBTREES) {
const outside = (cmdPath) => !excluded.some(prefix =>
cmdPath === prefix || (typeof cmdPath === 'string' && cmdPath.startsWith(prefix + ' ')))
const details = diffData.details || {}
const filteredDetails = { ...details }
for (const key of ['newCommands', 'newlyDeprecatedCommands', 'removedCommands', 'descriptionChanges']) {
if (Array.isArray(details[key])) filteredDetails[key] = details[key].filter(e => outside(e.path))
}
for (const key of ['newFlags', 'removedFlags', 'changedDefaults', 'changedFlagTypes', 'changedFlagRequirements', 'changedFlagDescriptions']) {
if (Array.isArray(details[key])) filteredDetails[key] = details[key].filter(e => outside(e.commandPath))
}
return { ...diffData, details: filteredDetails }
}

function updateWhatsNewFile(diffData, whatsNewPath, version, options = {}) {
// Each block opens with a "=== <version>" heading so accumulated blocks
// (successive RCs, multiple plugin releases) never collide on section ids
Expand Down Expand Up @@ -1350,8 +1381,18 @@ function acquireRpkBinary(rpkVersion, options = {}) {
console.warn(`Native build failed (${nativeErr.message.split('\n')[0]}); building in a container...`)
const goVersion = getRequiredGoVersion(sourcePath)
const goImage = goVersion ? `golang:${goVersion}` : 'golang:1'
// Cross-compile for the HOST platform: the container reports
// GOOS=linux, and a linux binary dies silently when executed on the
// macOS host that needs it for plugin installs (review finding on the
// 5.3.0 train). rpk builds with CGO disabled, so cross-compilation
// from the linux container is safe.
const hostGoos = process.platform === 'darwin' ? 'darwin' : 'linux'
const hostGoarch = process.arch === 'arm64' ? 'arm64' : 'amd64'
const buildResult = spawnSync('docker', [
'run', '--rm',
'-e', `GOOS=${hostGoos}`,
'-e', `GOARCH=${hostGoarch}`,
'-e', 'CGO_ENABLED=0',
'-v', `${path.resolve(sourcePath)}:/rpk-source:ro`,
'-v', `${workDir}:/out`,
'-w', '/rpk-source',
Expand Down Expand Up @@ -1914,10 +1955,14 @@ async function handleRpkDocsGeneration(options = {}) {
// render without xrefs because plugin subtrees may render as
// partials with no linkable pages.
if (whatsNewPath) {
const label = resolvedVersion
? `${plugin} plugin ${resolvedVersion}`
: `${plugin} plugin`
updateWhatsNewFile(pluginDiffData, whatsNewPath, label, { xrefs: false, sectionHeading: '== rpk plugins' })
if (WHATS_NEW_EXCLUDED_SUBTREES.includes(`rpk ${plugin}`)) {
console.log(`Skipping What's new for rpk ${plugin}: its documentation home covers CLI changes in its own release notes`)
} else {
const label = resolvedVersion
? `${plugin} plugin ${resolvedVersion}`
: `${plugin} plugin`
updateWhatsNewFile(pluginDiffData, whatsNewPath, label, { xrefs: false, sectionHeading: '== rpk plugins' })
}
}
}

Expand Down Expand Up @@ -2030,7 +2075,7 @@ async function handleRpkDocsGeneration(options = {}) {

// Update what's-new file if requested
if (whatsNewPath) {
updateWhatsNewFile(diffData, whatsNewPath, rpkVersion, { linkable: makeLinkablePredicate(overridesData), hasSubcommands: makeSubcommandPredicate(tree) })
updateWhatsNewFile(filterDiffForWhatsNew(diffData), whatsNewPath, rpkVersion, { linkable: makeLinkablePredicate(overridesData), hasSubcommands: makeSubcommandPredicate(tree) })
}
} else {
console.warn(`Warning: Could not load previous version ${diffVersion} for diff`)
Expand Down Expand Up @@ -2350,7 +2395,7 @@ async function handleRpkDocsGeneration(options = {}) {

// Update what's-new file if requested
if (whatsNewPath) {
updateWhatsNewFile(diffData, whatsNewPath, rpkVersion, { linkable: makeLinkablePredicate(overridesData), hasSubcommands: makeSubcommandPredicate(tree) })
updateWhatsNewFile(filterDiffForWhatsNew(diffData), whatsNewPath, rpkVersion, { linkable: makeLinkablePredicate(overridesData), hasSubcommands: makeSubcommandPredicate(tree) })
}
} else {
console.warn(`Warning: Could not load previous version ${diffVersion} for diff`)
Expand Down Expand Up @@ -2764,6 +2809,7 @@ module.exports = {
getPlatformDescription,
getCurrentPlatform,
updateOverridesWithIntroducedVersions,
filterDiffForWhatsNew,
computeDescriptionCoverage,
updateWhatsNewFile,
KNOWN_PLUGINS,
Expand Down
16 changes: 16 additions & 0 deletions tools/rpk-docs/validate-overrides.js
Original file line number Diff line number Diff line change
Expand Up @@ -465,8 +465,24 @@ function validatePlatforms(commandOverride, context) {
function validateFlags(commandOverride, context) {
const result = new ValidationResult()

// cloudOnly and selfHostedOnly are mutually exclusive: setting both wraps
// the row in an ifdef::env-cloud + ifndef::env-cloud pair that no build
// can satisfy, so the row silently vanishes from BOTH sites.
if (commandOverride.cloudOnly && commandOverride.selfHostedOnly) {
result.addError(
'cloudOnly and selfHostedOnly are both set: the command row is unsatisfiable and disappears from every build. Set at most one.',
context
)
}

if (commandOverride.flags) {
for (const [flagName, flagOverride] of Object.entries(commandOverride.flags)) {
if (flagOverride.cloudOnly && flagOverride.selfHostedOnly) {
result.addError(
`cloudOnly and selfHostedOnly are both set on flag "${flagName}": the flag row is unsatisfiable and disappears from every build. Set at most one.`,
`${context}.flags.${flagName}`
)
}
// Validate flag name format
if (!flagName.match(/^[a-zA-Z][-a-zA-Z0-9]*$/)) {
result.addWarning(
Expand Down
Loading