|
1 | 1 | import { relative, resolve } from 'node:path'; |
2 | 2 | import picomatch from 'picomatch'; |
3 | 3 | import { log, colorize } from '../utils/logger.ts'; |
4 | | -import { loadConfig, loadPackageConfig, getBumpyDir, matchGlob } from '../core/config.ts'; |
| 4 | +import { loadConfig, loadPackageConfig, getBumpyDir, matchGlob, resolveFixedGroups } from '../core/config.ts'; |
5 | 5 | import { discoverWorkspace } from '../core/workspace.ts'; |
6 | 6 | import { readBumpFiles, filterBranchBumpFiles } from '../core/bump-file.ts'; |
7 | 7 | import { getChangedFiles, getFileStatuses, getBaseCompareRef, readFileAtRef } from '../core/git.ts'; |
@@ -163,8 +163,15 @@ export async function checkCommand(rootDir: string, opts: CheckOptions = {}): Pr |
163 | 163 | return; |
164 | 164 | } |
165 | 165 |
|
166 | | - // Check which changed packages are missing bump files |
167 | | - const missing = changedPackages.filter((name) => !coveredPackages.has(name)); |
| 166 | + // Check which changed packages are missing bump files. Packages with |
| 167 | + // directBump: false can't have their own bump file — they count as covered |
| 168 | + // when a fixed-group member is covered, and otherwise point there. |
| 169 | + const { missing, hints } = resolveDirectBumpCoverage( |
| 170 | + changedPackages.filter((name) => !coveredPackages.has(name)), |
| 171 | + coveredPackages, |
| 172 | + packages, |
| 173 | + config, |
| 174 | + ); |
168 | 175 |
|
169 | 176 | // An empty bump file covers all remaining packages (in non-strict mode) |
170 | 177 | // It acts as a blanket acknowledgment that non-publishable changes are expected |
@@ -195,7 +202,8 @@ export async function checkCommand(rootDir: string, opts: CheckOptions = {}): Pr |
195 | 202 |
|
196 | 203 | (willFail ? log.error : log.warn)(`${missing.length} changed package(s) missing bump files:\n`); |
197 | 204 | for (const name of missing) { |
198 | | - console.log(` ${colorize(name, 'yellow')}`); |
| 205 | + const hint = hints.get(name); |
| 206 | + console.log(` ${colorize(name, 'yellow')}${hint ? ` — ${hint}` : ''}`); |
199 | 207 | } |
200 | 208 |
|
201 | 209 | if (effectiveBumpFiles.length > 0) { |
@@ -237,6 +245,42 @@ function printBumpFileList( |
237 | 245 | } |
238 | 246 | } |
239 | 247 |
|
| 248 | +/** |
| 249 | + * Coverage adjustment for `directBump: false` packages. Such a package never gets its |
| 250 | + * own bump file — its changes ship by bumping another member of its fixed group. So a |
| 251 | + * missing directBump package counts as covered when any other fixed-group member is |
| 252 | + * covered; otherwise it stays missing, with a hint pointing at the bumpable members. |
| 253 | + */ |
| 254 | +export function resolveDirectBumpCoverage( |
| 255 | + missing: string[], |
| 256 | + covered: Set<string>, |
| 257 | + packages: Map<string, WorkspacePackage>, |
| 258 | + config: BumpyConfig, |
| 259 | +): { missing: string[]; hints: Map<string, string> } { |
| 260 | + const fixedGroups = resolveFixedGroups(config, packages.keys()); |
| 261 | + const stillMissing: string[] = []; |
| 262 | + const hints = new Map<string, string>(); |
| 263 | + |
| 264 | + for (const name of missing) { |
| 265 | + if (packages.get(name)?.bumpy?.directBump !== false) { |
| 266 | + stillMissing.push(name); |
| 267 | + continue; |
| 268 | + } |
| 269 | + const group = fixedGroups.find((members) => members.includes(name)); |
| 270 | + if (group?.some((member) => member !== name && covered.has(member))) continue; |
| 271 | + |
| 272 | + stillMissing.push(name); |
| 273 | + const bumpable = (group ?? []).filter((m) => m !== name && packages.get(m)?.bumpy?.directBump !== false); |
| 274 | + hints.set( |
| 275 | + name, |
| 276 | + bumpable.length > 0 |
| 277 | + ? `has directBump: false — add a bump for its fixed-group member ${bumpable.join(' or ')} instead` |
| 278 | + : 'has directBump: false — it only receives propagated bumps; add it to a fixed group or bump its cascade source', |
| 279 | + ); |
| 280 | + } |
| 281 | + return { missing: stillMissing, hints }; |
| 282 | +} |
| 283 | + |
240 | 284 | /** Map changed files to the packages they belong to */ |
241 | 285 | export async function findChangedPackages( |
242 | 286 | changedFiles: string[], |
|
0 commit comments